chromium/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc

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

#include "chrome/browser/ash/eche_app/eche_app_manager_factory.h"

#include <memory>
#include <optional>

#include "ash/constants/ash_features.h"
#include "ash/system/eche/eche_tray.h"
#include "ash/system/phonehub/phone_hub_tray.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/test/test_ash_web_view_factory.h"
#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
#include "ash/webui/eche_app_ui/eche_alert_generator.h"
#include "ash/webui/eche_app_ui/system_info.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_chromeos_version_info.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/ash/eche_app/eche_app_notification_controller.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace ash {
namespace eche_app {

class EcheAppManagerFactoryTest : public ChromeAshTestBase {
 protected:
  EcheAppManagerFactoryTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kEcheSWA},
        /*disabled_features=*/{});
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    if (profile_manager_->SetUp()) {
      profile_ = profile_manager_->CreateTestingProfile("testing_profile");
    }
  }
  ~EcheAppManagerFactoryTest() override = default;
  EcheAppManagerFactoryTest(const EcheAppManagerFactoryTest&) = delete;
  EcheAppManagerFactoryTest& operator=(const EcheAppManagerFactoryTest&) =
      delete;

  // AshTestBase::Test:
  void SetUp() override {
    DCHECK(profile_);
    DCHECK(test_web_view_factory_.get());
    ChromeAshTestBase::SetUp();
    connection_handler_ = std::make_unique<EcheConnectionStatusHandler>();
    apps_launch_info_provider_ =
        std::make_unique<AppsLaunchInfoProvider>(connection_handler_.get());
    apps_launch_info_provider_->SetAppLaunchInfo(
        mojom::AppStreamLaunchEntryPoint::APPS_LIST);
    eche_tray_ = StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
    phone_hub_tray_ =
        StatusAreaWidgetTestHelper::GetStatusAreaWidget()->phone_hub_tray();
    display_service_ =
        std::make_unique<NotificationDisplayServiceTester>(GetProfile());
    eche_app_manager_factory_ = EcheAppManagerFactory::GetInstance();
  }

  void ShowAndCloseConnectionOrLaunchErrorNotifications() {
    std::u16string title = u"title";
    std::u16string message = u"message";
    EcheAppManagerFactory::ShowNotification(
        eche_app_manager_factory_->weak_ptr_factory_.GetWeakPtr(), GetProfile(),
        title, message,
        std::make_unique<LaunchAppHelper::NotificationInfo>(
            LaunchAppHelper::NotificationInfo::Category::kNative,
            LaunchAppHelper::NotificationInfo::NotificationType::kScreenLock));
    EcheAppManagerFactory::ShowNotification(
        eche_app_manager_factory_->weak_ptr_factory_.GetWeakPtr(), GetProfile(),
        title, message,
        std::make_unique<LaunchAppHelper::NotificationInfo>(
            LaunchAppHelper::NotificationInfo::Category::kWebUI,
            mojom::WebNotificationType::CONNECTION_FAILED));
    EcheAppManagerFactory::ShowNotification(
        eche_app_manager_factory_->weak_ptr_factory_.GetWeakPtr(), GetProfile(),
        title, message,
        std::make_unique<LaunchAppHelper::NotificationInfo>(
            LaunchAppHelper::NotificationInfo::Category::kWebUI,
            mojom::WebNotificationType::DEVICE_IDLE));
    EcheAppManagerFactory::ShowNotification(
        eche_app_manager_factory_->weak_ptr_factory_.GetWeakPtr(), GetProfile(),
        title, message,
        std::make_unique<LaunchAppHelper::NotificationInfo>(
            LaunchAppHelper::NotificationInfo::Category::kWebUI,
            mojom::WebNotificationType::INVALID_NOTIFICATION));
    eche_app_manager_factory_->CloseConnectionOrLaunchErrorNotifications();

    std::optional<message_center::Notification> notification =
        display_service_->GetNotification(kEcheAppScreenLockNotifierId);
    ASSERT_TRUE(notification.has_value());
    notification =
        display_service_->GetNotification(kEcheAppRetryConnectionNotifierId);
    ASSERT_FALSE(notification.has_value());
    notification =
        display_service_->GetNotification(kEcheAppInactivityNotifierId);
    ASSERT_FALSE(notification.has_value());
    notification = display_service_->GetNotification(
        kEcheAppFromWebWithoutButtonNotifierId);
    ASSERT_FALSE(notification.has_value());
  }

  TestingProfile* GetProfile() { return profile_; }
  AppsLaunchInfoProvider* GetAppsLaunchInfoProvider() {
    return apps_launch_info_provider_.get();
  }
  EcheTray* eche_tray() { return eche_tray_; }
  PhoneHubTray* phone_hub_tray() { return phone_hub_tray_; }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile> profile_;
  std::unique_ptr<EcheConnectionStatusHandler> connection_handler_;
  std::unique_ptr<AppsLaunchInfoProvider> apps_launch_info_provider_;
  raw_ptr<EcheTray, DanglingUntriaged> eche_tray_ = nullptr;
  raw_ptr<PhoneHubTray, DanglingUntriaged> phone_hub_tray_ = nullptr;
  raw_ptr<EcheAppManagerFactory> eche_app_manager_factory_ = nullptr;
  // Calling the factory constructor is enough to set it up.
  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
      std::make_unique<TestAshWebViewFactory>();
  std::unique_ptr<NotificationDisplayServiceTester> display_service_;
};

