chromium/chrome/browser/chromeos/arc/arc_external_protocol_dialog_unittest.cc

// Copyright 2016 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/chromeos/arc/arc_external_protocol_dialog.h"

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/arc/arc_web_contents_data.h"
#include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
#include "chrome/browser/sharing/sharing_service_factory.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "components/arc/common/intent_helper/arc_intent_helper_mojo_delegate.h"
#include "components/arc/common/intent_helper/arc_intent_helper_package.h"
#include "components/arc/common/test/fake_arc_icon_cache.h"
#include "components/arc/common/test/fake_arc_intent_helper_mojo.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/mock_sharing_service.h"
#include "components/sharing_message/proto/click_to_call_message.pb.h"
#include "components/sharing_message/proto/sharing_message.pb.h"
#include "components/sharing_message/sharing_target_device_info.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"

using ::testing::Property;

namespace arc {

namespace {

SharingTargetDeviceInfo CreateFakeSharingTargetDeviceInfo(
    const std::string& guid) {
  return SharingTargetDeviceInfo(guid, "Test name",
                                 SharingDevicePlatform::kUnknown,
                                 /*pulse_interval=*/base::TimeDelta(),
                                 syncer::DeviceInfo::FormFactor::kUnknown,
                                 /*last_updated_timestamp=*/base::Time());
}

// Helper class to run tests that need a dummy WebContents and arc delegate.
class ArcExternalProtocolDialogTestUtils : public BrowserWithTestWindowTest {
 public:
  ArcExternalProtocolDialogTestUtils() = default;
  ArcExternalProtocolDialogTestUtils(
      const ArcExternalProtocolDialogTestUtils&) = delete;
  ArcExternalProtocolDialogTestUtils& operator=(
      const ArcExternalProtocolDialogTestUtils&) = delete;

  void SetUp() override {
    BrowserWithTestWindowTest::SetUp();
    arc_icon_cache_ = std::make_unique<FakeArcIconCache>();
    delegate_provider_ =
        std::make_unique<ArcIconCacheDelegateProvider>(arc_icon_cache_.get());
  }

  void TearDown() override { BrowserWithTestWindowTest::TearDown(); }

 protected:
  void CreateTab(bool started_from_arc) {
    AddTab(browser(), GURL("http://www.tests.com"));

    web_contents_ = browser()->tab_strip_model()->GetWebContentsAt(0);
    if (started_from_arc) {
      web_contents_->SetUserData(
          &ArcWebContentsData::kArcTransitionFlag,
          std::make_unique<ArcWebContentsData>(web_contents_));
    }
  }

  bool WasTabStartedFromArc() {
    return GetAndResetSafeToRedirectToArcWithoutUserConfirmationFlagForTesting(
        web_contents_);
  }

  MockSharingService* CreateSharingService() {
    return static_cast<MockSharingService*>(
        SharingServiceFactory::GetInstance()->SetTestingFactoryAndUse(
            profile(),
            base::BindRepeating([](content::BrowserContext* context) {
              return static_cast<std::unique_ptr<KeyedService>>(
                  std::make_unique<MockSharingService>());
            })));
  }

  content::WebContents* web_contents() { return web_contents_; }

