| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/public/cpp/app_list/app_list_config.h" |
| #include "ash/public/cpp/shelf_model.h" |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/task_runner_util.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_command_line.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "build/build_config.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/arc_apps.h" |
| #include "chrome/browser/apps/app_service/arc_apps_factory.h" |
| #include "chrome/browser/apps/app_service/arc_icon_once_loader.h" |
| #include "chrome/browser/chromeos/arc/arc_optin_uma.h" |
| #include "chrome/browser/chromeos/arc/arc_support_host.h" |
| #include "chrome/browser/chromeos/arc/arc_util.h" |
| #include "chrome/browser/chromeos/arc/session/arc_session_manager.h" |
| #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_service_test_base.h" |
| #include "chrome/browser/policy/profile_policy_connector.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h" |
| #include "chrome/browser/ui/app_list/app_service/app_service_app_item.h" |
| #include "chrome/browser/ui/app_list/app_service/app_service_app_model_builder.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_icon.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_launcher.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs_factory.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_test.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" |
| #include "chrome/browser/ui/app_list/arc/arc_default_app_list.h" |
| #include "chrome/browser/ui/app_list/arc/arc_fast_app_reinstall_starter.h" |
| #include "chrome/browser/ui/app_list/arc/arc_package_syncable_service.h" |
| #include "chrome/browser/ui/app_list/arc/arc_package_syncable_service_factory.h" |
| #include "chrome/browser/ui/app_list/arc/arc_pai_starter.h" |
| #include "chrome/browser/ui/app_list/arc/mock_arc_app_list_prefs_observer.h" |
| #include "chrome/browser/ui/app_list/chrome_app_list_item.h" |
| #include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h" |
| #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h" |
| #include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h" |
| #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" |
| #include "chrome/browser/web_applications/test/test_web_app_provider.h" |
| #include "chrome/browser/web_applications/test/web_app_test.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/constants/chromeos_switches.h" |
| #include "components/arc/arc_prefs.h" |
| #include "components/arc/arc_service_manager.h" |
| #include "components/arc/arc_util.h" |
| #include "components/arc/metrics/arc_metrics_constants.h" |
| #include "components/arc/mojom/app.mojom.h" |
| #include "components/arc/test/fake_app_instance.h" |
| #include "components/services/app_service/public/cpp/app_registry_cache.h" |
| #include "components/services/app_service/public/cpp/app_update.h" |
| #include "components/services/app_service/public/cpp/stub_icon_loader.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "components/sync/driver/sync_service.h" |
| #include "components/sync/model/fake_sync_change_processor.h" |
| #include "components/sync/model/sync_data.h" |
| #include "components/sync/model/sync_error_factory_mock.h" |
| #include "components/sync/protocol/sync.pb.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| namespace { |
| |
| constexpr char kTestPackageName[] = "fake.package.name2"; |
| constexpr char kFrameworkPackageName[] = "android"; |
| |
| constexpr int kFrameworkNycVersion = 25; |
| constexpr int kFrameworkPiVersion = 28; |
| |
| class FakeAppIconLoaderDelegate : public AppIconLoaderDelegate { |
| public: |
| FakeAppIconLoaderDelegate() = default; |
| ~FakeAppIconLoaderDelegate() override = default; |
| |
| void OnAppImageUpdated(const std::string& app_id, |
| const gfx::ImageSkia& image) override { |
| app_id_ = app_id; |
| image_ = image; |
| ++update_image_count_; |
| if (update_image_count_ == expected_update_image_count_ && |
| !icon_updated_callback_.is_null()) { |
| std::move(icon_updated_callback_).Run(); |
| } |
| } |
| |
| void WaitForIconUpdates(size_t expected_updates) { |
| base::RunLoop run_loop; |
| expected_update_image_count_ = expected_updates + update_image_count_; |
| icon_updated_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| size_t update_image_count() const { return update_image_count_; } |
| |
| const std::string& app_id() const { return app_id_; } |
| |
| const gfx::ImageSkia& image() { return image_; } |
| |
| private: |
| size_t update_image_count_ = 0; |
| size_t expected_update_image_count_ = 0; |
| std::string app_id_; |
| gfx::ImageSkia image_; |
| base::OnceClosure icon_updated_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeAppIconLoaderDelegate); |
| }; |
| |
| ArcAppIconDescriptor GetAppListIconDescriptor(ui::ScaleFactor scale_factor) { |
| return ArcAppIconDescriptor( |
| ash::AppListConfig::instance().grid_icon_dimension(), scale_factor); |
| } |
| |
| bool IsIconCreated(ArcAppListPrefs* prefs, |
| const std::string& app_id, |
| ui::ScaleFactor scale_factor) { |
| return base::PathExists( |
| prefs->GetIconPath(app_id, GetAppListIconDescriptor(scale_factor))); |
| } |
| |
| void WaitForIconCreation(ArcAppListPrefs* prefs, |
| const std::string& app_id, |
| ui::ScaleFactor scale_factor) { |
| const base::FilePath icon_path = |
| prefs->GetIconPath(app_id, GetAppListIconDescriptor(scale_factor)); |
| // Process pending tasks. This performs multiple thread hops, so we need |
| // to run it continuously until it is resolved. |
| do { |
| content::RunAllTasksUntilIdle(); |
| } while (!base::PathExists(icon_path)); |
| } |
| |
| void WaitForIconUpdates(Profile* profile, const std::string& app_id) { |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile, ash::AppListConfig::instance().grid_icon_dimension(), &delegate); |
| |
| icon_loader.FetchImage(app_id); |
| delegate.WaitForIconUpdates(1); |
| } |
| |
| enum class ArcState { |
| // By default, ARC is non-persistent and Play Store is unmanaged. |
| ARC_PLAY_STORE_UNMANAGED, |
| // ARC is non-persistent and Play Store is managed and enabled. |
| ARC_PLAY_STORE_MANAGED_AND_ENABLED, |
| // ARC is non-persistent and Play Store is managed and disabled. |
| ARC_PLAY_STORE_MANAGED_AND_DISABLED, |
| // ARC is persistent but without Play Store UI support. |
| ARC_WITHOUT_PLAY_STORE, |
| }; |
| |
| struct ArcAppModelBuilderParam { |
| const ArcState arc_state; |
| const web_app::ProviderType provider_type; |
| }; |
| |
| constexpr ArcAppModelBuilderParam kManagedArcStates[] = { |
| {ArcState::ARC_PLAY_STORE_MANAGED_AND_ENABLED, |
| web_app::ProviderType::kBookmarkApps}, |
| {ArcState::ARC_PLAY_STORE_MANAGED_AND_DISABLED, |
| web_app::ProviderType::kBookmarkApps}, |
| {ArcState::ARC_PLAY_STORE_MANAGED_AND_ENABLED, |
| web_app::ProviderType::kWebApps}, |
| {ArcState::ARC_PLAY_STORE_MANAGED_AND_DISABLED, |
| web_app::ProviderType::kWebApps}, |
| }; |
| |
| constexpr ArcAppModelBuilderParam kUnmanagedArcStates[] = { |
| {ArcState::ARC_PLAY_STORE_UNMANAGED, web_app::ProviderType::kBookmarkApps}, |
| {ArcState::ARC_WITHOUT_PLAY_STORE, web_app::ProviderType::kBookmarkApps}, |
| {ArcState::ARC_PLAY_STORE_UNMANAGED, web_app::ProviderType::kWebApps}, |
| {ArcState::ARC_WITHOUT_PLAY_STORE, web_app::ProviderType::kWebApps}, |
| }; |
| |
| void OnPaiStartedCallback(bool* started_flag) { |
| *started_flag = true; |
| } |
| |
| int GetAppListIconDimensionForScaleFactor(ui::ScaleFactor scale_factor) { |
| switch (scale_factor) { |
| case ui::SCALE_FACTOR_100P: |
| return ash::AppListConfig::instance().grid_icon_dimension(); |
| case ui::SCALE_FACTOR_200P: |
| return ash::AppListConfig::instance().grid_icon_dimension() * 2; |
| default: |
| NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| ArcAppListPrefs::AppInfo GetAppInfoExpectation(const arc::mojom::AppInfo& app, |
| bool launchable) { |
| return ArcAppListPrefs::AppInfo( |
| app.name, app.package_name, app.activity, std::string() /* intent_uri */, |
| std::string() /* icon_resource_id */, base::Time() /* last_launch_time */, |
| base::Time() /* install_time */, app.sticky, app.notifications_enabled, |
| true /* ready */, false /* suspended */, launchable /* show_in_launcher*/, |
| false /* shortcut */, launchable); |
| } |
| |
| MATCHER_P(ArcPackageInfoIs, package, "") { |
| return arg.Equals(*package); |
| } |
| |
| // For testing purposes, we want to pretend there are only ARC apps on the |
| // system. This method removes the others. |
| void RemoveNonArcApps(Profile* profile, |
| FakeAppListModelUpdater* model_updater, |
| bool flush) { |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(profile); |
| DCHECK(proxy); |
| if (flush) |
| proxy->FlushMojoCallsForTesting(); |
| proxy->AppRegistryCache().ForEachApp( |
| [&model_updater](const apps::AppUpdate& update) { |
| if (update.AppType() != apps::mojom::AppType::kArc) { |
| model_updater->RemoveItem(update.AppId()); |
| } |
| }); |
| } |
| |
| } // namespace |
| |
| class ArcAppModelBuilderTest |
| : public extensions::ExtensionServiceTestBase, |
| public ::testing::WithParamInterface<ArcAppModelBuilderParam> { |
| public: |
| ArcAppModelBuilderTest() { |
| if (GetProviderType() == web_app::ProviderType::kWebApps) { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } else if (GetProviderType() == web_app::ProviderType::kBookmarkApps) { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } |
| } |
| ~ArcAppModelBuilderTest() override { |
| // Release profile file in order to keep right sequence. |
| profile_.reset(); |
| } |
| |
| void SetUp() override { |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) { |
| arc::SetArcAlwaysStartWithoutPlayStoreForTesting(); |
| } |
| |
| model_ = std::make_unique<ash::ShelfModel>(); |
| |
| extensions::ExtensionServiceTestBase::SetUp(); |
| InitializeExtensionService(ExtensionServiceInitParams()); |
| service_->Init(); |
| |
| OnBeforeArcTestSetup(); |
| arc_test_.SetUp(profile_.get()); |
| |
| web_app::TestWebAppProvider::Get(profile_.get())->Start(); |
| CreateBuilder(); |
| |
| CreateLauncherController(); |
| |
| // Validating decoded content does not fit well for unit tests. |
| ArcAppIcon::DisableSafeDecodingForTesting(); |
| } |
| |
| void TearDown() override { |
| launcher_controller_.reset(); |
| arc_test_.TearDown(); |
| ResetBuilder(); |
| extensions::ExtensionServiceTestBase::TearDown(); |
| } |
| |
| ArcState GetArcState() const { return GetParam().arc_state; } |
| |
| web_app::ProviderType GetProviderType() const { |
| return GetParam().provider_type; |
| } |
| |
| ChromeLauncherController* CreateLauncherController() { |
| launcher_controller_ = std::make_unique<ChromeLauncherController>( |
| profile_.get(), model_.get()); |
| launcher_controller_->Init(); |
| return launcher_controller_.get(); |
| } |
| |
| void DisableFlushForAppService() { should_flush_for_app_service_ = false; } |
| |
| protected: |
| // Notifies that initial preparation is done, profile is ready and it is time |
| // to initialize ARC subsystem. |
| virtual void OnBeforeArcTestSetup() {} |
| |
| // Creates a new builder, destroying any existing one. |
| void CreateBuilder() { |
| ResetBuilder(); // Destroy any existing builder in the correct order. |
| |
| model_updater_ = std::make_unique<FakeAppListModelUpdater>(); |
| controller_ = std::make_unique<test::TestAppListControllerDelegate>(); |
| builder_ = std::make_unique<AppServiceAppModelBuilder>(controller_.get()); |
| builder_->Initialize(nullptr, profile_.get(), model_updater_.get()); |
| RemoveNonArcApps(profile_.get(), model_updater_.get(), |
| should_flush_for_app_service_); |
| } |
| |
| void ResetBuilder() { |
| builder_.reset(); |
| controller_.reset(); |
| model_updater_.reset(); |
| } |
| |
| size_t GetArcItemCount() const { |
| size_t arc_count = 0; |
| const size_t count = model_updater_->ItemCount(); |
| for (size_t i = 0; i < count; ++i) { |
| ChromeAppListItem* item = model_updater_->ItemAtForTest(i); |
| if (item->GetItemType() == AppServiceAppItem::kItemType) { |
| ++arc_count; |
| } |
| } |
| return arc_count; |
| } |
| |
| AppServiceAppItem* GetArcItem(size_t index) const { |
| size_t arc_count = 0; |
| const size_t count = model_updater_->ItemCount(); |
| AppServiceAppItem* arc_item = nullptr; |
| for (size_t i = 0; i < count; ++i) { |
| ChromeAppListItem* item = model_updater_->ItemAtForTest(i); |
| if (item->GetItemType() == AppServiceAppItem::kItemType) { |
| if (arc_count++ == index) { |
| arc_item = reinterpret_cast<AppServiceAppItem*>(item); |
| break; |
| } |
| } |
| } |
| EXPECT_NE(nullptr, arc_item); |
| return arc_item; |
| } |
| |
| AppServiceAppItem* FindArcItem(const std::string& id) const { |
| const size_t count = GetArcItemCount(); |
| AppServiceAppItem* found_item = nullptr; |
| for (size_t i = 0; i < count; ++i) { |
| AppServiceAppItem* item = GetArcItem(i); |
| if (item && item->id() == id) { |
| DCHECK(!found_item); |
| found_item = item; |
| } |
| } |
| return found_item; |
| } |
| |
| // Validate that prefs and model have right content. |
| void ValidateHaveApps(const std::vector<arc::mojom::AppInfo> apps) { |
| ValidateHaveAppsAndShortcuts(apps, std::vector<arc::mojom::ShortcutInfo>()); |
| } |
| |
| void ValidateHaveShortcuts( |
| const std::vector<arc::mojom::ShortcutInfo> shortcuts) { |
| ValidateHaveAppsAndShortcuts(std::vector<arc::mojom::AppInfo>(), shortcuts); |
| } |
| |
| void ValidateHaveAppsAndShortcuts( |
| const std::vector<arc::mojom::AppInfo> apps, |
| const std::vector<arc::mojom::ShortcutInfo> shortcuts) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| const std::vector<std::string> ids = prefs->GetAppIds(); |
| ASSERT_EQ(apps.size() + shortcuts.size(), ids.size()); |
| ASSERT_EQ(apps.size() + shortcuts.size(), GetArcItemCount()); |
| // In principle, order of items is not defined. |
| for (const auto& app : apps) { |
| const std::string id = ArcAppTest::GetAppId(app); |
| EXPECT_TRUE(base::Contains(ids, id)); |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(app.name, app_info->name); |
| EXPECT_EQ(app.package_name, app_info->package_name); |
| EXPECT_EQ(app.activity, app_info->activity); |
| |
| const AppServiceAppItem* app_item = FindArcItem(id); |
| ASSERT_NE(nullptr, app_item); |
| EXPECT_EQ(app.name, app_item->name()); |
| } |
| |
| for (auto& shortcut : shortcuts) { |
| const std::string id = ArcAppTest::GetAppId(shortcut); |
| EXPECT_TRUE(base::Contains(ids, id)); |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(shortcut.name, app_info->name); |
| EXPECT_EQ(shortcut.package_name, app_info->package_name); |
| EXPECT_EQ(shortcut.intent_uri, app_info->intent_uri); |
| |
| const AppServiceAppItem* app_item = FindArcItem(id); |
| ASSERT_NE(nullptr, app_item); |
| EXPECT_EQ(shortcut.name, app_item->name()); |
| } |
| } |
| |
| // Validate that prefs have right packages. |
| void ValidateHavePackages( |
| const std::vector<arc::mojom::ArcPackageInfoPtr>& packages) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| const std::vector<std::string> pref_packages = |
| prefs->GetPackagesFromPrefs(); |
| ASSERT_EQ(packages.size(), pref_packages.size()); |
| for (const auto& package : packages) { |
| const std::string& package_name = package->package_name; |
| std::unique_ptr<ArcAppListPrefs::PackageInfo> package_info = |
| prefs->GetPackage(package_name); |
| ASSERT_NE(nullptr, package_info.get()); |
| EXPECT_EQ(package->last_backup_android_id, |
| package_info->last_backup_android_id); |
| EXPECT_EQ(package->last_backup_time, package_info->last_backup_time); |
| EXPECT_EQ(package->sync, package_info->should_sync); |
| EXPECT_EQ(package->permission_states, package_info->permissions); |
| } |
| } |
| |
| // Validate that requested apps have required ready state and other apps have |
| // opposite state. |
| void ValidateAppReadyState(const std::vector<arc::mojom::AppInfo> apps, |
| bool ready) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(ids.size(), GetArcItemCount()); |
| |
| // Process requested apps. |
| for (auto& app : apps) { |
| const std::string id = ArcAppTest::GetAppId(app); |
| std::vector<std::string>::iterator it_id = |
| std::find(ids.begin(), ids.end(), id); |
| ASSERT_NE(it_id, ids.end()); |
| ids.erase(it_id); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(ready, app_info->ready); |
| } |
| |
| // Process the rest of the apps. |
| for (auto& id : ids) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_NE(ready, app_info->ready); |
| } |
| } |
| |
| // Validate that requested shortcuts have required ready state |
| void ValidateShortcutReadyState( |
| const std::vector<arc::mojom::ShortcutInfo> shortcuts, |
| bool ready) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(ids.size(), GetArcItemCount()); |
| |
| // Process requested apps. |
| for (auto& shortcut : shortcuts) { |
| const std::string id = ArcAppTest::GetAppId(shortcut); |
| std::vector<std::string>::iterator it_id = |
| std::find(ids.begin(), ids.end(), id); |
| ASSERT_NE(it_id, ids.end()); |
| ids.erase(it_id); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(ready, app_info->ready); |
| } |
| } |
| |
| // Validates that provided image is acceptable as ARC app icon. |
| void ValidateIcon(const gfx::ImageSkia& image) { |
| const int icon_dimension = |
| ash::AppListConfig::instance().grid_icon_dimension(); |
| EXPECT_EQ(icon_dimension, image.width()); |
| EXPECT_EQ(icon_dimension, image.height()); |
| |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| for (auto& scale_factor : scale_factors) { |
| const float scale = ui::GetScaleForScaleFactor(scale_factor); |
| EXPECT_TRUE(image.HasRepresentation(scale)); |
| const gfx::ImageSkiaRep& representation = image.GetRepresentation(scale); |
| EXPECT_FALSE(representation.is_null()); |
| EXPECT_EQ(base::ClampCeil(icon_dimension * scale), |
| representation.pixel_width()); |
| EXPECT_EQ(base::ClampCeil(icon_dimension * scale), |
| representation.pixel_height()); |
| } |
| } |
| |
| // Removes icon request record and allowd re-sending icon request. |
| void MaybeRemoveIconRequestRecord(const std::string& app_id) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| prefs->MaybeRemoveIconRequestRecord(app_id); |
| } |
| |
| arc::mojom::ArcPackageInfoPtr CreatePackage(const std::string& package_name) { |
| return CreatePackageWithVersion(package_name, 1 /* package_version */); |
| } |
| |
| arc::mojom::ArcPackageInfoPtr CreatePackageWithVersion( |
| const std::string& package_name, |
| int package_version) { |
| base::flat_map<arc::mojom::AppPermission, arc::mojom::PermissionStatePtr> |
| permissions; |
| permissions.emplace(arc::mojom::AppPermission::CAMERA, |
| arc::mojom::PermissionState::New(false /* granted */, |
| false /* managed */)); |
| permissions.emplace(arc::mojom::AppPermission::LOCATION, |
| arc::mojom::PermissionState::New(true /* granted */, |
| false /* managed */)); |
| return arc::mojom::ArcPackageInfo::New( |
| package_name, package_version, 1 /* last_backup_android_id */, |
| 1 /* last_backup_time */, true /* sync */, false /* system */, |
| false /* vpn_provider */, nullptr /* web_app_info */, base::nullopt, |
| std::move(permissions) /* permission states */); |
| } |
| |
| // Flush mojo calls to allow AppService async callbacks to run. |
| void FlushMojoCallsForAppService() { |
| apps::AppServiceProxy* app_service_proxy_ = |
| apps::AppServiceProxyFactory::GetForProfile(profile_.get()); |
| DCHECK(app_service_proxy_); |
| app_service_proxy_->FlushMojoCallsForTesting(); |
| } |
| |
| void AddPackage(const arc::mojom::ArcPackageInfoPtr& package) { |
| arc_test_.AddPackage(package->Clone()); |
| app_instance()->SendPackageAdded(package->Clone()); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void RemovePackage(const std::string& package_name) { |
| arc_test_.RemovePackage(package_name); |
| app_instance()->SendPackageUninstalled(package_name); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendRefreshAppList(const std::vector<arc::mojom::AppInfo>& apps) { |
| app_instance()->SendRefreshAppList(apps); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendInstallShortcuts( |
| const std::vector<arc::mojom::ShortcutInfo>& shortcuts) { |
| app_instance()->SendInstallShortcuts(shortcuts); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendInstallShortcut(const arc::mojom::ShortcutInfo& shortcut) { |
| app_instance()->SendInstallShortcut(shortcut); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendInstallationStarted(const std::string& package_name) { |
| app_instance()->SendInstallationStarted(package_name); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendInstallationFinished(const std::string& package_name, bool success) { |
| app_instance()->SendInstallationFinished(package_name, success); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendUninstallShortcut(const std::string& package_name, |
| const std::string& intent_uri) { |
| app_instance()->SendUninstallShortcut(package_name, intent_uri); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendPackageUninstalled(const std::string& package_name) { |
| app_instance()->SendPackageUninstalled(package_name); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SendPackageAppListRefreshed( |
| const std::string& package_name, |
| const std::vector<arc::mojom::AppInfo>& apps) { |
| app_instance()->SendPackageAppListRefreshed(package_name, apps); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SetArcPlayStoreEnabledForProfile(Profile* profile, bool enabled) { |
| arc::SetArcPlayStoreEnabledForProfile(profile, enabled); |
| FlushMojoCallsForAppService(); |
| } |
| |
| void SimulateDefaultAppAvailabilityTimeoutForTesting(ArcAppListPrefs* prefs) { |
| prefs->SimulateDefaultAppAvailabilityTimeoutForTesting(); |
| FlushMojoCallsForAppService(); |
| } |
| |
| AppListControllerDelegate* controller() { return controller_.get(); } |
| |
| TestingProfile* profile() { return profile_.get(); } |
| |
| ArcAppTest* arc_test() { return &arc_test_; } |
| |
| const std::vector<arc::mojom::AppInfo>& fake_apps() const { |
| return arc_test_.fake_apps(); |
| } |
| |
| const std::vector<arc::mojom::AppInfo>& fake_default_apps() const { |
| return arc_test_.fake_default_apps(); |
| } |
| |
| const std::vector<arc::mojom::ArcPackageInfoPtr>& fake_packages() const { |
| return arc_test_.fake_packages(); |
| } |
| |
| const std::vector<arc::mojom::ShortcutInfo>& fake_shortcuts() const { |
| return arc_test_.fake_shortcuts(); |
| } |
| |
| arc::FakeAppInstance* app_instance() { return arc_test_.app_instance(); } |
| |
| FakeAppListModelUpdater* model_updater() { return model_updater_.get(); } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| ArcAppTest arc_test_; |
| std::unique_ptr<FakeAppListModelUpdater> model_updater_; |
| std::unique_ptr<test::TestAppListControllerDelegate> controller_; |
| std::unique_ptr<AppServiceAppModelBuilder> builder_; |
| std::unique_ptr<ChromeLauncherController> launcher_controller_; |
| std::unique_ptr<ash::ShelfModel> model_; |
| bool should_flush_for_app_service_ = true; |
| |
| DISALLOW_COPY_AND_ASSIGN(ArcAppModelBuilderTest); |
| }; |
| |
| class ArcAppModelBuilderRecreate : public ArcAppModelBuilderTest { |
| public: |
| ArcAppModelBuilderRecreate() = default; |
| ~ArcAppModelBuilderRecreate() override = default; |
| |
| protected: |
| // Simulates ARC restart. |
| void RestartArc() { |
| StopArc(); |
| StartArc(); |
| } |
| |
| void StartArc() { |
| ArcAppListPrefsFactory::GetInstance()->RecreateServiceInstanceForTesting( |
| profile_.get()); |
| arc_test()->SetUp(profile_.get()); |
| CreateBuilder(); |
| } |
| |
| void StopArc() { |
| FlushMojoCallsForAppService(); |
| apps::ArcAppsFactory::GetInstance()->ShutDownForTesting(profile_.get()); |
| arc_test()->TearDown(); |
| ResetBuilder(); |
| } |
| |
| // ArcAppModelBuilderTest: |
| void OnBeforeArcTestSetup() override { |
| arc::ArcPackageSyncableServiceFactory::GetInstance()->SetTestingFactory( |
| profile_.get(), BrowserContextKeyedServiceFactory::TestingFactory()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ArcAppModelBuilderRecreate); |
| }; |
| |
| class ArcAppModelIconTest : public ArcAppModelBuilderRecreate { |
| public: |
| ArcAppModelIconTest() = default; |
| ~ArcAppModelIconTest() override = default; |
| |
| void SetUp() override { |
| ArcAppModelBuilderRecreate::SetUp(); |
| |
| std::vector<ui::ScaleFactor> supported_scale_factors; |
| supported_scale_factors.push_back(ui::SCALE_FACTOR_100P); |
| supported_scale_factors.push_back(ui::SCALE_FACTOR_200P); |
| scoped_supported_scale_factors_ = |
| std::make_unique<ui::test::ScopedSetSupportedScaleFactors>( |
| supported_scale_factors); |
| } |
| |
| void TearDown() override { |
| scoped_supported_scale_factors_.reset(); |
| ArcAppModelBuilderRecreate::TearDown(); |
| } |
| |
| protected: |
| // Simulates one test app is ready in the system. |
| std::string StartApp(int package_version) { |
| DCHECK(!fake_apps().empty()); |
| |
| const arc::mojom::AppInfo app = test_app(); |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| DCHECK(prefs); |
| |
| SendRefreshAppList({app}); |
| AddPackage(CreatePackageWithVersion(app.package_name, package_version)); |
| return app_id; |
| } |
| |
| // Simulates package of the test app is updated. |
| void UpdatePackage(int package_version) { |
| const arc::mojom::AppInfo app = test_app(); |
| auto package = CreatePackageWithVersion(app.package_name, package_version); |
| SendPackageAppListRefreshed(package->package_name, {app}); |
| app_instance()->SendPackageModified(std::move(package)); |
| } |
| |
| // Ensures that icons for the test app were updated for each scale factor. |
| void EnsureIconsUpdated() { |
| const arc::mojom::AppInfo app = test_app(); |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| const std::vector<std::unique_ptr<arc::FakeAppInstance::IconRequest>>& |
| icon_requests = app_instance()->icon_requests(); |
| ASSERT_EQ(2U, icon_requests.size()); |
| ASSERT_TRUE(icon_requests[0]->IsForApp(app)); |
| ASSERT_EQ(GetAppListIconDimensionForScaleFactor(ui::SCALE_FACTOR_100P), |
| icon_requests[0]->dimension()); |
| ASSERT_TRUE(icon_requests[1]->IsForApp(app)); |
| ASSERT_EQ(GetAppListIconDimensionForScaleFactor(ui::SCALE_FACTOR_200P), |
| icon_requests[1]->dimension()); |
| |
| WaitForIconUpdates(profile_.get(), app_id); |
| } |
| |
| arc::mojom::AppInfo test_app() const { return fake_apps()[0]; } |
| |
| private: |
| std::unique_ptr<ui::test::ScopedSetSupportedScaleFactors> |
| scoped_supported_scale_factors_; |
| DISALLOW_COPY_AND_ASSIGN(ArcAppModelIconTest); |
| }; |
| |
| class ArcDefaultAppTest : public ArcAppModelBuilderRecreate { |
| public: |
| ArcDefaultAppTest() = default; |
| ~ArcDefaultAppTest() override = default; |
| |
| void SetUp() override { ArcAppModelBuilderRecreate::SetUp(); } |
| |
| protected: |
| // ArcAppModelBuilderTest: |
| void OnBeforeArcTestSetup() override { |
| ArcDefaultAppList::UseTestAppsDirectory(); |
| arc_test()->set_wait_default_apps(IsWaitDefaultAppsNeeded()); |
| ArcAppModelBuilderRecreate::OnBeforeArcTestSetup(); |
| } |
| |
| // Returns true if test needs to wait for default apps on setup. |
| virtual bool IsWaitDefaultAppsNeeded() const { return true; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ArcDefaultAppTest); |
| }; |
| |
| class ArcAppLauncherForDefaultAppTest : public ArcDefaultAppTest { |
| public: |
| ArcAppLauncherForDefaultAppTest() = default; |
| ~ArcAppLauncherForDefaultAppTest() override = default; |
| |
| void SetUp() override { |
| DisableFlushForAppService(); |
| ArcDefaultAppTest::SetUp(); |
| } |
| |
| protected: |
| // ArcDefaultAppTest: |
| bool IsWaitDefaultAppsNeeded() const override { return false; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ArcAppLauncherForDefaultAppTest); |
| }; |
| |
| class ArcPlayStoreAppTest : public ArcDefaultAppTest { |
| public: |
| ArcPlayStoreAppTest() = default; |
| ~ArcPlayStoreAppTest() override = default; |
| |
| protected: |
| // ArcAppModelBuilderTest: |
| void OnBeforeArcTestSetup() override { |
| ArcDefaultAppTest::OnBeforeArcTestSetup(); |
| |
| base::DictionaryValue manifest; |
| manifest.SetString(extensions::manifest_keys::kName, "Play Store"); |
| manifest.SetString(extensions::manifest_keys::kVersion, "1"); |
| manifest.SetInteger(extensions::manifest_keys::kManifestVersion, 2); |
| manifest.SetString(extensions::manifest_keys::kDescription, |
| "Play Store for testing"); |
| |
| std::string error; |
| arc_support_host_ = extensions::Extension::Create( |
| base::FilePath(), extensions::Manifest::UNPACKED, manifest, |
| extensions::Extension::NO_FLAGS, arc::kPlayStoreAppId, &error); |
| |
| extensions::ExtensionService* extension_service = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| extension_service->AddExtension(arc_support_host_.get()); |
| } |
| |
| void SendPlayStoreApp() { |
| arc::mojom::AppInfo app; |
| app.name = "Play Store"; |
| app.package_name = arc::kPlayStorePackage; |
| app.activity = arc::kPlayStoreActivity; |
| app.sticky = GetArcState() != ArcState::ARC_WITHOUT_PLAY_STORE; |
| |
| SendRefreshAppList({app}); |
| } |
| |
| private: |
| scoped_refptr<extensions::Extension> arc_support_host_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ArcPlayStoreAppTest); |
| }; |
| |
| class ArcDefaultAppForManagedUserTest : public ArcPlayStoreAppTest { |
| public: |
| ArcDefaultAppForManagedUserTest() = default; |
| ~ArcDefaultAppForManagedUserTest() override = default; |
| |
| protected: |
| bool IsEnabledByPolicy() const { |
| switch (GetArcState()) { |
| case ArcState::ARC_PLAY_STORE_MANAGED_AND_ENABLED: |
| case ArcState::ARC_PLAY_STORE_MANAGED_AND_DISABLED: |
| case ArcState::ARC_WITHOUT_PLAY_STORE: |
| return false; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| // ArcPlayStoreAppTest: |
| void OnBeforeArcTestSetup() override { |
| policy::ProfilePolicyConnector* const connector = |
| profile()->GetProfilePolicyConnector(); |
| connector->OverrideIsManagedForTesting(true); |
| profile()->GetTestingPrefService()->SetManagedPref( |
| arc::prefs::kArcEnabled, |
| std::make_unique<base::Value>(IsEnabledByPolicy())); |
| |
| ArcPlayStoreAppTest::OnBeforeArcTestSetup(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ArcDefaultAppForManagedUserTest); |
| }; |
| |
| TEST_P(ArcAppModelBuilderTest, ArcPackagePref) { |
| ValidateHavePackages({}); |
| app_instance()->SendRefreshPackageList( |
| ArcAppTest::ClonePackages(fake_packages())); |
| ValidateHavePackages(fake_packages()); |
| |
| RemovePackage(kTestPackageName); |
| ValidateHavePackages(fake_packages()); |
| |
| auto package = |
| CreatePackageWithVersion(kTestPackageName, 2 /* package_version */); |
| package->last_backup_android_id = 2; |
| package->last_backup_time = 2; |
| AddPackage(package); |
| ValidateHavePackages(fake_packages()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RefreshAllFillsContent) { |
| ValidateHaveApps({}); |
| SendRefreshAppList(fake_apps()); |
| ValidateHaveApps(fake_apps()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, InstallUninstallShortcut) { |
| ValidateHaveApps({}); |
| |
| std::vector<arc::mojom::ShortcutInfo> shortcuts = fake_shortcuts(); |
| ASSERT_GE(shortcuts.size(), 2U); |
| |
| // Adding package is required to safely call SendPackageUninstalled. |
| AddPackage(CreatePackage(shortcuts[1].package_name)); |
| |
| SendInstallShortcuts(shortcuts); |
| ValidateHaveShortcuts(shortcuts); |
| |
| // Uninstall first shortcut and validate it was removed. |
| const std::string package_name = shortcuts[0].package_name; |
| const std::string intent_uri = shortcuts[0].intent_uri; |
| shortcuts.erase(shortcuts.begin()); |
| SendUninstallShortcut(package_name, intent_uri); |
| ValidateHaveShortcuts(shortcuts); |
| |
| // Requests to uninstall non-existing shortcuts should be just ignored. |
| EXPECT_NE(package_name, shortcuts[0].package_name); |
| EXPECT_NE(intent_uri, shortcuts[0].intent_uri); |
| SendUninstallShortcut(package_name, shortcuts[0].intent_uri); |
| SendUninstallShortcut(shortcuts[0].package_name, intent_uri); |
| ValidateHaveShortcuts(shortcuts); |
| |
| // Removing package should also remove associated shortcuts. |
| SendPackageUninstalled(shortcuts[0].package_name); |
| shortcuts.erase(shortcuts.begin()); |
| ValidateHaveShortcuts(shortcuts); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RefreshAllPreservesShortcut) { |
| ValidateHaveApps(std::vector<arc::mojom::AppInfo>()); |
| SendRefreshAppList(fake_apps()); |
| ValidateHaveApps(fake_apps()); |
| |
| SendInstallShortcuts(fake_shortcuts()); |
| ValidateHaveAppsAndShortcuts(fake_apps(), fake_shortcuts()); |
| |
| SendRefreshAppList(fake_apps()); |
| ValidateHaveAppsAndShortcuts(fake_apps(), fake_shortcuts()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, MultipleRefreshAll) { |
| ValidateHaveApps(std::vector<arc::mojom::AppInfo>()); |
| // Send info about all fake apps except last. |
| std::vector<arc::mojom::AppInfo> apps1(fake_apps().begin(), |
| fake_apps().end() - 1); |
| SendRefreshAppList(apps1); |
| // At this point all apps (except last) should exist and be ready. |
| ValidateHaveApps(apps1); |
| ValidateAppReadyState(apps1, true); |
| |
| // Send info about all fake apps except first. |
| std::vector<arc::mojom::AppInfo> apps2(fake_apps().begin() + 1, |
| fake_apps().end()); |
| SendRefreshAppList(apps2); |
| // At this point all apps should exist but first one should be non-ready. |
| ValidateHaveApps(apps2); |
| ValidateAppReadyState(apps2, true); |
| |
| // Send info about all fake apps. |
| SendRefreshAppList(fake_apps()); |
| // At this point all apps should exist and be ready. |
| ValidateHaveApps(fake_apps()); |
| ValidateAppReadyState(fake_apps(), true); |
| |
| // Send info no app available. |
| std::vector<arc::mojom::AppInfo> no_apps; |
| SendRefreshAppList(no_apps); |
| // At this point no app should exist. |
| ValidateHaveApps(no_apps); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, StopStartServicePreserveApps) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| EXPECT_EQ(0u, GetArcItemCount()); |
| EXPECT_EQ(0u, prefs->GetAppIds().size()); |
| |
| SendRefreshAppList(fake_apps()); |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(fake_apps().size(), ids.size()); |
| ValidateAppReadyState(fake_apps(), true); |
| |
| // Stopping service does not delete items. It makes them non-ready. |
| arc_test()->StopArcInstance(); |
| // Ids should be the same. |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateAppReadyState(fake_apps(), false); |
| |
| // Ids should be the same. |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateAppReadyState(fake_apps(), false); |
| |
| // Refreshing app list makes apps available. |
| arc_test()->RestartArcInstance(); |
| SendRefreshAppList(fake_apps()); |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateAppReadyState(fake_apps(), true); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, StopStartServicePreserveShortcuts) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| EXPECT_EQ(0u, GetArcItemCount()); |
| EXPECT_EQ(0u, prefs->GetAppIds().size()); |
| |
| SendInstallShortcuts(fake_shortcuts()); |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(fake_shortcuts().size(), ids.size()); |
| ValidateShortcutReadyState(fake_shortcuts(), true); |
| |
| // Stopping service does not delete items. It makes them non-ready. |
| arc_test()->StopArcInstance(); |
| // Ids should be the same. |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateShortcutReadyState(fake_shortcuts(), false); |
| |
| // Ids should be the same. |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateShortcutReadyState(fake_shortcuts(), false); |
| |
| // Refreshing app list makes apps available. |
| arc_test()->RestartArcInstance(); |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>()); |
| EXPECT_EQ(ids, prefs->GetAppIds()); |
| ValidateShortcutReadyState(fake_shortcuts(), true); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RestartPreserveApps) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| // Start from scratch and fill with apps. |
| SendRefreshAppList(fake_apps()); |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(fake_apps().size(), ids.size()); |
| ValidateAppReadyState(fake_apps(), true); |
| |
| // This recreates model and ARC apps will be read from prefs. |
| arc_test()->StopArcInstance(); |
| CreateBuilder(); |
| ValidateAppReadyState(fake_apps(), false); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IsUnknownBasic) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| EXPECT_TRUE(prefs->IsUnknownPackage("com.package.notreallyapackage")); |
| } |
| |
| TEST_P(ArcDefaultAppTest, IsUnknownDefaultApps) { |
| // Note we run as a default test here so that we can use the fake default apps |
| // list. |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| for (const auto& app : fake_default_apps()) |
| EXPECT_FALSE(prefs->IsUnknownPackage(app.package_name)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IsUnknownSyncTest) { |
| app_instance()->SendRefreshPackageList( |
| ArcAppTest::ClonePackages(fake_packages())); |
| |
| const std::string sync_package_name = "com.google.fakesyncpack"; |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| // Check that this is indeed unknown before adding to sync. |
| ASSERT_TRUE(prefs->IsUnknownPackage(sync_package_name)); |
| |
| // Add to sync, then check unknown. |
| auto data_list = syncer::SyncDataList(); |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_arc_package()->set_package_name(sync_package_name); |
| data_list.push_back(syncer::SyncData::CreateRemoteData(specifics)); |
| auto* sync_service = arc::ArcPackageSyncableServiceFactory::GetInstance() |
| ->GetForBrowserContext(profile_.get()); |
| ASSERT_NE(nullptr, sync_service); |
| sync_service->MergeDataAndStartSyncing( |
| syncer::ARC_PACKAGE, data_list, |
| std::make_unique<syncer::FakeSyncChangeProcessor>(), |
| std::make_unique<syncer::SyncErrorFactoryMock>()); |
| |
| EXPECT_FALSE(prefs->IsUnknownPackage(sync_package_name)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IsUnknownInstalling) { |
| const std::string package_name = "com.fakepackage.name"; |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| EXPECT_TRUE(prefs->IsUnknownPackage(package_name)); |
| app_instance()->SendInstallationStarted(package_name); |
| EXPECT_FALSE(prefs->IsUnknownPackage(package_name)); |
| AddPackage(CreatePackage(package_name)); |
| app_instance()->SendInstallationFinished(package_name, true /* success */); |
| EXPECT_FALSE(prefs->IsUnknownPackage(package_name)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IsUnknownAfterUninstall) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| ASSERT_GE(fake_packages().size(), 1U); |
| app_instance()->SendRefreshPackageList( |
| ArcAppTest::ClonePackages(fake_packages())); |
| EXPECT_FALSE(prefs->IsUnknownPackage(fake_packages()[0]->package_name)); |
| app_instance()->UninstallPackage(fake_packages()[0]->package_name); |
| EXPECT_TRUE(prefs->IsUnknownPackage(fake_packages()[0]->package_name)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, MetricsIncremented) { |
| const std::string package_name = "com.fakepackage.name"; |
| const std::string install_histogram = "Arc.AppInstalledReason"; |
| const std::string uninstall_histogram = "Arc.AppUninstallReason"; |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| base::HistogramTester histogram_tester; |
| app_instance()->SendInstallationStarted(package_name); |
| histogram_tester.ExpectTotalCount(install_histogram, 0); |
| |
| app_instance()->SendInstallationFinished(package_name, true /* success */); |
| histogram_tester.ExpectTotalCount(install_histogram, 1); |
| |
| // Uninstalls checked similarly. |
| histogram_tester.ExpectTotalCount(uninstall_histogram, 0); |
| SendPackageUninstalled(package_name); |
| histogram_tester.ExpectTotalCount(uninstall_histogram, 1); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RestartPreserveShortcuts) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| // Start from scratch and install shortcuts. |
| SendInstallShortcuts(fake_shortcuts()); |
| std::vector<std::string> ids = prefs->GetAppIds(); |
| EXPECT_EQ(fake_apps().size(), ids.size()); |
| ValidateShortcutReadyState(fake_shortcuts(), true); |
| |
| // This recreates model and ARC apps and shortcuts will be read from prefs. |
| arc_test()->StopArcInstance(); |
| CreateBuilder(); |
| ValidateShortcutReadyState(fake_shortcuts(), false); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, LaunchApps) { |
| // Disable attempts to dismiss app launcher view. |
| ChromeAppListItem::OverrideAppListControllerDelegateForTesting(controller()); |
| |
| std::vector<arc::mojom::AppInfo> apps = fake_apps(); |
| ASSERT_GE(apps.size(), 3U); |
| |
| apps[2].suspended = true; |
| |
| SendRefreshAppList(apps); |
| |
| // Simulate item activate. |
| AppServiceAppItem* item1 = FindArcItem(ArcAppTest::GetAppId(apps[0])); |
| AppServiceAppItem* item2 = FindArcItem(ArcAppTest::GetAppId(apps[1])); |
| AppServiceAppItem* item3 = FindArcItem(ArcAppTest::GetAppId(apps[2])); |
| ASSERT_TRUE(item1); |
| ASSERT_TRUE(item2); |
| ASSERT_TRUE(item3); |
| item1->PerformActivate(0); |
| item2->PerformActivate(0); |
| item1->PerformActivate(0); |
| |
| const std::vector<std::unique_ptr<arc::FakeAppInstance::Request>>& |
| launch_requests = app_instance()->launch_requests(); |
| FlushMojoCallsForAppService(); |
| ASSERT_EQ(3u, launch_requests.size()); |
| EXPECT_TRUE(launch_requests[0]->IsForApp(apps[0])); |
| EXPECT_TRUE(launch_requests[1]->IsForApp(apps[1])); |
| EXPECT_TRUE(launch_requests[2]->IsForApp(apps[0])); |
| |
| // Test an attempt to launch suspended app. It should be blocked. |
| item3->PerformActivate(0); |
| EXPECT_EQ(3u, app_instance()->launch_requests().size()); |
| |
| // Test an attempt to launch of a not-ready app. Number of launch requests |
| // should be the same, indicating that launch request was blocked. |
| arc_test()->StopArcInstance(); |
| item1 = FindArcItem(ArcAppTest::GetAppId(apps[0])); |
| ASSERT_TRUE(item1); |
| item1->PerformActivate(0); |
| EXPECT_EQ(3u, app_instance()->launch_requests().size()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, LaunchShortcuts) { |
| // Disable attempts to dismiss app launcher view. |
| ChromeAppListItem::OverrideAppListControllerDelegateForTesting(controller()); |
| |
| SendInstallShortcuts(fake_shortcuts()); |
| |
| // Simulate item activate. |
| ASSERT_GE(fake_shortcuts().size(), 2U); |
| const arc::mojom::ShortcutInfo& app_first = fake_shortcuts()[0]; |
| const arc::mojom::ShortcutInfo& app_last = fake_shortcuts()[1]; |
| AppServiceAppItem* item_first = FindArcItem(ArcAppTest::GetAppId(app_first)); |
| AppServiceAppItem* item_last = FindArcItem(ArcAppTest::GetAppId(app_last)); |
| ASSERT_NE(nullptr, item_first); |
| ASSERT_NE(nullptr, item_last); |
| item_first->PerformActivate(0); |
| item_last->PerformActivate(0); |
| item_first->PerformActivate(0); |
| |
| const std::vector<std::string>& launch_intents = |
| app_instance()->launch_intents(); |
| FlushMojoCallsForAppService(); |
| ASSERT_EQ(3u, launch_intents.size()); |
| EXPECT_EQ(app_first.intent_uri, launch_intents[0]); |
| EXPECT_EQ(app_last.intent_uri, launch_intents[1]); |
| EXPECT_EQ(app_first.intent_uri, launch_intents[2]); |
| |
| // Test an attempt to launch of a not-ready shortcut. |
| arc_test()->StopArcInstance(); |
| item_first = FindArcItem(ArcAppTest::GetAppId(app_first)); |
| ASSERT_NE(nullptr, item_first); |
| size_t launch_request_count_before = app_instance()->launch_intents().size(); |
| item_first->PerformActivate(0); |
| // Number of launch requests must not change. |
| EXPECT_EQ(launch_request_count_before, |
| app_instance()->launch_intents().size()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RequestIcons) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| SendRefreshAppList(fake_apps()); |
| |
| // Validate that no icon exists at the beginning and request icon for |
| // each supported scale factor. This will start asynchronous loading. |
| std::set<int> expected_dimensions; |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| for (auto& scale_factor : scale_factors) { |
| expected_dimensions.insert( |
| GetAppListIconDimensionForScaleFactor(scale_factor)); |
| for (auto& app : fake_apps()) { |
| AppServiceAppItem* app_item = FindArcItem(ArcAppTest::GetAppId(app)); |
| ASSERT_NE(nullptr, app_item); |
| const float scale = ui::GetScaleForScaleFactor(scale_factor); |
| app_item->icon().GetRepresentation(scale); |
| |
| // This does not result in an icon being loaded, so WaitForIconUpdates |
| // cannot be used. |
| content::RunAllTasksUntilIdle(); |
| } |
| } |
| |
| const size_t expected_size = scale_factors.size() * fake_apps().size(); |
| |
| // At this moment we should receive all requests for icon loading. |
| const std::vector<std::unique_ptr<arc::FakeAppInstance::IconRequest>>& |
| icon_requests = app_instance()->icon_requests(); |
| EXPECT_EQ(expected_size, icon_requests.size()); |
| std::map<std::string, std::set<int>> app_dimensions; |
| for (size_t i = 0; i < icon_requests.size(); ++i) { |
| const arc::FakeAppInstance::IconRequest* icon_request = |
| icon_requests[i].get(); |
| const std::string id = ArcAppListPrefs::GetAppId( |
| icon_request->package_name(), icon_request->activity()); |
| // Make sure no double requests. Dimension is stepped by 16. |
| EXPECT_EQ(0U, app_dimensions[id].count(icon_request->dimension())); |
| app_dimensions[id].insert(icon_request->dimension()); |
| } |
| |
| // Validate that we have a request for each icon for each supported scale |
| // factor. |
| EXPECT_EQ(fake_apps().size(), app_dimensions.size()); |
| for (auto& app : fake_apps()) { |
| const std::string id = ArcAppTest::GetAppId(app); |
| ASSERT_NE(app_dimensions.find(id), app_dimensions.end()); |
| EXPECT_EQ(app_dimensions[id], expected_dimensions); |
| } |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RequestShortcutIcons) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| const arc::mojom::ShortcutInfo& shortcut = fake_shortcuts()[0]; |
| SendInstallShortcut(shortcut); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| // Icons representations loading is done asynchronously and is started once |
| // the AppServiceAppItem is created. Wait for icons for all supported scales |
| // to be loaded. |
| std::set<int> expected_dimensions; |
| AppServiceAppItem* app_item = FindArcItem(ArcAppTest::GetAppId(shortcut)); |
| ASSERT_NE(nullptr, app_item); |
| WaitForIconUpdates(profile_.get(), app_item->id()); |
| |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| for (auto& scale_factor : scale_factors) { |
| expected_dimensions.insert( |
| GetAppListIconDimensionForScaleFactor(scale_factor)); |
| EXPECT_TRUE( |
| IsIconCreated(prefs, ArcAppTest::GetAppId(shortcut), scale_factor)); |
| } |
| |
| // At this moment we should receive all requests for icon loading. |
| const size_t expected_size = scale_factors.size(); |
| const std::vector<std::unique_ptr<arc::FakeAppInstance::ShortcutIconRequest>>& |
| icon_requests = app_instance()->shortcut_icon_requests(); |
| EXPECT_EQ(expected_size, icon_requests.size()); |
| std::set<int> shortcut_dimensions; |
| for (size_t i = 0; i < icon_requests.size(); ++i) { |
| const arc::FakeAppInstance::ShortcutIconRequest* icon_request = |
| icon_requests[i].get(); |
| EXPECT_EQ(shortcut.icon_resource_id, icon_request->icon_resource_id()); |
| |
| // Make sure no double requests. |
| EXPECT_EQ(0U, shortcut_dimensions.count(icon_request->dimension())); |
| shortcut_dimensions.insert(icon_request->dimension()); |
| } |
| |
| // Validate that we have a request for each icon for each supported scale |
| // factor. |
| EXPECT_EQ(shortcut_dimensions, expected_dimensions); |
| |
| // Validate all icon files are installed. |
| for (auto& scale_factor : scale_factors) { |
| const base::FilePath icon_path = prefs->GetIconPath( |
| ArcAppTest::GetAppId(shortcut), GetAppListIconDescriptor(scale_factor)); |
| EXPECT_TRUE(base::PathExists(icon_path)); |
| } |
| } |
| |
| // Verifies that if required flag is provided then ARC app icons are |
| // automatically cached for the set of dpi and supported scale factors. |
| TEST_P(ArcAppModelBuilderTest, ForceCacheIcons) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| base::test::ScopedCommandLine command_line; |
| command_line.GetProcessCommandLine()->AppendSwitch( |
| chromeos::switches::kArcForceCacheAppIcons); |
| |
| const std::string app_id = ArcAppTest::GetAppId(fake_apps()[0]); |
| |
| SendRefreshAppList({fake_apps()[0]}); |
| |
| // Number of requests per size in pixels. |
| std::map<int, int> requests_expectation; |
| const std::vector<int> expected_dip_sizes({16, 32, 48, 64}); |
| for (auto scale_factor : ui::GetSupportedScaleFactors()) { |
| for (int dip_size : expected_dip_sizes) { |
| const int size_in_pixels = |
| ArcAppIconDescriptor(dip_size, scale_factor).GetSizeInPixels(); |
| requests_expectation[size_in_pixels]++; |
| } |
| } |
| |
| const std::vector<std::unique_ptr<arc::FakeAppInstance::IconRequest>>& |
| icon_requests = app_instance()->icon_requests(); |
| |
| EXPECT_EQ(ui::GetSupportedScaleFactors().size() * expected_dip_sizes.size(), |
| icon_requests.size()); |
| |
| for (size_t i = 0; i < icon_requests.size(); ++i) { |
| const arc::FakeAppInstance::IconRequest* icon_request = |
| icon_requests[i].get(); |
| const std::string request_app_id = ArcAppListPrefs::GetAppId( |
| icon_request->package_name(), icon_request->activity()); |
| EXPECT_EQ(app_id, request_app_id); |
| EXPECT_GE(--requests_expectation[icon_request->dimension()], 0); |
| } |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, InstallIcon) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| app_instance()->SendRefreshAppList(std::vector<arc::mojom::AppInfo>( |
| fake_apps().begin(), fake_apps().begin() + 1)); |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| const ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactors()[0]; |
| const float scale = ui::GetScaleForScaleFactor(scale_factor); |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| const base::FilePath icon_path = |
| prefs->GetIconPath(app_id, GetAppListIconDescriptor(scale_factor)); |
| EXPECT_FALSE(IsIconCreated(prefs, app_id, scale_factor)); |
| |
| FlushMojoCallsForAppService(); |
| const AppServiceAppItem* app_item = FindArcItem(app_id); |
| EXPECT_NE(nullptr, app_item); |
| // This initiates async loading. |
| app_item->icon().GetRepresentation(scale); |
| |
| WaitForIconUpdates(profile_.get(), app_id); |
| |
| // Validate that icons are installed, have right content and icon is |
| // refreshed for ARC app item. |
| EXPECT_TRUE(IsIconCreated(prefs, app_id, scale_factor)); |
| |
| std::string png_data; |
| EXPECT_TRUE(app_instance()->GetIconResponse( |
| GetAppListIconDimensionForScaleFactor(scale_factor), &png_data)); |
| |
| std::string icon_data; |
| // Read the file from disk and compare with reference data. |
| EXPECT_TRUE(base::ReadFileToString(icon_path, &icon_data)); |
| ASSERT_EQ(icon_data, png_data); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, RemoveAppCleanUpFolder) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| const ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactors()[0]; |
| |
| // No app folder by default. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(IsIconCreated(prefs, app_id, scale_factor)); |
| |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>(fake_apps().begin(), |
| fake_apps().begin() + 1)); |
| const base::FilePath app_path = prefs->GetAppPath(app_id); |
| |
| // Now send generated icon for the app. |
| WaitForIconUpdates(profile_.get(), app_id); |
| EXPECT_TRUE(IsIconCreated(prefs, app_id, scale_factor)); |
| |
| // Send empty app list. This will delete app and its folder. |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>()); |
| // Process pending tasks. This performs multiple thread hops, so we need |
| // to run it continuously until it is resolved. |
| do { |
| content::RunAllTasksUntilIdle(); |
| } while (IsIconCreated(prefs, app_id, scale_factor)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, LastLaunchTime) { |
| // Make sure we are on UI thread. |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| ASSERT_GE(fake_apps().size(), 3U); |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>(fake_apps().begin(), |
| fake_apps().begin() + 3)); |
| const arc::mojom::AppInfo& app1 = fake_apps()[0]; |
| const arc::mojom::AppInfo& app2 = fake_apps()[1]; |
| const arc::mojom::AppInfo& app3 = fake_apps()[2]; |
| const std::string id1 = ArcAppTest::GetAppId(app1); |
| const std::string id2 = ArcAppTest::GetAppId(app2); |
| const std::string id3 = ArcAppTest::GetAppId(app3); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id1); |
| ASSERT_NE(nullptr, app_info.get()); |
| |
| EXPECT_EQ(base::Time(), app_info->last_launch_time); |
| |
| // Test direct setting last launch time. |
| const base::Time before_time = base::Time::Now(); |
| prefs->SetLastLaunchTime(id1); |
| |
| app_info = prefs->GetApp(id1); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_GE(app_info->last_launch_time, before_time); |
| |
| // Test setting last launch time via LaunchApp. |
| app_info = prefs->GetApp(id2); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(base::Time(), app_info->last_launch_time); |
| |
| base::Time time_before = base::Time::Now(); |
| arc::LaunchApp(profile(), id2, ui::EF_NONE, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| const base::Time time_after = base::Time::Now(); |
| |
| app_info = prefs->GetApp(id2); |
| ASSERT_NE(nullptr, app_info.get()); |
| ASSERT_LE(time_before, app_info->last_launch_time); |
| ASSERT_GE(time_after, app_info->last_launch_time); |
| |
| // Test last launch time when app is started externally, not from App |
| // Launcher. |
| app_info = prefs->GetApp(id3); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_EQ(base::Time(), app_info->last_launch_time); |
| time_before = base::Time::Now(); |
| app_instance()->SendTaskCreated(0, fake_apps()[2], std::string()); |
| app_info = prefs->GetApp(id3); |
| ASSERT_NE(nullptr, app_info.get()); |
| EXPECT_GE(app_info->last_launch_time, time_before); |
| } |
| |
| // Makes sure that install time is set. |
| TEST_P(ArcAppModelBuilderTest, InstallTime) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| ASSERT_TRUE(fake_apps().size()); |
| |
| const std::string app_id = ArcAppTest::GetAppId(fake_apps()[0]); |
| EXPECT_FALSE(prefs->GetApp(app_id)); |
| |
| SendRefreshAppList(fake_apps()); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| ASSERT_TRUE(app_info); |
| EXPECT_NE(base::Time(), app_info->install_time); |
| EXPECT_LE(app_info->install_time, base::Time::Now()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, AppLifeCycleEventsOnOptOut) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| arc::ConnectionObserver<arc::mojom::AppInstance>* const connection_observer = |
| prefs; |
| |
| arc::MockArcAppListPrefsObserver observer; |
| |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs::AppInfo::SetIgnoreCompareInstallTimeForTesting(true); |
| const ArcAppListPrefs::AppInfo expected_app_info_registered = |
| GetAppInfoExpectation(app, true /* launchable */); |
| |
| ArcAppListPrefs::AppInfo expected_app_info_disabled( |
| expected_app_info_registered); |
| expected_app_info_disabled.ready = false; |
| |
| prefs->AddObserver(&observer); |
| |
| EXPECT_CALL(observer, OnAppRegistered(app_id, expected_app_info_registered)) |
| .Times(1); |
| EXPECT_CALL(observer, OnAppStatesChanged(app_id, expected_app_info_disabled)) |
| .Times(1); |
| EXPECT_CALL(observer, OnAppRemoved(app_id)) |
| .Times(arc::ShouldArcAlwaysStart() ? 0 : 1); |
| EXPECT_CALL(observer, OnAppIconUpdated(testing::_, testing::_)).Times(0); |
| EXPECT_CALL(observer, OnAppNameUpdated(testing::_, testing::_)).Times(0); |
| EXPECT_CALL(observer, OnAppLastLaunchTimeUpdated(testing::_)).Times(0); |
| |
| app_instance()->SendRefreshAppList({app}); |
| |
| // On Opt-out ARC app instance is disconnected first and only then |
| // notification that ARC is disabled called. |
| connection_observer->OnConnectionClosed(); |
| SetArcPlayStoreEnabledForProfile(profile(), false); |
| |
| prefs->RemoveObserver(&observer); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, AppLifeCycleEventsOnPackageListRefresh) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| arc::MockArcAppListPrefsObserver observer; |
| prefs->AddObserver(&observer); |
| |
| // Refreshing the package list with hithero unseen packages should call |
| // OnPackageInstalled. |
| EXPECT_CALL(observer, OnPackageInstalled(testing::Field( |
| &arc::mojom::ArcPackageInfo::package_name, |
| fake_packages()[0]->package_name))) |
| .Times(1); |
| EXPECT_CALL(observer, OnPackageInstalled(testing::Field( |
| &arc::mojom::ArcPackageInfo::package_name, |
| fake_packages()[1]->package_name))) |
| .Times(1); |
| EXPECT_CALL(observer, OnPackageInstalled(testing::Field( |
| &arc::mojom::ArcPackageInfo::package_name, |
| fake_packages()[2]->package_name))) |
| .Times(1); |
| app_instance()->SendRefreshPackageList( |
| ArcAppTest::ClonePackages(fake_packages())); |
| |
| // Refreshing the package list and omitting previously seen packages should |
| // call OnPackageRemoved for the omitted packages only. |
| EXPECT_CALL(observer, |
| OnPackageRemoved(fake_packages()[0]->package_name, false)) |
| .Times(0); |
| EXPECT_CALL(observer, |
| OnPackageRemoved(fake_packages()[1]->package_name, false)) |
| .Times(1); |
| EXPECT_CALL(observer, |
| OnPackageRemoved(fake_packages()[2]->package_name, false)) |
| .Times(1); |
| |
| std::vector<arc::mojom::ArcPackageInfoPtr> packages; |
| packages.push_back(fake_packages()[0].Clone()); |
| app_instance()->SendRefreshPackageList(std::move(packages)); |
| prefs->RemoveObserver(&observer); |
| } |
| |
| // Validate that arc model contains expected elements on restart. |
| // Flaky. https://crbug.com/1013813 |
| TEST_P(ArcAppModelBuilderRecreate, DISABLED_AppModelRestart) { |
| // No apps on initial start. |
| ValidateHaveApps(std::vector<arc::mojom::AppInfo>()); |
| |
| // Send info about all fake apps except last. |
| std::vector<arc::mojom::AppInfo> apps1(fake_apps().begin(), |
| fake_apps().end() - 1); |
| SendRefreshAppList(apps1); |
| // Model has refreshed apps. |
| ValidateHaveApps(apps1); |
| EXPECT_EQ(apps1.size(), GetArcItemCount()); |
| |
| // Simulate restart. |
| RestartArc(); |
| |
| // On restart new model contains last apps. |
| ValidateHaveApps(apps1); |
| EXPECT_EQ(apps1.size(), GetArcItemCount()); |
| |
| // Now refresh old apps with new one. |
| SendRefreshAppList(fake_apps()); |
| ValidateHaveApps(fake_apps()); |
| EXPECT_EQ(fake_apps().size(), GetArcItemCount()); |
| } |
| |
| // Verifies that no OnAppRegistered/OnAppRemoved is called in case ARC++ started |
| // next time disabled. |
| // Flaky. https://crbug.com/1013813 |
| TEST_P(ArcAppModelBuilderRecreate, |
| DISABLED_AppsNotReportedNextSessionDisabled) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| // Register one app first. |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| SendRefreshAppList({app}); |
| |
| // We will wait for default apps manually once observer is set. |
| arc_test()->set_wait_default_apps(false); |
| arc_test()->set_activate_arc_on_start(false); |
| StopArc(); |
| // Disable ARC in beetwen sessions. |
| SetArcPlayStoreEnabledForProfile(profile(), false); |
| StartArc(); |
| |
| prefs = ArcAppListPrefs::Get(profile_.get()); |
| |
| arc::MockArcAppListPrefsObserver observer; |
| prefs->AddObserver(&observer); |
| EXPECT_CALL(observer, |
| OnAppRegistered( |
| app_id, GetAppInfoExpectation(app, true /* launchable */))) |
| .Times(0); |
| EXPECT_CALL(observer, OnAppRemoved(app_id)).Times(0); |
| |
| arc_test()->WaitForDefaultApps(); |
| } |
| |
| TEST_P(ArcPlayStoreAppTest, PlayStore) { |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(arc::kPlayStoreAppId); |
| if (GetArcState() != ArcState::ARC_WITHOUT_PLAY_STORE) { |
| // Make sure PlayStore is available. |
| ASSERT_TRUE(app_info); |
| EXPECT_FALSE(app_info->ready); |
| } else { |
| // By default Play Store is not available in case no Play Store mode. But |
| // explicitly adding it makes it appear as an ordinal app. |
| EXPECT_FALSE(app_info); |
| } |
| |
| SendPlayStoreApp(); |
| |
| app_info = prefs->GetApp(arc::kPlayStoreAppId); |
| ASSERT_TRUE(app_info); |
| EXPECT_TRUE(app_info->ready); |
| |
| // TODO(victorhsieh): Opt-out on Persistent ARC is special. Skip until |
| // implemented. |
| if (arc::ShouldArcAlwaysStart()) |
| return; |
| SetArcPlayStoreEnabledForProfile(profile(), false); |
| |
| app_info = prefs->GetApp(arc::kPlayStoreAppId); |
| ASSERT_TRUE(app_info); |
| EXPECT_FALSE(app_info->ready); |
| |
| arc::LaunchApp(profile(), arc::kPlayStoreAppId, ui::EF_NONE, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| EXPECT_TRUE(arc::IsArcPlayStoreEnabledForProfile(profile())); |
| } |
| |
| TEST_P(ArcPlayStoreAppTest, PaiStarter) { |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| bool pai_started = false; |
| |
| arc::ArcPaiStarter starter1(profile_.get()); |
| arc::ArcPaiStarter starter2(profile_.get()); |
| EXPECT_FALSE(starter1.started()); |
| EXPECT_FALSE(starter2.started()); |
| EXPECT_EQ(app_instance()->start_pai_request_count(), 0); |
| |
| starter1.AddOnStartCallback( |
| base::BindOnce(&OnPaiStartedCallback, &pai_started)); |
| EXPECT_FALSE(pai_started); |
| |
| arc::ArcSessionManager* session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(session_manager); |
| |
| // PAI starter is not expected for ARC without the Play Store. |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) { |
| EXPECT_FALSE(session_manager->pai_starter()); |
| return; |
| } |
| |
| ASSERT_TRUE(session_manager->pai_starter()); |
| EXPECT_FALSE(session_manager->pai_starter()->started()); |
| |
| starter2.AcquireLock(); |
| |
| SendPlayStoreApp(); |
| |
| EXPECT_TRUE(starter1.started()); |
| EXPECT_TRUE(pai_started); |
| |
| // Test that callback is called immediately in case PAI was already started. |
| pai_started = false; |
| starter1.AddOnStartCallback( |
| base::BindOnce(&OnPaiStartedCallback, &pai_started)); |
| EXPECT_TRUE(pai_started); |
| |
| EXPECT_FALSE(starter2.started()); |
| EXPECT_TRUE(session_manager->pai_starter()->started()); |
| EXPECT_EQ(app_instance()->start_pai_request_count(), 2); |
| |
| starter2.ReleaseLock(); |
| EXPECT_TRUE(starter2.started()); |
| EXPECT_EQ(app_instance()->start_pai_request_count(), 3); |
| |
| arc::ArcPaiStarter starter3(profile_.get()); |
| EXPECT_TRUE(starter3.started()); |
| EXPECT_EQ(app_instance()->start_pai_request_count(), 4); |
| } |
| |
| // Validates that PAI is started on the next session start if it was not started |
| // during the previous sessions for some reason. |
| TEST_P(ArcPlayStoreAppTest, StartPaiOnNextRun) { |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) |
| return; |
| |
| arc::ArcSessionManager* session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(session_manager); |
| |
| arc::ArcPaiStarter* pai_starter = session_manager->pai_starter(); |
| ASSERT_TRUE(pai_starter); |
| EXPECT_FALSE(pai_starter->started()); |
| |
| // Finish session with lock. This would prevent running PAI. |
| pai_starter->AcquireLock(); |
| SendPlayStoreApp(); |
| EXPECT_FALSE(pai_starter->started()); |
| session_manager->Shutdown(); |
| |
| // Simulate ARC restart. |
| RestartArc(); |
| |
| // PAI was not started during the previous session due the lock status and |
| // should be available now. |
| session_manager = arc::ArcSessionManager::Get(); |
| pai_starter = session_manager->pai_starter(); |
| ASSERT_TRUE(pai_starter); |
| EXPECT_FALSE(pai_starter->locked()); |
| |
| SendPlayStoreApp(); |
| EXPECT_TRUE(pai_starter->started()); |
| |
| // Simulate the next ARC restart. |
| RestartArc(); |
| |
| // PAI was started during the previous session and should not be available |
| // now. |
| session_manager = arc::ArcSessionManager::Get(); |
| pai_starter = session_manager->pai_starter(); |
| EXPECT_FALSE(pai_starter); |
| } |
| |
| // Validates that PAI is not started in case it is explicitly disabled. |
| TEST_P(ArcPlayStoreAppTest, StartPaiDisabled) { |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) |
| return; |
| |
| base::test::ScopedCommandLine command_line; |
| command_line.GetProcessCommandLine()->AppendSwitch( |
| chromeos::switches::kArcDisablePlayAutoInstall); |
| |
| arc::ArcSessionManager* session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(session_manager); |
| |
| arc::ArcPaiStarter* pai_starter = session_manager->pai_starter(); |
| ASSERT_TRUE(pai_starter); |
| EXPECT_FALSE(pai_starter->started()); |
| SendPlayStoreApp(); |
| EXPECT_FALSE(pai_starter->started()); |
| } |
| |
| TEST_P(ArcPlayStoreAppTest, PaiStarterOnError) { |
| // No PAI starter without Play Store. |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) |
| return; |
| |
| arc::ArcSessionManager* const session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_NE(nullptr, session_manager); |
| arc::ArcPaiStarter* const pai_starter = session_manager->pai_starter(); |
| ASSERT_NE(nullptr, pai_starter); |
| EXPECT_FALSE(pai_starter->started()); |
| |
| app_instance()->set_pai_state_response(arc::mojom::PaiFlowState::NO_APPS); |
| |
| SendPlayStoreApp(); |
| |
| EXPECT_FALSE(pai_starter->started()); |
| int pai_request_expected = 1; |
| EXPECT_EQ(pai_request_expected, app_instance()->start_pai_request_count()); |
| |
| // Verify retry and all possible errors. |
| for (int error_state = static_cast<int>(arc::mojom::PaiFlowState::UNKNOWN); |
| error_state <= static_cast<int>(arc::mojom::PaiFlowState::kMaxValue); |
| ++error_state) { |
| app_instance()->set_pai_state_response( |
| static_cast<arc::mojom::PaiFlowState>(error_state)); |
| pai_starter->TriggerRetryForTesting(); |
| EXPECT_FALSE(pai_starter->started()); |
| EXPECT_EQ(++pai_request_expected, |
| app_instance()->start_pai_request_count()); |
| } |
| |
| // Now return success and verify it is started. |
| app_instance()->set_pai_state_response(arc::mojom::PaiFlowState::SUCCEEDED); |
| pai_starter->TriggerRetryForTesting(); |
| EXPECT_TRUE(pai_starter->started()); |
| EXPECT_EQ(++pai_request_expected, app_instance()->start_pai_request_count()); |
| } |
| |
| TEST_P(ArcPlayStoreAppTest, |
| FastAppReinstallStarterUserFinishesSelectionBeforePlayStore) { |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| arc::ArcFastAppReinstallStarter starter1(profile_.get(), |
| profile_->GetPrefs()); |
| EXPECT_FALSE(starter1.started()); |
| EXPECT_EQ(0, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| arc::ArcSessionManager* session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(session_manager); |
| |
| // Fast App Reinstall starter is not expected for ARC without the Play Store. |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) { |
| EXPECT_FALSE(session_manager->fast_app_resintall_starter()); |
| return; |
| } |
| |
| ASSERT_TRUE(session_manager->fast_app_resintall_starter()); |
| EXPECT_FALSE(session_manager->fast_app_resintall_starter()->started()); |
| |
| // Fast App Reinstall is not expected to start when the user finishes |
| // selection without the Play Store. |
| base::ListValue package_list; |
| package_list.Set(0, std::make_unique<base::Value>("fake_package_name")); |
| const base::ListValue* selected_packages(&package_list); |
| profile_.get()->GetTestingPrefService()->Set( |
| arc::prefs::kArcFastAppReinstallPackages, *selected_packages); |
| starter1.OnAppsSelectionFinished(); |
| EXPECT_FALSE(starter1.started()); |
| EXPECT_EQ(0, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| SendPlayStoreApp(); |
| |
| EXPECT_TRUE(starter1.started()); |
| EXPECT_EQ(2, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| arc::ArcFastAppReinstallStarter starter2(profile_.get(), |
| profile_->GetPrefs()); |
| EXPECT_TRUE(starter2.started()); |
| EXPECT_EQ(3, app_instance()->start_fast_app_reinstall_request_count()); |
| } |
| |
| TEST_P(ArcPlayStoreAppTest, |
| FastAppReinstallStarterUserFinishesSelectionAfterPlayStore) { |
| ASSERT_TRUE(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| arc::ArcFastAppReinstallStarter starter1(profile_.get(), |
| profile_->GetPrefs()); |
| EXPECT_FALSE(starter1.started()); |
| EXPECT_EQ(0, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| arc::ArcSessionManager* session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(session_manager); |
| |
| // Fast App Reinstall starter is not expected for ARC without the Play Store. |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) { |
| EXPECT_FALSE(session_manager->fast_app_resintall_starter()); |
| return; |
| } |
| |
| ASSERT_TRUE(session_manager->fast_app_resintall_starter()); |
| EXPECT_FALSE(session_manager->fast_app_resintall_starter()->started()); |
| |
| SendPlayStoreApp(); |
| |
| // Fast App Reinstall is not expected to start when the user has not finished |
| // selection. |
| EXPECT_FALSE(starter1.started()); |
| EXPECT_EQ(0, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| base::ListValue package_list; |
| package_list.Set(0, std::make_unique<base::Value>("fake_package_name")); |
| const base::ListValue* selected_packages(&package_list); |
| profile_.get()->GetTestingPrefService()->Set( |
| arc::prefs::kArcFastAppReinstallPackages, *selected_packages); |
| starter1.OnAppsSelectionFinished(); |
| // Fast App Reinstall is expected to start right after user finishes selection |
| // after Play Store is ready. |
| EXPECT_TRUE(starter1.started()); |
| EXPECT_EQ(1, app_instance()->start_fast_app_reinstall_request_count()); |
| |
| arc::ArcFastAppReinstallStarter starter2(profile_.get(), |
| profile_->GetPrefs()); |
| EXPECT_TRUE(starter2.started()); |
| EXPECT_EQ(2, app_instance()->start_fast_app_reinstall_request_count()); |
| } |
| |
| // Test that icon is correctly extracted for shelf group. |
| TEST_P(ArcAppModelBuilderTest, IconLoaderForShelfGroup) { |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>(fake_apps().begin(), |
| fake_apps().begin() + 1)); |
| content::RunAllTasksUntilIdle(); |
| |
| // Store number of requests generated during the App List item creation. Same |
| // request will not be re-sent without clearing the request record in |
| // ArcAppListPrefs. |
| const size_t initial_icon_request_count = |
| app_instance()->icon_requests().size(); |
| |
| std::vector<arc::mojom::ShortcutInfo> shortcuts = |
| arc_test()->fake_shortcuts(); |
| shortcuts.resize(1); |
| shortcuts[0].intent_uri += |
| ";S.org.chromium.arc.shelf_group_id=arc_test_shelf_group;end"; |
| SendInstallShortcuts(shortcuts); |
| const std::string shortcut_id = ArcAppTest::GetAppId(shortcuts[0]); |
| content::RunAllTasksUntilIdle(); |
| |
| const std::string id_shortcut_exist = |
| arc::ArcAppShelfId("arc_test_shelf_group", app_id).ToString(); |
| const std::string id_shortcut_absent = |
| arc::ArcAppShelfId("arc_test_shelf_group_absent", app_id).ToString(); |
| |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &delegate); |
| EXPECT_EQ(0UL, delegate.update_image_count()); |
| |
| // Fetch original app icon. |
| icon_loader.FetchImage(app_id); |
| delegate.WaitForIconUpdates(1); |
| EXPECT_EQ(app_id, delegate.app_id()); |
| const gfx::ImageSkia app_icon = delegate.image(); |
| |
| // Shortcut exists, icon is requested from shortcut. |
| icon_loader.FetchImage(id_shortcut_exist); |
| // Icon was sent on request and loader should be updated. |
| delegate.WaitForIconUpdates(1); |
| EXPECT_EQ(id_shortcut_exist, delegate.app_id()); |
| |
| // Validate that fetched shortcut icon for existing shortcut does not match |
| // referenced app icon. |
| content::RunAllTasksUntilIdle(); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.0f).GetBitmap(), |
| delegate.image().GetRepresentation(1.0f).GetBitmap())); |
| |
| content::RunAllTasksUntilIdle(); |
| const size_t shortcut_request_count = |
| app_instance()->shortcut_icon_requests().size(); |
| EXPECT_NE(0U, shortcut_request_count); |
| EXPECT_EQ(initial_icon_request_count, app_instance()->icon_requests().size()); |
| for (const auto& request : app_instance()->shortcut_icon_requests()) |
| EXPECT_EQ(shortcuts[0].icon_resource_id, request->icon_resource_id()); |
| |
| // Fallback when shortcut is not found for shelf group id, use app id instead. |
| // Remove the IconRequestRecord for |app_id| to observe the icon request for |
| // |app_id| is re-sent. |
| const size_t update_image_count_before = delegate.update_image_count(); |
| MaybeRemoveIconRequestRecord(app_id); |
| icon_loader.FetchImage(id_shortcut_absent); |
| // Expected no update. |
| EXPECT_EQ(update_image_count_before, delegate.update_image_count()); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(shortcut_request_count, |
| app_instance()->shortcut_icon_requests().size()); |
| |
| // Validate that fetched shortcut icon for absent shortcut contains referenced |
| // app icon. |
| EXPECT_TRUE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.0f).GetBitmap(), |
| delegate.image().GetRepresentation(1.0f).GetBitmap())); |
| } |
| |
| // Test that icon is correctly updated for suspended/non-suspended app. |
| TEST_P(ArcAppModelBuilderTest, IconLoaderForSuspendedApps) { |
| arc::mojom::AppInfo app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &delegate); |
| |
| SendRefreshAppList({app}); |
| content::RunAllTasksUntilIdle(); |
| |
| icon_loader.FetchImage(app_id); |
| delegate.WaitForIconUpdates(1); |
| |
| const gfx::ImageSkia app_normal_icon = delegate.image(); |
| |
| const size_t update_count = delegate.update_image_count(); |
| // Now switch to suspended mode. Image is updated inline because primary icon |
| // is loaded and we only apply gray effect. |
| app.suspended = true; |
| SendPackageAppListRefreshed(app.package_name, {app}); |
| EXPECT_EQ(update_count + 1, delegate.update_image_count()); |
| // No futher updates. |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(update_count + 1, delegate.update_image_count()); |
| |
| // We should have different icons. |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_normal_icon.GetRepresentation(1.0f).GetBitmap(), |
| delegate.image().GetRepresentation(1.0f).GetBitmap())); |
| |
| // Now switch back to normal mode. |
| app.suspended = false; |
| SendPackageAppListRefreshed(app.package_name, {app}); |
| EXPECT_EQ(update_count + 2, delegate.update_image_count()); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(update_count + 2, delegate.update_image_count()); |
| |
| // Icon should be restored to normal |
| EXPECT_TRUE(gfx::test::AreBitmapsEqual( |
| app_normal_icon.GetRepresentation(1.0f).GetBitmap(), |
| delegate.image().GetRepresentation(1.0f).GetBitmap())); |
| } |
| |
| // If the cached icon file is corrupted, we expect send request to ARC for a new |
| // icon. |
| TEST_P(ArcAppModelBuilderTest, IconLoaderWithBadIcon) { |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| app_instance()->set_icon_response_type( |
| arc::FakeAppInstance::IconResponseType::ICON_RESPONSE_SEND_BAD); |
| |
| // When calling SendRefreshAppList to add the fake apps, AppServiceAppItem |
| // could load the icon by calling ArcAppIcon, so override the icon loader |
| // temporarily to avoid calling ArcAppIcon. Otherwise, it might affect |
| // the test result when calling AppServiceAppIconLoader to load the icon. |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(profile_.get()); |
| DCHECK(proxy); |
| apps::StubIconLoader stub_icon_loader; |
| apps::IconLoader* old_icon_loader = |
| proxy->OverrideInnerIconLoaderForTesting(&stub_icon_loader); |
| |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>(fake_apps().begin(), |
| fake_apps().begin() + 1)); |
| content::RunAllTasksUntilIdle(); |
| proxy->OverrideInnerIconLoaderForTesting(old_icon_loader); |
| |
| // Store number of requests generated during the App List item creation. Same |
| // request will not be re-sent without clearing the request record in |
| // ArcAppListPrefs. |
| const size_t initial_icon_request_count = |
| app_instance()->icon_requests().size(); |
| |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &delegate); |
| icon_loader.FetchImage(app_id); |
| |
| // So far Icon update is not expected because of bad icon. |
| EXPECT_EQ(delegate.update_image_count(), 0U); |
| |
| MaybeRemoveIconRequestRecord(app_id); |
| |
| // Install Bad image. |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| AppServiceAppItem* app_item = FindArcItem(app_id); |
| for (auto& scale_factor : scale_factors) { |
| // Force the icon to be loaded. |
| app_item->icon().GetRepresentation( |
| ui::GetScaleForScaleFactor(scale_factor)); |
| WaitForIconCreation(prefs, app_id, scale_factor); |
| } |
| |
| // After clear request record related to |app_id|, when bad icon is installed, |
| // decoding failure will trigger re-sending new icon request to ARC. |
| EXPECT_TRUE(app_instance()->icon_requests().size() > |
| initial_icon_request_count); |
| for (size_t i = initial_icon_request_count; |
| i < app_instance()->icon_requests().size(); ++i) { |
| const auto& request = app_instance()->icon_requests()[i]; |
| EXPECT_TRUE(request->IsForApp(app)); |
| } |
| |
| // Icon update is not expected because of bad icon. |
| EXPECT_EQ(delegate.update_image_count(), 0U); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IconLoader) { |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| |
| app_instance()->SendRefreshAppList(std::vector<arc::mojom::AppInfo>( |
| fake_apps().begin(), fake_apps().begin() + 1)); |
| |
| // Wait AppServiceAppItem to finish loading icon, otherwise, the test result |
| // could be flaky, because the update image count could include the icon |
| // updates by AppServiceAppItem. |
| model_updater()->WaitForIconUpdates(1 + scale_factors.size()); |
| |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &delegate); |
| EXPECT_EQ(0UL, delegate.update_image_count()); |
| icon_loader.FetchImage(app_id); |
| EXPECT_EQ(0UL, delegate.update_image_count()); |
| |
| AppServiceAppItem* app_item = FindArcItem(app_id); |
| for (auto& scale_factor : scale_factors) { |
| // Force the icon to be loaded. |
| app_item->icon().GetRepresentation( |
| ui::GetScaleForScaleFactor(scale_factor)); |
| } |
| |
| delegate.WaitForIconUpdates(1); |
| |
| // Validate loaded image. |
| EXPECT_EQ(1UL, delegate.update_image_count()); |
| EXPECT_EQ(app_id, delegate.app_id()); |
| ValidateIcon(delegate.image()); |
| |
| // No more updates are expected. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1UL, delegate.update_image_count()); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, IconLoaderCompressed) { |
| const arc::mojom::AppInfo& app = fake_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| const int icon_size = ash::AppListConfig::instance().grid_icon_dimension(); |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| ui::GetSupportedScaleFactors(); |
| |
| SendRefreshAppList(std::vector<arc::mojom::AppInfo>(fake_apps().begin(), |
| fake_apps().begin() + 1)); |
| |
| base::RunLoop run_loop; |
| base::Closure quit = run_loop.QuitClosure(); |
| |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(profile_.get()); |
| ASSERT_NE(nullptr, proxy); |
| |
| proxy->LoadIcon( |
| apps::mojom::AppType::kArc, app_id, apps::mojom::IconType::kCompressed, |
| icon_size, false /*allow_placeholder_icon*/, |
| base::BindLambdaForTesting([&](apps::mojom::IconValuePtr icon_value) { |
| EXPECT_EQ(apps::mojom::IconType::kCompressed, icon_value->icon_type); |
| EXPECT_TRUE(icon_value->compressed); |
| std::vector<uint8_t> png_data = icon_value->compressed.value(); |
| std::string compressed(png_data.begin(), png_data.end()); |
| // Check that |compressed| starts with the 8-byte PNG magic string. |
| EXPECT_EQ(compressed.substr(0, 8), |
| "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"); |
| quit.Run(); |
| })); |
| run_loop.Run(); |
| return; |
| } |
| |
| apps::ArcIconOnceLoader once_loader(profile()); |
| once_loader.LoadIcon( |
| app_id, icon_size, apps::mojom::IconType::kCompressed, |
| base::BindLambdaForTesting([&](ArcAppIcon* icon) { |
| const std::map<ui::ScaleFactor, std::string>& compressed_images = |
| icon->compressed_images(); |
| size_t num_compressed_images_seen = 0; |
| for (auto& scale_factor : scale_factors) { |
| auto iter = compressed_images.find(scale_factor); |
| if (iter != compressed_images.end()) { |
| num_compressed_images_seen++; |
| const std::string& compressed = iter->second; |
| // Check that |compressed| starts with the 8-byte PNG magic |
| // string. |
| EXPECT_EQ(compressed.substr(0, 8), |
| "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"); |
| } |
| } |
| EXPECT_EQ(num_compressed_images_seen, scale_factors.size()); |
| quit.Run(); |
| })); |
| |
| run_loop.Run(); |
| once_loader.StopObserving(ArcAppListPrefs::Get(profile_.get())); |
| } |
| |
| TEST_P(ArcAppModelIconTest, IconInvalidation) { |
| ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| const std::string app_id = StartApp(1 /* package_version */); |
| WaitForIconUpdates(profile_.get(), app_id); |
| |
| // Simulate ARC restart. |
| RestartArc(); |
| StartApp(1 /* package_version */); |
| |
| // No icon update requests on restart. Icons were not invalidated. |
| EXPECT_TRUE(app_instance()->icon_requests().empty()); |
| |
| // Send new apps for the package. This should invalidate package icons. |
| UpdatePackage(2 /* package_version */); |
| |
| EnsureIconsUpdated(); |
| |
| // Simulate ARC restart again. |
| RestartArc(); |
| StartApp(2 /* package_version */); |
| |
| // No new icon update requests on restart. Icons were invalidated and updated. |
| EXPECT_TRUE(app_instance()->icon_requests().empty()); |
| } |
| |
| TEST_P(ArcAppModelIconTest, IconInvalidationOnFrameworkUpdate) { |
| ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| const arc::mojom::AppInfo app = test_app(); |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| SendRefreshAppList({app}); |
| |
| std::vector<arc::mojom::ArcPackageInfoPtr> packages; |
| packages.emplace_back(CreatePackage(app.package_name)); |
| packages.emplace_back( |
| CreatePackageWithVersion(kFrameworkPackageName, kFrameworkNycVersion)); |
| app_instance()->SendRefreshPackageList(std::move(packages)); |
| |
| WaitForIconUpdates(profile_.get(), app_id); |
| |
| RestartArc(); |
| |
| SendRefreshAppList({app}); |
| |
| // Framework is the same, no update. |
| packages.emplace_back(CreatePackage(app.package_name)); |
| packages.emplace_back( |
| CreatePackageWithVersion(kFrameworkPackageName, kFrameworkNycVersion)); |
| app_instance()->SendRefreshPackageList(std::move(packages)); |
| |
| EXPECT_TRUE(app_instance()->icon_requests().empty()); |
| |
| RestartArc(); |
| |
| SendRefreshAppList({app}); |
| |
| // Framework was updated, app icons should be updated even app's package is |
| // the same. |
| packages.emplace_back(CreatePackage(app.package_name)); |
| packages.emplace_back( |
| CreatePackageWithVersion(kFrameworkPackageName, kFrameworkPiVersion)); |
| app_instance()->SendRefreshPackageList(std::move(packages)); |
| |
| EXPECT_FALSE(app_instance()->icon_requests().empty()); |
| EnsureIconsUpdated(); |
| } |
| |
| // This verifies that app icons are invalidated in case icon version was |
| // changed which means ARC sends icons using updated processing. |
| TEST_P(ArcAppModelIconTest, IconInvalidationOnIconVersionUpdate) { |
| ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| const std::string app_id = StartApp(1 /* package_version */); |
| |
| // Simulate ARC restart. |
| RestartArc(); |
| // Simulate new icons version. |
| ArcAppListPrefs::UprevCurrentIconsVersionForTesting(); |
| StartApp(1 /* package_version */); |
| |
| // Requests to reload icons are issued for all supported scales. |
| EnsureIconsUpdated(); |
| |
| // Next start should be without invalidation. |
| RestartArc(); |
| StartApp(1 /* package_version */); |
| |
| // No new icon update requests on restart. Icons were invalidated and updated. |
| EXPECT_TRUE(app_instance()->icon_requests().empty()); |
| } |
| |
| // TODO(crbug.com/1005069) Disabled on Chrome OS due to flake |
| #if defined(OS_CHROMEOS) |
| #define MAYBE_IconLoadNonSupportedScales DISABLED_IconLoadNonSupportedScales |
| #else |
| #define MAYBE_IconLoadNonSupportedScales IconLoadNonSupportedScales |
| #endif |
| TEST_P(ArcAppModelIconTest, MAYBE_IconLoadNonSupportedScales) { |
| ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| const std::string app_id = StartApp(1 /* package_version */); |
| |
| FakeAppIconLoaderDelegate delegate; |
| AppServiceAppIconLoader icon_loader( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &delegate); |
| icon_loader.FetchImage(app_id); |
| // Expected 1 update with default image and 2 representations should be |
| // allocated. |
| EXPECT_EQ(1U, delegate.update_image_count()); |
| gfx::ImageSkia app_icon = delegate.image(); |
| EXPECT_EQ(2U, app_icon.image_reps().size()); |
| EXPECT_TRUE(app_icon.HasRepresentation(1.0f)); |
| EXPECT_TRUE(app_icon.HasRepresentation(2.0f)); |
| |
| // Request non-supported scales. Cached supported representations with |
| // default image should be used. 1.0 is used to scale 1.15 and |
| // 2.0 is used to scale 1.25. |
| app_icon.GetRepresentation(1.15f); |
| app_icon.GetRepresentation(1.25f); |
| EXPECT_EQ(1U, delegate.update_image_count()); |
| EXPECT_EQ(4U, app_icon.image_reps().size()); |
| EXPECT_TRUE(app_icon.HasRepresentation(1.0f)); |
| EXPECT_TRUE(app_icon.HasRepresentation(2.0f)); |
| EXPECT_TRUE(app_icon.HasRepresentation(1.15f)); |
| EXPECT_TRUE(app_icon.HasRepresentation(1.25f)); |
| |
| // Keep default images for reference. |
| const SkBitmap bitmap_1_0 = app_icon.GetRepresentation(1.0f).GetBitmap(); |
| const SkBitmap bitmap_1_15 = app_icon.GetRepresentation(1.15f).GetBitmap(); |
| const SkBitmap bitmap_1_25 = app_icon.GetRepresentation(1.25f).GetBitmap(); |
| const SkBitmap bitmap_2_0 = app_icon.GetRepresentation(2.0f).GetBitmap(); |
| |
| delegate.WaitForIconUpdates(1); |
| |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.0f).GetBitmap(), bitmap_1_0)); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.15f).GetBitmap(), bitmap_1_15)); |
| EXPECT_TRUE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.25f).GetBitmap(), bitmap_1_25)); |
| EXPECT_TRUE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(2.0f).GetBitmap(), bitmap_2_0)); |
| |
| // Send icon image for 200P. 2.0 and 1.25 should be updated. |
| delegate.WaitForIconUpdates(1); |
| |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.0f).GetBitmap(), bitmap_1_0)); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.15f).GetBitmap(), bitmap_1_15)); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(1.25f).GetBitmap(), bitmap_1_25)); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual( |
| app_icon.GetRepresentation(2.0f).GetBitmap(), bitmap_2_0)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, AppLauncher) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile()); |
| ASSERT_NE(nullptr, prefs); |
| |
| // App1 is called in deferred mode, after refreshing apps. |
| // App2 is never called since app is not avaialble. |
| // App3 is never called immediately because app is available already. |
| const arc::mojom::AppInfo& app1 = fake_apps()[0]; |
| const arc::mojom::AppInfo& app2 = fake_apps()[1]; |
| const arc::mojom::AppInfo& app3 = fake_apps()[2]; |
| const std::string id1 = ArcAppTest::GetAppId(app1); |
| const std::string id2 = ArcAppTest::GetAppId(app2); |
| const std::string id3 = ArcAppTest::GetAppId(app3); |
| |
| ArcAppLauncher launcher1(profile(), id1, base::Optional<std::string>(), false, |
| display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| EXPECT_FALSE(launcher1.app_launched()); |
| EXPECT_TRUE(prefs->HasObserver(&launcher1)); |
| |
| ArcAppLauncher launcher3(profile(), id3, base::Optional<std::string>(), false, |
| display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| EXPECT_FALSE(launcher1.app_launched()); |
| EXPECT_TRUE(prefs->HasObserver(&launcher1)); |
| EXPECT_FALSE(launcher3.app_launched()); |
| EXPECT_TRUE(prefs->HasObserver(&launcher3)); |
| |
| EXPECT_EQ(0u, app_instance()->launch_requests().size()); |
| |
| std::vector<arc::mojom::AppInfo> apps(fake_apps().begin(), |
| fake_apps().begin() + 2); |
| SendRefreshAppList(apps); |
| |
| EXPECT_TRUE(launcher1.app_launched()); |
| ASSERT_EQ(1u, app_instance()->launch_requests().size()); |
| EXPECT_TRUE(app_instance()->launch_requests()[0]->IsForApp(app1)); |
| EXPECT_FALSE(launcher3.app_launched()); |
| EXPECT_FALSE(prefs->HasObserver(&launcher1)); |
| EXPECT_TRUE(prefs->HasObserver(&launcher3)); |
| |
| const std::string launch_intent2 = arc::GetLaunchIntent( |
| app2.package_name, app2.activity, std::vector<std::string>()); |
| ArcAppLauncher launcher2(profile(), id2, launch_intent2, false, |
| display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| EXPECT_TRUE(launcher2.app_launched()); |
| EXPECT_FALSE(prefs->HasObserver(&launcher2)); |
| EXPECT_EQ(1u, app_instance()->launch_requests().size()); |
| ASSERT_EQ(1u, app_instance()->launch_intents().size()); |
| EXPECT_EQ(app_instance()->launch_intents()[0], launch_intent2); |
| } |
| |
| // Suspended app cannot be triggered from app launcher. |
| TEST_P(ArcAppModelBuilderTest, AppLauncherForSuspendedApp) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile()); |
| ASSERT_NE(nullptr, prefs); |
| |
| arc::mojom::AppInfo app = fake_apps()[0]; |
| app.suspended = true; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| ArcAppLauncher launcher(profile(), app_id, base::Optional<std::string>(), |
| false, display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| EXPECT_FALSE(launcher.app_launched()); |
| |
| // Register app, however it is suspended. |
| SendRefreshAppList({app}); |
| EXPECT_FALSE(launcher.app_launched()); |
| EXPECT_TRUE(app_instance()->launch_requests().empty()); |
| |
| // Update app with non-suspended state. |
| app.suspended = false; |
| SendPackageAppListRefreshed(app.package_name, {app}); |
| EXPECT_TRUE(launcher.app_launched()); |
| |
| ASSERT_EQ(1u, app_instance()->launch_requests().size()); |
| EXPECT_TRUE(app_instance()->launch_requests()[0]->IsForApp(app)); |
| } |
| |
| // Validates an app that have no launchable flag. |
| TEST_P(ArcAppModelBuilderTest, NonLaunchableApp) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ValidateHaveApps(std::vector<arc::mojom::AppInfo>()); |
| // Send all except first. |
| std::vector<arc::mojom::AppInfo> apps(fake_apps().begin() + 1, |
| fake_apps().end()); |
| SendRefreshAppList(apps); |
| ValidateHaveApps(apps); |
| |
| const std::string app_id = ArcAppTest::GetAppId(fake_apps()[0]); |
| |
| EXPECT_FALSE(prefs->IsRegistered(app_id)); |
| EXPECT_FALSE(FindArcItem(app_id)); |
| app_instance()->SendTaskCreated(0, fake_apps()[0], std::string()); |
| // App should not appear now in the model but should be registered. |
| EXPECT_FALSE(FindArcItem(app_id)); |
| EXPECT_TRUE(prefs->IsRegistered(app_id)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, ArcAppsAndShortcutsOnPackageChange) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| std::vector<arc::mojom::AppInfo> apps = fake_apps(); |
| ASSERT_GE(apps.size(), 3U); |
| const std::string& test_package_name = apps[2].package_name; |
| apps[0].package_name = test_package_name; |
| apps[1].package_name = test_package_name; |
| |
| std::vector<arc::mojom::ShortcutInfo> shortcuts = fake_shortcuts(); |
| for (auto& shortcut : shortcuts) |
| shortcut.package_name = test_package_name; |
| |
| // Second app should be preserved after update. |
| std::vector<arc::mojom::AppInfo> apps1(apps.begin(), apps.begin() + 2); |
| std::vector<arc::mojom::AppInfo> apps2(apps.begin() + 1, apps.begin() + 3); |
| |
| // Adding package is required to safely call SendPackageUninstalled. |
| AddPackage(CreatePackage(test_package_name)); |
| |
| SendRefreshAppList(apps1); |
| SendInstallShortcuts(shortcuts); |
| |
| ValidateHaveAppsAndShortcuts(apps1, shortcuts); |
| |
| const std::string app_id = ArcAppTest::GetAppId(apps[1]); |
| const base::Time time_before = base::Time::Now(); |
| prefs->SetLastLaunchTime(app_id); |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info_before = |
| prefs->GetApp(app_id); |
| ASSERT_TRUE(app_info_before); |
| EXPECT_GE(base::Time::Now(), time_before); |
| |
| SendPackageAppListRefreshed(test_package_name, apps2); |
| ValidateHaveAppsAndShortcuts(apps2, shortcuts); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info_after = |
| prefs->GetApp(app_id); |
| ASSERT_TRUE(app_info_after); |
| EXPECT_EQ(app_info_before->last_launch_time, |
| app_info_after->last_launch_time); |
| |
| RemovePackage(test_package_name); |
| ValidateHaveAppsAndShortcuts(std::vector<arc::mojom::AppInfo>(), |
| std::vector<arc::mojom::ShortcutInfo>()); |
| } |
| |
| // This validates that runtime apps are not removed on package change event. |
| TEST_P(ArcAppModelBuilderTest, DontRemoveRuntimeAppOnPackageChange) { |
| ArcAppListPrefs::AppInfo::SetIgnoreCompareInstallTimeForTesting(true); |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| arc::MockArcAppListPrefsObserver observer; |
| |
| ASSERT_GE(fake_apps().size(), 2U); |
| |
| // Second app should be preserved after the package update. |
| std::vector<arc::mojom::AppInfo> apps(fake_apps().begin(), |
| fake_apps().begin() + 2); |
| apps[0].package_name = apps[1].package_name; |
| |
| const std::string app_id1 = ArcAppTest::GetAppId(apps[0]); |
| const std::string app_id2 = ArcAppTest::GetAppId(apps[1]); |
| |
| arc::mojom::ArcPackageInfoPtr package = CreatePackage(apps[0].package_name); |
| |
| prefs->AddObserver(&observer); |
| |
| EXPECT_CALL(observer, OnPackageInstalled(ArcPackageInfoIs(package.get()))) |
| .Times(1); |
| EXPECT_CALL(observer, |
| OnAppRegistered(app_id1, GetAppInfoExpectation( |
| apps[0], true /* launchable */))) |
| .Times(1); |
| EXPECT_CALL(observer, |
| OnAppRegistered(app_id2, GetAppInfoExpectation( |
| apps[1], true /* launchable */))) |
| .Times(1); |
| |
| AddPackage(package); |
| |
| SendRefreshAppList(apps); |
| |
| // Send a task for non-existing lauchable app. That would register new runtime |
| // app. |
| arc::mojom::AppInfo app_runtime = apps[0]; |
| app_runtime.activity += "_runtime"; |
| // Runtime apps have notifications_enabled and sticky false. |
| app_runtime.notifications_enabled = false; |
| app_runtime.sticky = false; |
| const std::string app_id3 = ArcAppTest::GetAppId(app_runtime); |
| |
| EXPECT_CALL(observer, OnAppRegistered( |
| app_id3, GetAppInfoExpectation( |
| app_runtime, false /* launchable */))) |
| .Times(1); |
| EXPECT_CALL(observer, |
| OnTaskCreated(1 /* task_id */, app_runtime.package_name, |
| app_runtime.activity, std::string() /* name */)) |
| .Times(1); |
| |
| app_instance()->SendTaskCreated(1, app_runtime, std::string()); |
| |
| // Simulate package update when first launchable app is removed. This should |
| // trigger app removing for it but not for the runtime app. |
| EXPECT_CALL(observer, OnAppRemoved(app_id1)).Times(1); |
| EXPECT_CALL(observer, OnAppRemoved(app_id2)).Times(0); |
| EXPECT_CALL(observer, OnAppRemoved(app_id3)).Times(0); |
| |
| apps.erase(apps.begin()); |
| SendPackageAppListRefreshed(apps[0].package_name, apps); |
| |
| prefs->RemoveObserver(&observer); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, PackageSyncableServiceEnabled) { |
| EXPECT_TRUE(ProfileSyncServiceFactory::GetForProfile(profile_.get()) |
| ->GetRegisteredDataTypes() |
| .Has(syncer::ARC_PACKAGE)); |
| } |
| |
| TEST_P(ArcAppModelBuilderTest, PackageSyncableServiceDisabled) { |
| base::test::ScopedCommandLine command_line; |
| command_line.GetProcessCommandLine()->AppendSwitch( |
| chromeos::switches::kArcDisableAppSync); |
| EXPECT_FALSE(ProfileSyncServiceFactory::GetForProfile(profile_.get()) |
| ->GetRegisteredDataTypes() |
| .Has(syncer::ARC_PACKAGE)); |
| } |
| |
| TEST_P(ArcDefaultAppTest, DefaultApps) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ValidateHaveApps(fake_default_apps()); |
| |
| // Start normal apps. We should have apps from 2 subsets. |
| SendRefreshAppList(fake_apps()); |
| |
| std::vector<arc::mojom::AppInfo> all_apps = fake_default_apps(); |
| all_apps.insert(all_apps.end(), fake_apps().begin(), fake_apps().end()); |
| ValidateHaveApps(all_apps); |
| |
| // However default apps are still not ready. |
| for (const auto& default_app : fake_default_apps()) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(ArcAppTest::GetAppId(default_app)); |
| ASSERT_TRUE(app_info); |
| EXPECT_FALSE(app_info->ready); |
| EXPECT_NE(base::Time(), app_info->install_time); |
| } |
| |
| // Install default apps. |
| for (const auto& default_app : fake_default_apps()) { |
| std::vector<arc::mojom::AppInfo> package_apps; |
| package_apps.push_back(default_app); |
| SendPackageAppListRefreshed(default_app.package_name, package_apps); |
| |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(ArcAppTest::GetAppId(default_app)); |
| ASSERT_TRUE(app_info); |
| EXPECT_NE(base::Time(), app_info->install_time); |
| } |
| |
| // And now default apps are ready. |
| std::map<std::string, bool> oem_states; |
| for (const auto& default_app : fake_default_apps()) { |
| const std::string app_id = ArcAppTest::GetAppId(default_app); |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| ASSERT_TRUE(app_info); |
| EXPECT_TRUE(app_info->ready); |
| oem_states[app_id] = prefs->IsOem(app_id); |
| } |
| |
| // Uninstall first default package. Default app should go away. |
| SendPackageUninstalled(all_apps[0].package_name); |
| all_apps.erase(all_apps.begin()); |
| ValidateHaveApps(all_apps); |
| |
| // OptOut and default apps should exist minus first. |
| // TODO(victorhsieh): Opt-out on Persistent ARC is special. Skip until |
| // implemented. |
| if (arc::ShouldArcAlwaysStart()) |
| return; |
| SetArcPlayStoreEnabledForProfile(profile(), false); |
| |
| all_apps = fake_default_apps(); |
| all_apps.erase(all_apps.begin()); |
| ValidateHaveApps(all_apps); |
| |
| // Sign-out and sign-in again. Removed default app should not appear. |
| RestartArc(); |
| |
| // Prefs are changed. |
| prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| ValidateHaveApps(all_apps); |
| |
| // Install deleted default app again. |
| std::vector<arc::mojom::AppInfo> package_apps; |
| package_apps.push_back(fake_default_apps()[0]); |
| SendPackageAppListRefreshed(fake_default_apps()[0].package_name, |
| package_apps); |
| ValidateHaveApps(fake_default_apps()); |
| |
| // Validate that OEM state is preserved. |
| for (const auto& default_app : fake_default_apps()) { |
| const std::string app_id = ArcAppTest::GetAppId(default_app); |
| EXPECT_TRUE(prefs->IsDefault(app_id)); |
| EXPECT_EQ(oem_states[app_id], prefs->IsOem(app_id)); |
| } |
| } |
| |
| // Test that validates disabling default app removes app from the list and this |
| // is persistent in next sessions. |
| TEST_P(ArcDefaultAppTest, DisableDefaultApps) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| ValidateHaveApps(fake_default_apps()); |
| |
| // Install default app. |
| const arc::mojom::AppInfo default_app = fake_default_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(default_app); |
| std::vector<arc::mojom::AppInfo> package_apps; |
| package_apps.push_back(default_app); |
| SendPackageAppListRefreshed(default_app.package_name, package_apps); |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| ASSERT_TRUE(app_info); |
| EXPECT_TRUE(app_info->ready); |
| EXPECT_TRUE(prefs->IsDefault(app_id)); |
| |
| // Disable default app. In this case list of apps for package is empty. |
| package_apps.clear(); |
| SendPackageAppListRefreshed(default_app.package_name, package_apps); |
| EXPECT_FALSE(prefs->GetApp(app_id)); |
| |
| // Sign-out and sign-in again. Disabled default app should not appear. |
| RestartArc(); |
| |
| prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| EXPECT_FALSE(prefs->GetApp(app_id)); |
| } |
| |
| TEST_P(ArcAppLauncherForDefaultAppTest, AppIconUpdated) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ASSERT_FALSE(fake_default_apps().empty()); |
| const arc::mojom::AppInfo& app = fake_default_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| EXPECT_FALSE(prefs->GetApp(app_id)); |
| EXPECT_TRUE(prefs |
| ->MaybeGetIconPathForDefaultApp( |
| app_id, GetAppListIconDescriptor(ui::SCALE_FACTOR_100P)) |
| .empty()); |
| arc_test()->WaitForDefaultApps(); |
| EXPECT_TRUE(prefs->GetApp(app_id)); |
| EXPECT_FALSE(prefs |
| ->MaybeGetIconPathForDefaultApp( |
| app_id, GetAppListIconDescriptor(ui::SCALE_FACTOR_100P)) |
| .empty()); |
| |
| // Icon can be only fetched after app is registered in the system. |
| FakeAppIconLoaderDelegate icon_delegate; |
| std::unique_ptr<AppServiceAppIconLoader> icon_loader = |
| std::make_unique<AppServiceAppIconLoader>( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &icon_delegate); |
| icon_loader->FetchImage(app_id); |
| icon_delegate.WaitForIconUpdates(1); |
| icon_loader.reset(); |
| |
| // Restart ARC to validate default app icon can be loaded next session. |
| RestartArc(); |
| prefs = ArcAppListPrefs::Get(profile_.get()); |
| |
| FakeAppIconLoaderDelegate icon_delegate2; |
| icon_loader = std::make_unique<AppServiceAppIconLoader>( |
| profile(), ash::AppListConfig::instance().grid_icon_dimension(), |
| &icon_delegate2); |
| icon_loader->FetchImage(app_id); |
| // Default app icon becomes available once default apps loaded |
| // (asynchronously). |
| EXPECT_TRUE(prefs |
| ->MaybeGetIconPathForDefaultApp( |
| app_id, GetAppListIconDescriptor(ui::SCALE_FACTOR_100P)) |
| .empty()); |
| icon_delegate2.WaitForIconUpdates(1); |
| EXPECT_FALSE(prefs |
| ->MaybeGetIconPathForDefaultApp( |
| app_id, GetAppListIconDescriptor(ui::SCALE_FACTOR_100P)) |
| .empty()); |
| icon_loader.reset(); |
| } |
| |
| // Validates that default app icon can be loaded for non-default dips, that do |
| // not exist in Chrome image. |
| TEST_P(ArcAppLauncherForDefaultAppTest, AppIconNonDefaultDip) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ASSERT_FALSE(fake_default_apps().empty()); |
| const arc::mojom::AppInfo& app = fake_default_apps()[0]; |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| |
| // Icon can be only fetched after app is registered in the system. |
| arc_test()->WaitForDefaultApps(); |
| |
| FakeAppIconLoaderDelegate icon_delegate; |
| // 17 should never be a default dip size. |
| std::unique_ptr<AppServiceAppIconLoader> icon_loader = |
| std::make_unique<AppServiceAppIconLoader>(profile(), 17, &icon_delegate); |
| icon_loader->FetchImage(app_id); |
| icon_delegate.WaitForIconUpdates(1); |
| icon_loader.reset(); |
| } |
| |
| TEST_P(ArcAppLauncherForDefaultAppTest, AppLauncherForDefaultApps) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ASSERT_GE(fake_default_apps().size(), 2U); |
| const arc::mojom::AppInfo& app1 = fake_default_apps()[0]; |
| const arc::mojom::AppInfo& app2 = fake_default_apps()[1]; |
| const std::string id1 = ArcAppTest::GetAppId(app1); |
| const std::string id2 = ArcAppTest::GetAppId(app2); |
| |
| // Launch when app is registered and ready. |
| ArcAppLauncher launcher1(profile(), id1, base::Optional<std::string>(), false, |
| display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| // Launch when app is registered. |
| ArcAppLauncher launcher2(profile(), id2, base::Optional<std::string>(), true, |
| display::kInvalidDisplayId, |
| arc::UserInteractionType::NOT_USER_INITIATED); |
| |
| EXPECT_FALSE(launcher1.app_launched()); |
| |
| arc_test()->WaitForDefaultApps(); |
| |
| // Only second app is expected to be launched. |
| EXPECT_FALSE(launcher1.app_launched()); |
| EXPECT_TRUE(launcher2.app_launched()); |
| |
| SendRefreshAppList(fake_default_apps()); |
| // Default apps are ready now and it is expected that first app was launched |
| // now. |
| EXPECT_TRUE(launcher1.app_launched()); |
| } |
| |
| TEST_P(ArcDefaultAppTest, DefaultAppsNotAvailable) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| ValidateHaveApps(fake_default_apps()); |
| |
| const std::vector<arc::mojom::AppInfo> empty_app_list; |
| |
| SendRefreshAppList(empty_app_list); |
| |
| std::vector<arc::mojom::AppInfo> expected_apps(fake_default_apps()); |
| ValidateHaveApps(expected_apps); |
| |
| if (GetArcState() == ArcState::ARC_WITHOUT_PLAY_STORE) { |
| SimulateDefaultAppAvailabilityTimeoutForTesting(prefs); |
| ValidateHaveApps(std::vector<arc::mojom::AppInfo>()); |
| return; |
| } |
| |
| // PAI was not started and we should not have any active timer for default |
| // apps. |
| SimulateDefaultAppAvailabilityTimeoutForTesting(prefs); |
| ValidateHaveApps(expected_apps); |
| |
| arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get(); |
| ASSERT_TRUE(arc_session_manager); |
| |
| arc::ArcPaiStarter* pai_starter = arc_session_manager->pai_starter(); |
| ASSERT_TRUE(pai_starter); |
| |
| EXPECT_FALSE(pai_starter->started()); |
| |
| // Play store app triggers PAI. |
| arc::mojom::AppInfo app; |
| app.name = "Play Store"; |
| app.package_name = arc::kPlayStorePackage; |
| app.activity = arc::kPlayStoreActivity; |
| |
| std::vector<arc::mojom::AppInfo> only_play_store({app}); |
| SendRefreshAppList(only_play_store); |
| expected_apps.push_back(app); |
| |
| // Timer was set to detect not available default apps. |
| ValidateHaveApps(expected_apps); |
| |
| SimulateDefaultAppAvailabilityTimeoutForTesting(prefs); |
| |
| // No default app installation and already installed packages. |
| ValidateHaveApps(only_play_store); |
| } |
| |
| TEST_P(ArcDefaultAppTest, DefaultAppsInstallation) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_NE(nullptr, prefs); |
| |
| const std::vector<arc::mojom::AppInfo> empty_app_list; |
| |
| ValidateHaveApps(fake_default_apps()); |
| |
| SendRefreshAppList(empty_app_list); |
| |
| ValidateHaveApps(fake_default_apps()); |
| |
| // Notify that default installations have been started. |
| for (const auto& fake_app : fake_default_apps()) |
| SendInstallationStarted(fake_app.package_name); |
| |
| // Timeout does not affect default app availability because all installations |
| // for default apps have been started. |
| // prefs->SimulateDefaultAppAvailabilityTimeoutForTesting(); |
| SimulateDefaultAppAvailabilityTimeoutForTesting(prefs); |
| ValidateHaveApps(fake_default_apps()); |
| |
| const arc::mojom::AppInfo& app_last = fake_default_apps().back(); |
| std::vector<arc::mojom::AppInfo> available_apps = fake_default_apps(); |
| available_apps.pop_back(); |
| |
| for (const auto& fake_app : available_apps) |
| SendInstallationFinished(fake_app.package_name, true); |
| |
| // So far we have all default apps available because not all installations |
| // completed. |
| ValidateHaveApps(fake_default_apps()); |
| |
| // Last default app installation failed. |
| SendInstallationFinished(app_last.package_name, false); |
| |
| // We should have all default apps except last. |
| ValidateHaveApps(available_apps); |
| } |
| |
| TEST_P(ArcDefaultAppForManagedUserTest, DefaultAppsForManagedUser) { |
| const ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get()); |
| ASSERT_TRUE(prefs); |
| |
| // There is no default app for managed users except Play Store |
| for (const auto& app : fake_default_apps()) { |
| const std::string app_id = ArcAppTest::GetAppId(app); |
| EXPECT_FALSE(prefs->IsRegistered(app_id)); |
| EXPECT_FALSE(prefs->GetApp(app_id)); |
| } |
| |
| // PlayStor exists for managed and enabled state. |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(arc::kPlayStoreAppId); |
| if (IsEnabledByPolicy()) { |
| ASSERT_TRUE(app_info); |
| EXPECT_FALSE(app_info->ready); |
| } else { |
| EXPECT_FALSE(prefs->IsRegistered(arc::kPlayStoreAppId)); |
| EXPECT_FALSE(app_info); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcAppModelBuilderTest, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcDefaultAppTest, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcAppLauncherForDefaultAppTest, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcDefaultAppForManagedUserTest, |
| ::testing::ValuesIn(kManagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcPlayStoreAppTest, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcAppModelBuilderRecreate, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |
| INSTANTIATE_TEST_SUITE_P(All, |
| ArcAppModelIconTest, |
| ::testing::ValuesIn(kUnmanagedArcStates)); |