class EcheAppManagerFactoryWithBackgroundTest : public ChromeAshTestBase {
 protected:
  EcheAppManagerFactoryWithBackgroundTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kEcheSWA},
        /*disabled_features=*/{});
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    if (profile_manager_->SetUp()) {
      profile_ = profile_manager_->CreateTestingProfile("testing_profile");
    }
  }
  ~EcheAppManagerFactoryWithBackgroundTest() override = default;
  EcheAppManagerFactoryWithBackgroundTest(
      const EcheAppManagerFactoryWithBackgroundTest&) = delete;
  EcheAppManagerFactoryWithBackgroundTest& operator=(
      const EcheAppManagerFactoryWithBackgroundTest&) = delete;

  // AshTestBase::Test:
  void SetUp() override {
    DCHECK(profile_);
    DCHECK(test_web_view_factory_.get());
    ChromeAshTestBase::SetUp();
    connection_handler_ = std::make_unique<EcheConnectionStatusHandler>();
    apps_launch_info_provider_ =
        std::make_unique<AppsLaunchInfoProvider>(connection_handler_.get());
    apps_launch_info_provider_->SetAppLaunchInfo(
        mojom::AppStreamLaunchEntryPoint::APPS_LIST);
    eche_tray_ = StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
  }

  TestingProfile* GetProfile() { return profile_; }
  AppsLaunchInfoProvider* GetAppsLaunchInfoProvider() {
    return apps_launch_info_provider_.get();
  }

  EcheTray* eche_tray() { return eche_tray_; }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile> profile_;
  std::unique_ptr<EcheConnectionStatusHandler> connection_handler_;
  std::unique_ptr<AppsLaunchInfoProvider> apps_launch_info_provider_;
  raw_ptr<EcheTray, DanglingUntriaged> eche_tray_ = nullptr;
  // Calling the factory constructor is enough to set it up.
  std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
      std::make_unique<TestAshWebViewFactory>();
};

TEST_F(EcheAppManagerFactoryTest, LaunchEcheApp) {
  const int64_t user_id = 1;
  const char16_t visible_name_1[] = u"Fake App 1";
  const char package_name_1[] = "com.fakeapp1";
  const char16_t phone_name[] = u"your phone";

  EcheAppManagerFactory::LaunchEcheApp(
      GetProfile(), /*notification_id=*/std::nullopt, package_name_1,
      visible_name_1, user_id, gfx::Image(), phone_name,
      GetAppsLaunchInfoProvider());
  // Wait for Eche Tray to load Eche Web to complete
  base::RunLoop().RunUntilIdle();
  // Eche icon should be visible after launch.
  EXPECT_TRUE(phone_hub_tray()->eche_icon_view()->GetVisible());

  // Launch different application should not recreate widget
  views::Widget* widget = eche_tray()->GetBubbleWidget();
  const char16_t visible_name_2[] = u"Fake App 2";
  const char package_name_2[] = "com.fakeapp2";
  EcheAppManagerFactory::LaunchEcheApp(
      GetProfile(), /*notification_id=*/std::nullopt, package_name_2,
      visible_name_2, user_id, gfx::Image(), phone_name,
      GetAppsLaunchInfoProvider());
  // Wait for Eche Tray to load Eche Web to complete
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(widget, eche_tray()->GetBubbleWidget());
}