 private:
  base::test::ScopedFeatureList features_{kClickToCall};
  // Keep only one |WebContents| at a time.
  raw_ptr<content::WebContents, DanglingUntriaged> web_contents_;
  std::unique_ptr<ArcIconCacheDelegate> arc_icon_cache_;
  std::unique_ptr<ArcIconCacheDelegateProvider> delegate_provider_;
};

// Creates a dummy GurlAndActivityInfo object.
GurlAndActivityInfo CreateEmptyGurlAndActivityInfo() {
  return std::make_pair(GURL(), ArcIntentHelperMojoDelegate::ActivityName(
                                    /*package_name=*/std::string(),
                                    /*activity_name=*/std::string()));
}

// Creates and returns a new IntentHandlerInfo object.
ArcIntentHelperMojoDelegate::IntentHandlerInfo Create(
    const std::string& name,
    const std::string& package_name,
    const std::string& activity_name,
    bool is_preferred,
    const GURL& fallback_url) {
  std::optional<std::string> url;
  if (!fallback_url.is_empty())
    url = fallback_url.spec();

  return ArcIntentHelperMojoDelegate::IntentHandlerInfo(
      std::move(name), std::move(package_name), std::move(activity_name),
      is_preferred, std::move(url));
}

}  // namespace

// Tests that when one app is passed to GetAction but the user hasn't selected
// it and |in_out_safe_to_bypass_ui| is true, the function returns
// HANDLE_URL_IN_ARC.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOneAppBypassesIntentPicker) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", "com.google.package.name",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(GURL("external-protocol:foo"), handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests that when one app is passed to GetAction but the user hasn't selected
// it and |in_out_safe_to_bypass_ui| is false, the function returns
// ASK_USER.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOneAppDoesntBypassIntentPicker) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", "com.google.package.name",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(GURL("external-protocol:foo"), handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests that when 2+ apps are passed to GetAction but the user hasn't selected
// any the function returns ASK_USER, independently of whether or not is marked
// as safe to bypass the ui.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoAppWontBypassIntentPicker) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", "com.google.package.name",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));
  handlers.push_back(Create("package2", "com.google.package.name2",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(GURL("external-protocol:foo"), handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(GURL("external-protocol:foo"), handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests that when one preferred app is passed to GetAction, the function
// returns HANDLE_URL_IN_ARC even if the user hasn't selected the app, safe to
// bypass the UI is not relevant for this context.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithOnePreferredApp) {
  const GURL external_url("external-protocol:foo");
  const std::string package_name("com.google.package.name");
  const std::string activity_name("com.google.activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", package_name, activity_name,
                            /*is_preferred=*/true,
                            /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();

  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  // The flag was flipped since we have a preferred app.
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
}

// Tests that when one app is passed to GetAction, the user has already selected
// it, the function returns HANDLE_URL_IN_ARC. Since the user already selected
// safe to bypass ui it's always false.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithOneAppSelected) {
  const GURL external_url("external-protocol:foo");
  const std::string package_name("com.google.package.name");
  const std::string activity_name("fake_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", package_name, activity_name,
                            /*is_preferred=*/false,
                            /*fallback_url=*/GURL()));

  constexpr size_t kSelection = 0;
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_FALSE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests the same as TestGetActionWithOnePreferredApp but with two apps.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOnePreferredAppAndOneOther) {
  const GURL external_url("external-protocol:foo");
  const std::string package_name("com.google.package2.name");
  const std::string activity_name("fake_activity_name2");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("package", "com.google.package.name",
                            "fake_activity_name",
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));
  handlers.push_back(Create("package2", package_name, activity_name,
                            /*is_preferred=*/true,
                            /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  // For cases with 2+ apps it doesn't matter whether it was marked as safe to
  // bypass or not, it will only check for user's preferrences.
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  // It is expected to correct the flag to true, regardless of the initial
  // value, since there is a preferred app.
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(external_url, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(external_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests that HANDLE_URL_IN_ARC is returned for geo: URL. The URL is special in
// that intent_helper (i.e. the Chrome proxy) can handle it but Chrome cannot.
// We have to send such a URL to intent_helper to let the helper rewrite the
// URL to https://maps.google.com/?latlon=xxx which Chrome can handle. Since the
// url needs to be fixed in ARC first, safe to bypass doesn't modify this
// behavior.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithGeoUrl) {
  const GURL geo_url("geo:37.7749,-122.4194");

  const std::string activity_name("chrome_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            activity_name,
                            /*is_preferred=*/true,
                            /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(geo_url, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(geo_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  // Value will be corrected as in previous scenarios.
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests that OPEN_URL_IN_CHROME is returned when a handler with a fallback http
// URL and kArcIntentHelperPackageName is passed to GetAction, even if the
// handler is not a preferred one.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithOneFallbackUrl) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");
  const std::string activity_name("fake_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            activity_name,
                            /*is_preferred=*/false, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();

  // Since the navigation is intended to stay in Chrome the UI is bypassed.
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests the same with https and is_preferred == true.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithOnePreferredFallbackUrl) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=https://zxing.org;end");
  const GURL fallback_url("https://zxing.org");
  const std::string activity_name("fake_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            activity_name,
                            /*is_preferred=*/true, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();

  // Safe to bypass should be marked as true in the end, since the
  // OPEN_URL_IN_CHROME actually bypasses the UI, regardless of the flag.
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  // Changing the flag will not modify the outcome.
  in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests that ASK_USER is returned when two handlers with fallback URLs are
// passed to GetAction. This may happen when the user has installed a 3rd party
// browser app, and then clicks a intent: URI with a http fallback.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithTwoFallbackUrls) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other browser", "com.other.browser",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests the same but set Chrome as a preferred app. In this case, ASK_USER
// shouldn't be returned.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoFallbackUrlsChromePreferred) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");
  const std::string chrome_activity("chrome_activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other browser", "com.other.browser",
                            "fake_activity",
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            chrome_activity,
                            /*is_preferred=*/true, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(chrome_activity, url_and_activity_name.second.activity_name);
  // Remember that this flag gets fixed under the presence of a preferred app.
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests the same but set "other browser" as a preferred app. In this case,
// ASK_USER shouldn't be returned either.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoFallbackUrlsOtherBrowserPreferred) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");
  const std::string package_name = "com.other.browser";
  const std::string chrome_activity_name("chrome_activity_name");
  const std::string other_activity_name("other_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other browser", package_name, other_activity_name,
                            /*is_preferred=*/true, fallback_url));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            chrome_activity_name,
                            /*is_preferred=*/false, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(other_activity_name, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests the same but set Chrome as a user-selected app.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoFallbackUrlsChromeSelected) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");
  const std::string chrome_activity_name("chrome_activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other browser", "com.other.browser",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            chrome_activity_name,
                            /*is_preferred=*/false, fallback_url));

  constexpr size_t kSelection = 1;  // Chrome
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::OPEN_URL_IN_CHROME,
      GetActionForTesting(intent_url_with_fallback, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(chrome_activity_name, url_and_activity_name.second.activity_name);
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests the same but set "other browser" as a preferred app.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoFallbackUrlsOtherBrowserSelected) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=http://zxing.org;end");
  const GURL fallback_url("http://zxing.org");
  const std::string package_name = "com.other.browser";
  const std::string other_activity_name("other_activity_name");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other browser", package_name, other_activity_name,
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            "chrome_activity",
                            /*is_preferred=*/false, fallback_url));

  constexpr size_t kSelection = 0;  // the other browser
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  // Already selected app index, output should be corrected to false.
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(other_activity_name, url_and_activity_name.second.activity_name);
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests that HANDLE_URL_IN_ARC is returned when a handler with a fallback
// market: URL is passed to GetAction iff the flag to bypass the UI is set,
// otherwise UI will prompt to ASK_USER.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithOneMarketFallbackUrl) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Play Store", "com.google.play.store",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests the same but with is_preferred == true.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOnePreferredMarketFallbackUrl) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");
  const std::string play_store_package_name = "com.google.play.store";
  const std::string play_store_activity("play_store_activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Play Store", play_store_package_name,
                            play_store_activity,
                            /*is_preferred=*/true, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(play_store_package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(play_store_activity, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);

  in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(play_store_package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(play_store_activity, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests the same but with an app_seleteced_index.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOneSelectedMarketFallbackUrl) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");
  const std::string play_store_package_name = "com.google.play.store";
  const std::string play_store_activity("play_store_activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Play Store", play_store_package_name,
                            play_store_activity,
                            /*is_preferred=*/false, fallback_url));

  constexpr size_t kSelection = 0;
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  // App already selected, it doesn't really makes sense to call GetAction with
  // |in_out_safe_to_bypass_ui| set to true here.
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(play_store_package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(play_store_activity, url_and_activity_name.second.activity_name);
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests that HANDLE_URL_IN_ARC is returned when a handler with a fallback
// market: URL is passed to GetAction.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithOneMarketFallbackUrlBypassIntentPicker) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Play Store", "com.google.play.store",
                            "play_store_activity", /*is_preferred=*/false,
                            fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = true;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests that ASK_USER is returned when two handlers with fallback market: URLs
// are passed to GetAction. Unlike the two browsers case, this rarely happens on
// the user's device, though.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithTwoMarketFallbackUrls) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Play Store", "com.google.play.store",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Other Store app", "com.other.play.store",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::ASK_USER,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_FALSE(in_out_safe_to_bypass_ui);
}

// Tests the same, but make the second handler a preferred one.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoMarketFallbackUrlsOnePreferred) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");
  const std::string play_store_package_name = "com.google.play.store";
  const std::string play_store_activity("play.store.act1");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other Store app", "com.other.play.store",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Play Store", play_store_package_name,
                            play_store_activity,
                            /*is_preferred=*/true, fallback_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(play_store_package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(play_store_activity, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Tests the same, but make the second handler a selected one.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithTwoMarketFallbackUrlsOneSelected) {
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;end");
  const GURL fallback_url("market://details?id=com.google.abc");
  const std::string play_store_package_name = "com.google.play.store";
  const std::string play_store_activity("play.store.act1");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Other Store app", "com.other.play.store",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, fallback_url));
  handlers.push_back(Create("Play Store", play_store_package_name,
                            play_store_activity,
                            /*is_preferred=*/false, fallback_url));

  const size_t kSelection = 1;  // Play Store
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  // After selection doesn't really makes sense to check this value.
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, kSelection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(fallback_url, url_and_activity_name.first);
  EXPECT_EQ(play_store_package_name, url_and_activity_name.second.package_name);
  EXPECT_EQ(play_store_activity, url_and_activity_name.second.activity_name);
}

// Tests the case where geo: URL is returned as a fallback. This should never
// happen because intent_helper ignores such a fallback, but just in case.
// GetAction shouldn't crash at least.
TEST(ArcExternalProtocolDialogTest, TestGetActionWithGeoUrlAsFallback) {
  // Note: geo: as a browser fallback is banned in the production code.
  const GURL intent_url_with_fallback(
      "intent://scan/#Intent;scheme=abc;package=com.google.abc;"
      "S.browser_fallback_url=geo:37.7749,-122.4194;end");
  const GURL geo_url("geo:37.7749,-122.4194");
  const std::string chrome_activity("chrome.activity");

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            chrome_activity,
                            /*is_preferred=*/true, geo_url));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  // GetAction shouldn't return OPEN_URL_IN_CHROME because Chrome doesn't
  // directly support geo:.
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(intent_url_with_fallback, handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_EQ(geo_url, url_and_activity_name.first);
  EXPECT_EQ(kArcIntentHelperPackageName,
            url_and_activity_name.second.package_name);
  EXPECT_EQ(chrome_activity, url_and_activity_name.second.activity_name);
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

// Test that GetUrlToNavigateOnDeactivate returns an empty GURL when |handlers|
// is empty.
TEST(ArcExternalProtocolDialogTest, TestGetUrlToNavigateOnDeactivateEmpty) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  EXPECT_EQ(GURL(), GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Test that GetUrlToNavigateOnDeactivate returns an empty GURL when |handlers|
// only contains a (non-Chrome) app.
TEST(ArcExternalProtocolDialogTest, TestGetUrlToNavigateOnDeactivateAppOnly) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  // On production, when |handlers| only contains app(s), the fallback field is
  // empty, but to make the test more reliable, use non-empty fallback URL.
  handlers.push_back(Create("App", "app.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www")));
  EXPECT_EQ(GURL(), GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Test that GetUrlToNavigateOnDeactivate returns an empty GURL when |handlers|
// only contains (non-Chrome) apps.
TEST(ArcExternalProtocolDialogTest, TestGetUrlToNavigateOnDeactivateAppsOnly) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  // On production, when |handlers| only contains app(s), the fallback field is
  // empty, but to make the test more reliable, use non-empty fallback URL.
  handlers.push_back(Create("App1", "app1.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www")));
  handlers.push_back(Create("App2", "app2.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www")));
  EXPECT_EQ(GURL(), GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Test that GetUrlToNavigateOnDeactivate returns an empty GURL when |handlers|
// contains Chrome, but it's not for http(s).
TEST(ArcExternalProtocolDialogTest, TestGetUrlToNavigateOnDeactivateGeoUrl) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create(
      "Chrome", kArcIntentHelperPackageName, /*activity_name=*/std::string(),
      /*is_preferred=*/false, GURL("geo:37.4220,-122.0840")));
  EXPECT_EQ(GURL(), GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Test that GetUrlToNavigateOnDeactivate returns non-empty GURL when |handlers|
// contains Chrome and an app.
TEST(ArcExternalProtocolDialogTest,
     TestGetUrlToNavigateOnDeactivateChromeAndApp) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  // On production, all handlers have the same fallback URL, but to make sure
  // that "Chrome" is actually selected by the function, use different URLs.
  handlers.push_back(Create("A browser app", "browser.app.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www1/")));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www2/")));
  handlers.push_back(Create("Yet another browser app",
                            "yet.another.browser.app.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("http://www3/")));

  EXPECT_EQ(GURL("http://www2/"),
            GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Does the same with https, just in case.
TEST(ArcExternalProtocolDialogTest,
     TestGetUrlToNavigateOnDeactivateChromeAndAppHttps) {
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("A browser app", "browser.app.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("https://www1/")));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("https://www2/")));
  handlers.push_back(Create("Yet another browser app",
                            "yet.another.browser.app.package",
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("https://www3/")));

  EXPECT_EQ(GURL("https://www2/"),
            GetUrlToNavigateOnDeactivateForTesting(handlers));
}

// Checks that the flag is correctly attached to the current tab.
TEST_F(ArcExternalProtocolDialogTestUtils, TestTabIsStartedFromArc) {
  CreateTab(/*started_from_arc=*/true);

  EXPECT_TRUE(WasTabStartedFromArc());
}

// Tests the same as the previous, just for when the data is not attached to the
// tab.
TEST_F(ArcExternalProtocolDialogTestUtils, TestTabIsNotStartedFromArc) {
  CreateTab(/*started_from_arc=*/false);

  EXPECT_FALSE(WasTabStartedFromArc());
}

// Tests that IsChromeAnAppCandidate works as intended.
TEST(ArcExternalProtocolDialogTest, TestIsChromeAnAppCandidate) {
  // First 3 cases are valid, just switching the position of Chrome.
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(
      Create("fake app 1", "fake.app.package", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.fo.com")));
  handlers.push_back(
      Create("fake app 2", "fake.app.package2", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.bar.com")));
  handlers.push_back(Create("Chrome", kArcIntentHelperPackageName,
                            /*activity_name=*/std::string(),
                            /*is_preferred=*/false, GURL("https://www/")));
  EXPECT_TRUE(IsChromeAnAppCandidateForTesting(handlers));

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers2;
  handlers2.push_back(
      Create("fake app 1", "fake.app.package", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.fo.com")));
  handlers2.push_back(Create("Chrome", kArcIntentHelperPackageName,
                             /*activity_name=*/std::string(),
                             /*is_preferred=*/false, GURL("https://www/")));
  handlers2.push_back(
      Create("fake app 2", "fake.app.package2", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.bar.com")));
  EXPECT_TRUE(IsChromeAnAppCandidateForTesting(handlers2));

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers3;
  handlers3.push_back(Create("Chrome", kArcIntentHelperPackageName,
                             /*activity_name=*/std::string(),
                             /*is_preferred=*/false, GURL("https://www/")));
  handlers3.push_back(
      Create("fake app 1", "fake.app.package", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.fo.com")));
  handlers3.push_back(
      Create("fake app 2", "fake.app.package2", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.bar.com")));
  EXPECT_TRUE(IsChromeAnAppCandidateForTesting(handlers3));

  // Only non-Chrome apps.
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers4;
  handlers4.push_back(
      Create("fake app 1", "fake.app.package", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.fo.com")));
  handlers4.push_back(
      Create("fake app 2", "fake.app.package2", /*activity_name=*/std::string(),
             /*is_preferred=*/false, GURL("https://www.bar.com")));
  handlers4.push_back(Create("fake app 3", "fake.app.package3",
                             /*activity_name=*/std::string(),
                             /*is_preferred=*/false, GURL("https://www/")));
  EXPECT_FALSE(IsChromeAnAppCandidateForTesting(handlers4));

  // Empty vector case.
  EXPECT_FALSE(IsChromeAnAppCandidateForTesting(
      std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo>()));
}

// Tests that when one app is passed to GetAction and it's for ARC IME, the
// picker won't be triggered.
TEST(ArcExternalProtocolDialogTest,
     TestGetActionWithArcImeSettingsActivityBypassesIntentPicker) {
  constexpr char kPackageForOpeningArcImeSettingsPage[] =
      "org.chromium.arc.applauncher";
  constexpr char kActivityForOpeningArcImeSettingsPage[] =
      "org.chromium.arc.applauncher.InputMethodSettingsActivity";

  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  handlers.push_back(Create("ARC IME settings",
                            kPackageForOpeningArcImeSettingsPage,
                            kActivityForOpeningArcImeSettingsPage,
                            /*is_preferred=*/false, /*fallback_url=*/GURL()));

  const size_t no_selection = handlers.size();
  GurlAndActivityInfo url_and_activity_name = CreateEmptyGurlAndActivityInfo();
  bool in_out_safe_to_bypass_ui = false;
  EXPECT_EQ(
      GetActionResult::HANDLE_URL_IN_ARC,
      GetActionForTesting(GURL("intent:foo"), handlers, no_selection,
                          &url_and_activity_name, &in_out_safe_to_bypass_ui));
  EXPECT_TRUE(in_out_safe_to_bypass_ui);
}

MATCHER_P(ProtoEquals, message, "") {
  std::string expected_serialized, actual_serialized;
  message.SerializeToString(&expected_serialized);
  arg.SerializeToString(&actual_serialized);
  return expected_serialized == actual_serialized;
}

// Tests that clicking on a device calls through to SharingService.
TEST_F(ArcExternalProtocolDialogTestUtils, TestSelectDeviceForTelLink) {
  CreateTab(/*started_from_arc=*/false);

  std::string device_guid = "device_guid";
  MockSharingService* sharing_service = CreateSharingService();
  std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo> handlers;
  std::vector<SharingTargetDeviceInfo> devices;
  devices.push_back(CreateFakeSharingTargetDeviceInfo(device_guid));

  GURL phone_number("tel:073%2099%209999%2099");

  components_sharing_message::SharingMessage sharing_message;
  sharing_message.mutable_click_to_call_message()->set_phone_number(
      phone_number.GetContent());
  EXPECT_CALL(*sharing_service,
              SendMessageToDevice(
                  Property(&SharingTargetDeviceInfo::guid, device_guid),
                  testing::_, ProtoEquals(sharing_message), testing::_));

  OnIntentPickerClosedForTesting(
      web_contents()->GetWeakPtr(), phone_number,
      /*safe_to_bypass_ui=*/true, std::move(handlers),
      std::make_unique<FakeArcIntentHelperMojo>(), std::move(devices),
      /*selected_app_package=*/device_guid, apps::PickerEntryType::kDevice,
      apps::IntentPickerCloseReason::OPEN_APP, /*should_persist=*/false);
}

TEST_F(ArcExternalProtocolDialogTestUtils, TestDialogWithoutAppsWithDevices) {
  CreateTab(/*started_from_arc=*/false);

  MockSharingService* sharing_service = CreateSharingService();
  std::vector<SharingTargetDeviceInfo> devices;
  devices.push_back(CreateFakeSharingTargetDeviceInfo("device_guid"));

  EXPECT_CALL(*sharing_service, GetDeviceCandidates(testing::_))
      .WillOnce(testing::Return(testing::ByMove(std::move(devices))));

  base::RunLoop run_loop;
  ClickToCallUiController::GetOrCreateFromWebContents(web_contents())
      ->set_on_dialog_shown_closure_for_testing(run_loop.QuitClosure());

  bool handled = false;
  RunArcExternalProtocolDialog(
      GURL("tel:12341234"), /*initiating_origin=*/std::nullopt,
      web_contents()->GetWeakPtr(), ui::PAGE_TRANSITION_LINK,
      /*has_user_gesture=*/true, /*is_in_fenced_frame_tree=*/false,
      std::make_unique<FakeArcIntentHelperMojo>(),
      base::BindOnce([](bool* handled, bool result) { *handled = result; },
                     &handled));

  // Wait until the bubble is visible.
  run_loop.Run();
  EXPECT_TRUE(handled);
}

TEST_F(ArcExternalProtocolDialogTestUtils,
       TestDialogWithoutAppsWithDevicesInFencedFrameWithGesture) {
  CreateTab(/*started_from_arc=*/false);

  MockSharingService* sharing_service = CreateSharingService();
  std::vector<SharingTargetDeviceInfo> devices;
  devices.push_back(CreateFakeSharingTargetDeviceInfo("device_guid"));

  EXPECT_CALL(*sharing_service, GetDeviceCandidates(testing::_))
      .WillOnce(testing::Return(testing::ByMove(std::move(devices))));

  base::RunLoop run_loop;
  ClickToCallUiController::GetOrCreateFromWebContents(web_contents())
      ->set_on_dialog_shown_closure_for_testing(run_loop.QuitClosure());

  bool handled = false;
  RunArcExternalProtocolDialog(
      GURL("tel:12341234"), /*initiating_origin=*/std::nullopt,
      web_contents()->GetWeakPtr(), ui::PAGE_TRANSITION_AUTO_SUBFRAME,
      /*has_user_gesture=*/true, /*is_in_fenced_frame_tree=*/true,
      std::make_unique<FakeArcIntentHelperMojo>(),
      base::BindOnce([](bool* handled, bool result) { *handled = result; },
                     &handled));

  // Wait until the bubble is visible.
  run_loop.Run();
  EXPECT_TRUE(handled);
}

TEST_F(ArcExternalProtocolDialogTestUtils,
       TestDialogWithoutAppsWithDevicesInFencedFrameWithoutGesture) {
  CreateTab(/*started_from_arc=*/false);

  MockSharingService* sharing_service = CreateSharingService();
  EXPECT_CALL(*sharing_service, GetDeviceCandidates).Times(0);

  base::RunLoop run_loop;
  ClickToCallUiController::GetOrCreateFromWebContents(web_contents())
      ->set_on_dialog_shown_closure_for_testing(run_loop.QuitClosure());

  std::optional<bool> handled;
  RunArcExternalProtocolDialog(
      GURL("tel:12341234"), /*initiating_origin=*/std::nullopt,
      web_contents()->GetWeakPtr(), ui::PAGE_TRANSITION_AUTO_SUBFRAME,
      /*has_user_gesture=*/false, /*is_in_fenced_frame_tree=*/true,
      std::make_unique<FakeArcIntentHelperMojo>(),
      base::BindOnce(
          [](std::optional<bool>* handled, bool result) { *handled = result; },
          &handled));
  EXPECT_TRUE(handled.has_value());
  EXPECT_FALSE(*handled);
}

}  // namespace arc