TEST_F(EcheAppManagerFactoryTest, LaunchedAppInfo) {
  const int64_t user_id = 1;
  const std::u16string visible_name = u"Fake App";
  const std::string package_name = "com.fakeapp";
  const gfx::Image icon = gfx::test::CreateImage(100, 100);
  const std::u16string phone_name = u"your phone";

  EcheAppManagerFactory::LaunchEcheApp(
      GetProfile(), /*notification_id=*/std::nullopt, package_name,
      visible_name, user_id, icon, phone_name, GetAppsLaunchInfoProvider());

  std::unique_ptr<LaunchedAppInfo> launched_app_info =
      EcheAppManagerFactory::GetInstance()->GetLastLaunchedAppInfo();

  EXPECT_EQ(launched_app_info->user_id(), user_id);
  EXPECT_EQ(launched_app_info->visible_name(), visible_name);
  EXPECT_EQ(launched_app_info->package_name(), package_name);
  EXPECT_EQ(launched_app_info->icon(), icon);
}

TEST_F(EcheAppManagerFactoryTest, CloseConnectionOrLaunchErrorNotifications) {
  ShowAndCloseConnectionOrLaunchErrorNotifications();
}

TEST_F(EcheAppManagerFactoryWithBackgroundTest, LaunchEcheApp) {
  const int64_t user_id = 1;
  const char16_t visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";
  const char16_t phone_name[] = u"your phone";

  EcheAppManagerFactory::LaunchEcheApp(
      GetProfile(), /*notification_id=*/std::nullopt, package_name,
      visible_name, user_id, gfx::Image(), phone_name,
      GetAppsLaunchInfoProvider());
  // Wait for Eche Tray to load Eche Web to complete
  base::RunLoop().RunUntilIdle();
  // Eche tray should be visible when streaming is active, not ative when
  // launch.
  EXPECT_FALSE(eche_tray()->is_active());
}

TEST_F(EcheAppManagerFactoryTest, GetSystemInfo) {
  const char kLsbRelease[] =
      "CHROMEOS_RELEASE_NAME=Non Chrome OS\n"
      "CHROMEOS_RELEASE_VERSION=1.2.3.4\n";
  const base::Time lsb_release_time(
      base::Time::FromSecondsSinceUnixEpoch(12345.6));
  base::test::ScopedChromeOSVersionInfo version(kLsbRelease, lsb_release_time);
  std::unique_ptr<SystemInfo> system_info =
      EcheAppManagerFactory::GetInstance()->GetSystemInfo(GetProfile());

  EXPECT_EQ("1.2.3", system_info->GetOsVersion());
  EXPECT_EQ("Chrome device", system_info->GetDeviceType());
}

TEST_F(EcheAppManagerFactoryTest, GetSystemInfo_flagDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;

  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kEcheSWA},
      /*disabled_features=*/{features::kEcheMetricsRevamp});
  const char kLsbRelease[] =
      "CHROMEOS_RELEASE_NAME=Non Chrome OS\n"
      "CHROMEOS_RELEASE_VERSION=1.2.3.4\n";
  const base::Time lsb_release_time(
      base::Time::FromSecondsSinceUnixEpoch(12345.6));
  base::test::ScopedChromeOSVersionInfo version(kLsbRelease, lsb_release_time);
  std::unique_ptr<SystemInfo> system_info =
      EcheAppManagerFactory::GetInstance()->GetSystemInfo(GetProfile());

  EXPECT_EQ("", system_info->GetOsVersion());
  EXPECT_EQ("Chrome device", system_info->GetDeviceType());
}

}  // namespace eche_app
}  // namespace ash