WebOTP: User Consent API behind flag

This change adds an implementation of the WebOTP API using the GMS Core
User Consent API [1] that can be enabled behind a flag.

[1]https://developers.google.com/identity/sms-retriever/user-consent/overview

Bug: 1060233
Change-Id: I31c849dfbc43258746121f1dd893292038371cae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2103067
Commit-Queue: Ayu Ishii <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Reviewed-by: Jinsuk Kim <[email protected]>
Reviewed-by: Victor Costan <[email protected]>
Cr-Commit-Position: refs/heads/master@{#752230}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3ebee77..b34aeb4a4 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1661,6 +1661,16 @@
 };
 #endif  // defined(OS_CHROMEOS)
 
+#if defined(OS_ANDROID)
+const FeatureEntry::Choice kWebOtpBackendChoices[] = {
+    {flags_ui::kGenericExperimentChoiceDefault, "", ""},
+    {flag_descriptions::kWebOtpBackendSmsVerification, switches::kWebOtpBackend,
+     switches::kWebOtpBackendSmsVerification},
+    {flag_descriptions::kWebOtpBackendUserConsent, switches::kWebOtpBackend,
+     switches::kWebOtpBackendUserConsent},
+};
+#endif  // defined(OS_ANDROID)
+
 // RECORDING USER METRICS FOR FLAGS:
 // -----------------------------------------------------------------------------
 // The first line of the entry is the internal name.
@@ -4722,6 +4732,10 @@
      FEATURE_VALUE_TYPE(features::kWebBundles)},
 
 #if defined(OS_ANDROID)
+    {"web-otp-backend", flag_descriptions::kWebOtpBackendName,
+     flag_descriptions::kWebOtpBackendDescription, kOsAndroid,
+     MULTI_VALUE_TYPE(kWebOtpBackendChoices)},
+
     {"darken-websites-checkbox-in-themes-setting",
      flag_descriptions::kDarkenWebsitesCheckboxInThemesSettingName,
      flag_descriptions::kDarkenWebsitesCheckboxInThemesSettingDescription,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 9a46241a..8c05966b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4053,6 +4053,11 @@
     "expiry_milestone": 84
   },
   {
+    "name": "web-otp-backend",
+    "owners": [ "ayui", "goto" ],
+    "expiry_milestone": 86
+  },
+  {
     "name": "webpage-text-accessibility",
     "owners": [ "rkgibson" ],
     "expiry_milestone": 86
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 869fcc89..8342320 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2066,6 +2066,13 @@
     "Enables experimental supports for Web Bundles (Bundled HTTP Exchanges) "
     "navigation.";
 
+const char kWebOtpBackendName[] = "Web OTP";
+const char kWebOtpBackendDescription[] =
+    "Enables Web OTP API that uses the specified backend.";
+const char kWebOtpBackendSmsVerification[] =
+    "SMS Verification API (requires app-hash)";
+const char kWebOtpBackendUserConsent[] = "User Consent API";
+
 const char kWebglDraftExtensionsName[] = "WebGL Draft Extensions";
 const char kWebglDraftExtensionsDescription[] =
     "Enabling this option allows web applications to access the WebGL "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index bd364baa..f497d61 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1186,6 +1186,11 @@
 extern const char kWebBundlesName[];
 extern const char kWebBundlesDescription[];
 
+extern const char kWebOtpBackendName[];
+extern const char kWebOtpBackendDescription[];
+extern const char kWebOtpBackendSmsVerification[];
+extern const char kWebOtpBackendUserConsent[];
+
 extern const char kWebglDraftExtensionsName[];
 extern const char kWebglDraftExtensionsDescription[];
 
diff --git a/chrome/browser/sharing/sms/sms_fetch_request_handler_unittest.cc b/chrome/browser/sharing/sms/sms_fetch_request_handler_unittest.cc
index af25ea2..5dae3dd 100644
--- a/chrome/browser/sharing/sms/sms_fetch_request_handler_unittest.cc
+++ b/chrome/browser/sharing/sms/sms_fetch_request_handler_unittest.cc
@@ -36,6 +36,7 @@
   MOCK_METHOD2(Unsubscribe,
                void(const url::Origin& origin, Subscriber* subscriber));
   MOCK_METHOD0(HasSubscribers, bool());
+  MOCK_METHOD0(CanReceiveSms, bool());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockSmsFetcher);
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 9505df0..f097105 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2405,8 +2405,10 @@
       "renderer_host/render_widget_host_view_android.h",
       "screen_orientation/screen_orientation_delegate_android.cc",
       "screen_orientation/screen_orientation_delegate_android.h",
-      "sms/sms_provider_android.cc",
-      "sms/sms_provider_android.h",
+      "sms/sms_provider_gms_user_consent.cc",
+      "sms/sms_provider_gms_user_consent.h",
+      "sms/sms_provider_gms_verification.cc",
+      "sms/sms_provider_gms_verification.h",
       "web_contents/web_contents_android.cc",
       "web_contents/web_contents_android.h",
       "web_contents/web_contents_view_android.cc",
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 9c78583..63127af 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -6909,7 +6909,7 @@
     mojo::ReportBadMessage("Must have the same origin as the top-level frame.");
     return;
   }
-  auto* fetcher = SmsFetcher::Get(GetProcess()->GetBrowserContext());
+  auto* fetcher = SmsFetcher::Get(GetProcess()->GetBrowserContext(), this);
   SmsService::Create(fetcher, this, std::move(receiver));
 }
 
diff --git a/content/browser/sms/sms_fetcher_impl.cc b/content/browser/sms/sms_fetcher_impl.cc
index a286afb..96e75cd 100644
--- a/content/browser/sms/sms_fetcher_impl.cc
+++ b/content/browser/sms/sms_fetcher_impl.cc
@@ -30,8 +30,7 @@
 // static
 SmsFetcher* SmsFetcher::Get(BrowserContext* context) {
   if (!context->GetUserData(kSmsFetcherImplKeyName)) {
-    auto fetcher =
-        std::make_unique<SmsFetcherImpl>(context, SmsProvider::Create());
+    auto fetcher = std::make_unique<SmsFetcherImpl>(context, nullptr);
     context->SetUserData(kSmsFetcherImplKeyName, std::move(fetcher));
   }
 
@@ -39,6 +38,18 @@
       context->GetUserData(kSmsFetcherImplKeyName));
 }
 
+SmsFetcher* SmsFetcher::Get(BrowserContext* context, RenderFrameHost* rfh) {
+  auto* stored_fetcher = static_cast<SmsFetcherImpl*>(
+      context->GetUserData(kSmsFetcherImplKeyName));
+  if (!stored_fetcher || !stored_fetcher->CanReceiveSms()) {
+    auto fetcher =
+        std::make_unique<SmsFetcherImpl>(context, SmsProvider::Create(rfh));
+    context->SetUserData(kSmsFetcherImplKeyName, std::move(fetcher));
+  }
+  return static_cast<SmsFetcherImpl*>(
+      context->GetUserData(kSmsFetcherImplKeyName));
+}
+
 void SmsFetcherImpl::Subscribe(const url::Origin& origin,
                                SmsQueue::Subscriber* subscriber) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -102,6 +113,10 @@
   return subscribers_.HasSubscribers();
 }
 
+bool SmsFetcherImpl::CanReceiveSms() {
+  return provider_ != nullptr;
+}
+
 void SmsFetcherImpl::SetSmsProviderForTesting(
     std::unique_ptr<SmsProvider> provider) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/content/browser/sms/sms_fetcher_impl.h b/content/browser/sms/sms_fetcher_impl.h
index a707703..1f47f0a3 100644
--- a/content/browser/sms/sms_fetcher_impl.h
+++ b/content/browser/sms/sms_fetcher_impl.h
@@ -33,8 +33,6 @@
                  std::unique_ptr<SmsProvider> provider);
   ~SmsFetcherImpl() override;
 
-  static SmsFetcher* Get(BrowserContext* context);
-
   void Subscribe(const url::Origin& origin, Subscriber* subscriber) override;
   void Unsubscribe(const url::Origin& origin, Subscriber* subscriber) override;
 
@@ -43,6 +41,7 @@
                  const std::string& one_time_code) override;
 
   bool HasSubscribers() override;
+  bool CanReceiveSms() override;
 
   void SetSmsProviderForTesting(std::unique_ptr<SmsProvider> provider);
 
diff --git a/content/browser/sms/sms_provider.cc b/content/browser/sms/sms_provider.cc
index 9ff6c0cd..4113e73dd 100644
--- a/content/browser/sms/sms_provider.cc
+++ b/content/browser/sms/sms_provider.cc
@@ -4,24 +4,33 @@
 
 #include <memory>
 
+#include "base/command_line.h"
+
 #include "build/build_config.h"
 #include "content/browser/sms/sms_provider.h"
+#include "content/public/common/content_switches.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 #if defined(OS_ANDROID)
-#include "content/browser/sms/sms_provider_android.h"
+#include "content/browser/sms/sms_provider_gms_user_consent.h"
+#include "content/browser/sms/sms_provider_gms_verification.h"
 #endif
 
 namespace content {
 
-SmsProvider::SmsProvider() = default;
+class RenderFrameHost;
 
+SmsProvider::SmsProvider() = default;
 SmsProvider::~SmsProvider() = default;
 
 // static
-std::unique_ptr<SmsProvider> SmsProvider::Create() {
+std::unique_ptr<SmsProvider> SmsProvider::Create(RenderFrameHost* rfh) {
 #if defined(OS_ANDROID)
-  return std::make_unique<SmsProviderAndroid>();
+  if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kWebOtpBackend) == switches::kWebOtpBackendUserConsent) {
+    return std::make_unique<SmsProviderGmsUserConsent>(rfh);
+  }
+  return std::make_unique<SmsProviderGmsVerification>();
 #else
   return nullptr;
 #endif
diff --git a/content/browser/sms/sms_provider.h b/content/browser/sms/sms_provider.h
index 9ef14b0..1f19c7bb 100644
--- a/content/browser/sms/sms_provider.h
+++ b/content/browser/sms/sms_provider.h
@@ -19,6 +19,8 @@
 
 namespace content {
 
+class RenderFrameHost;
+
 // This class wraps the platform-specific functions and allows tests to
 // inject custom providers.
 class CONTENT_EXPORT SmsProvider {
@@ -38,7 +40,7 @@
   // it is received or (exclusively) when it timeouts.
   virtual void Retrieve() = 0;
 
-  static std::unique_ptr<SmsProvider> Create();
+  static std::unique_ptr<SmsProvider> Create(RenderFrameHost* rfh);
 
   void AddObserver(Observer*);
   void RemoveObserver(const Observer*);
diff --git a/content/browser/sms/sms_provider_android.cc b/content/browser/sms/sms_provider_android.cc
deleted file mode 100644
index d43990af8..0000000
--- a/content/browser/sms/sms_provider_android.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2019 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 "content/browser/sms/sms_provider_android.h"
-
-#include <string>
-
-#include "base/bind.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-
-#include "content/public/android/content_jni_headers/SmsReceiver_jni.h"
-
-using base::android::AttachCurrentThread;
-using base::android::ConvertJavaStringToUTF8;
-
-namespace content {
-
-SmsProviderAndroid::SmsProviderAndroid() : SmsProvider() {
-  // This class is constructed a single time whenever the
-  // first web page uses the SMS Retriever API to wait for
-  // SMSes.
-  JNIEnv* env = AttachCurrentThread();
-  j_sms_receiver_.Reset(
-      Java_SmsReceiver_create(env, reinterpret_cast<intptr_t>(this)));
-}
-
-SmsProviderAndroid::~SmsProviderAndroid() {
-  JNIEnv* env = AttachCurrentThread();
-  Java_SmsReceiver_destroy(env, j_sms_receiver_);
-}
-
-void SmsProviderAndroid::Retrieve() {
-  JNIEnv* env = AttachCurrentThread();
-
-  Java_SmsReceiver_listen(env, j_sms_receiver_);
-}
-
-void SmsProviderAndroid::OnReceive(
-    JNIEnv* env,
-    jstring message) {
-  std::string sms = ConvertJavaStringToUTF8(env, message);
-  NotifyReceive(sms);
-}
-
-void SmsProviderAndroid::OnTimeout(JNIEnv* env) {}
-
-base::android::ScopedJavaGlobalRef<jobject>
-SmsProviderAndroid::GetSmsReceiverForTesting() const {
-  return j_sms_receiver_;
-}
-
-}  // namespace content
diff --git a/content/browser/sms/sms_provider_android_unittest.cc b/content/browser/sms/sms_provider_android_unittest.cc
deleted file mode 100644
index f8cea00..0000000
--- a/content/browser/sms/sms_provider_android_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2019 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 "content/browser/sms/sms_service.h"
-
-#include <string>
-
-#include "base/android/jni_string.h"
-#include "base/android/scoped_java_ref.h"
-#include "content/browser/sms/sms_provider.h"
-#include "content/browser/sms/sms_provider_android.h"
-#include "content/public/test/test_renderer_host.h"
-#include "content/test/content_unittests_jni_headers/Fakes_jni.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::android::AttachCurrentThread;
-using ::testing::_;
-using ::testing::NiceMock;
-using url::Origin;
-
-namespace content {
-
-namespace {
-
-class MockObserver : public SmsProvider::Observer {
- public:
-  MockObserver() = default;
-  ~MockObserver() override = default;
-
-  MOCK_METHOD2(OnReceive,
-               bool(const Origin&, const std::string& one_time_code));
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockObserver);
-};
-
-// SmsProviderAndroidTest tests the JNI bindings to the android SmsReceiver
-// and the handling of the SMS upon retrieval.
-class SmsProviderAndroidTest : public RenderViewHostTestHarness {
- protected:
-  SmsProviderAndroidTest() {}
-  ~SmsProviderAndroidTest() override {}
-
-  void SetUp() override {
-    RenderViewHostTestHarness::SetUp();
-    j_fake_sms_retriever_client_.Reset(
-        Java_FakeSmsRetrieverClient_create(AttachCurrentThread()));
-    Java_Fakes_setClientForTesting(AttachCurrentThread(),
-                                   provider_.GetSmsReceiverForTesting(),
-                                   j_fake_sms_retriever_client_);
-    provider_.AddObserver(&observer_);
-  }
-
-  void TriggerSms(const std::string& sms) {
-    JNIEnv* env = base::android::AttachCurrentThread();
-    Java_FakeSmsRetrieverClient_triggerSms(
-        env, j_fake_sms_retriever_client_,
-        base::android::ConvertUTF8ToJavaString(env, sms));
-  }
-
-  void TriggerTimeout() {
-    JNIEnv* env = base::android::AttachCurrentThread();
-    Java_FakeSmsRetrieverClient_triggerTimeout(env,
-                                               j_fake_sms_retriever_client_);
-  }
-
-  SmsProviderAndroid& provider() { return provider_; }
-
-  NiceMock<MockObserver>* observer() { return &observer_; }
-
- private:
-  SmsProviderAndroid provider_;
-  NiceMock<MockObserver> observer_;
-  base::android::ScopedJavaGlobalRef<jobject> j_fake_sms_retriever_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(SmsProviderAndroidTest);
-};
-
-}  // namespace
-
-TEST_F(SmsProviderAndroidTest, Retrieve) {
-  std::string test_url = "https://google.com";
-
-  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
-  provider().Retrieve();
-  TriggerSms("Hi\[email protected] #ABC123");
-}
-
-TEST_F(SmsProviderAndroidTest, IgnoreBadSms) {
-  std::string test_url = "https://google.com";
-  std::string good_sms = "Hi\[email protected] #ABC123";
-  std::string bad_sms = "Hi\[email protected]";
-
-  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
-
-  provider().Retrieve();
-  TriggerSms(bad_sms);
-  TriggerSms(good_sms);
-}
-
-TEST_F(SmsProviderAndroidTest, TaskTimedOut) {
-  EXPECT_CALL(*observer(), OnReceive(_, _)).Times(0);
-  provider().Retrieve();
-  TriggerTimeout();
-}
-
-TEST_F(SmsProviderAndroidTest, OneObserverTwoTasks) {
-  std::string test_url = "https://google.com";
-
-  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
-
-  // Two tasks for when 1 request gets aborted but the task is still triggered.
-  provider().Retrieve();
-  provider().Retrieve();
-
-  // First timeout should be ignored.
-  TriggerTimeout();
-  TriggerSms("Hi\[email protected] #ABC123");
-}
-
-}  // namespace content
diff --git a/content/browser/sms/sms_provider_gms_user_consent.cc b/content/browser/sms/sms_provider_gms_user_consent.cc
new file mode 100644
index 0000000..7ca74fd7
--- /dev/null
+++ b/content/browser/sms/sms_provider_gms_user_consent.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 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 "content/browser/sms/sms_provider_gms_user_consent.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+#include "content/public/android/content_jni_headers/SmsUserConsentReceiver_jni.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/android/window_android.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+
+namespace content {
+
+SmsProviderGmsUserConsent::SmsProviderGmsUserConsent(RenderFrameHost* rfh)
+    : SmsProvider(), render_frame_host_(rfh) {
+  // This class is constructed a single time whenever the
+  // first web page uses the SMS Retriever API to wait for
+  // SMSes.
+  JNIEnv* env = AttachCurrentThread();
+  j_sms_receiver_.Reset(Java_SmsUserConsentReceiver_create(
+      env, reinterpret_cast<intptr_t>(this)));
+}
+
+SmsProviderGmsUserConsent::~SmsProviderGmsUserConsent() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_SmsUserConsentReceiver_destroy(env, j_sms_receiver_);
+}
+
+void SmsProviderGmsUserConsent::Retrieve() {
+  JNIEnv* env = AttachCurrentThread();
+
+  WebContents* web_contents =
+      WebContents::FromRenderFrameHost(render_frame_host_);
+  if (!web_contents || !web_contents->GetTopLevelNativeWindow())
+    return;
+
+  Java_SmsUserConsentReceiver_listen(
+      env, j_sms_receiver_,
+      web_contents->GetTopLevelNativeWindow()->GetJavaObject());
+}
+
+void SmsProviderGmsUserConsent::OnReceive(JNIEnv* env, jstring message) {
+  std::string sms = ConvertJavaStringToUTF8(env, message);
+  NotifyReceive(sms);
+}
+
+void SmsProviderGmsUserConsent::OnTimeout(JNIEnv* env) {}
+
+base::android::ScopedJavaGlobalRef<jobject>
+SmsProviderGmsUserConsent::GetSmsReceiverForTesting() const {
+  return j_sms_receiver_;
+}
+
+}  // namespace content
diff --git a/content/browser/sms/sms_provider_android.h b/content/browser/sms/sms_provider_gms_user_consent.h
similarity index 60%
copy from content/browser/sms/sms_provider_android.h
copy to content/browser/sms/sms_provider_gms_user_consent.h
index 618c68f..177a1fe 100644
--- a/content/browser/sms/sms_provider_android.h
+++ b/content/browser/sms/sms_provider_gms_user_consent.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
-#define CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+#ifndef CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_USER_CONSENT_H_
+#define CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_USER_CONSENT_H_
 
 #include <utility>
 
@@ -15,10 +15,10 @@
 
 namespace content {
 
-class CONTENT_EXPORT SmsProviderAndroid : public SmsProvider {
+class CONTENT_EXPORT SmsProviderGmsUserConsent : public SmsProvider {
  public:
-  SmsProviderAndroid();
-  ~SmsProviderAndroid() override;
+  SmsProviderGmsUserConsent(RenderFrameHost* rfh);
+  ~SmsProviderGmsUserConsent() override;
 
   void Retrieve() override;
 
@@ -30,10 +30,11 @@
 
  private:
   base::android::ScopedJavaGlobalRef<jobject> j_sms_receiver_;
+  RenderFrameHost* const render_frame_host_;
 
-  DISALLOW_COPY_AND_ASSIGN(SmsProviderAndroid);
+  DISALLOW_COPY_AND_ASSIGN(SmsProviderGmsUserConsent);
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_USER_CONSENT_H_
diff --git a/content/browser/sms/sms_provider_gms_user_consent_unittest.cc b/content/browser/sms/sms_provider_gms_user_consent_unittest.cc
new file mode 100644
index 0000000..425e263
--- /dev/null
+++ b/content/browser/sms/sms_provider_gms_user_consent_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright 2020 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 "content/browser/sms/sms_service.h"
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/sms/sms_provider.h"
+#include "content/browser/sms/sms_provider_gms_user_consent.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/test/content_unittests_jni_headers/SmsUserConsentFakes_jni.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/android/window_android.h"
+
+using base::android::AttachCurrentThread;
+using ::testing::_;
+using ::testing::NiceMock;
+using url::Origin;
+
+namespace content {
+
+namespace {
+
+class MockObserver : public SmsProvider::Observer {
+ public:
+  MockObserver() = default;
+  ~MockObserver() override = default;
+
+  MOCK_METHOD2(OnReceive,
+               bool(const Origin&, const std::string& one_time_code));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockObserver);
+};
+
+// SmsProviderGmsUserConsentTest tests the JNI bindings to the android
+// SmsUserConsentReceiver and the handling of the SMS upon retrieval.
+class SmsProviderGmsUserConsentTest : public RenderViewHostTestHarness {
+ protected:
+  SmsProviderGmsUserConsentTest() = default;
+  ~SmsProviderGmsUserConsentTest() override = default;
+
+  void SetUp() {
+    RenderViewHostTestHarness::SetUp();
+    provider_ = std::make_unique<SmsProviderGmsUserConsent>(
+        web_contents()->GetMainFrame());
+    j_fake_sms_retriever_client_.Reset(
+        Java_FakeSmsUserConsentRetrieverClient_create(AttachCurrentThread()));
+    Java_SmsUserConsentFakes_setUserConsentClientForTesting(
+        AttachCurrentThread(), provider_->GetSmsReceiverForTesting(),
+        j_fake_sms_retriever_client_,
+        ui::WindowAndroid::CreateForTesting()->GetJavaObject());
+    provider_->AddObserver(&observer_);
+  }
+
+  void TriggerUserConsentSms(const std::string& sms) {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_FakeSmsUserConsentRetrieverClient_triggerUserConsentSms(
+        env, j_fake_sms_retriever_client_,
+        base::android::ConvertUTF8ToJavaString(env, sms));
+  }
+
+  void TriggerTimeout() {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_FakeSmsUserConsentRetrieverClient_triggerTimeout(
+        env, j_fake_sms_retriever_client_);
+  }
+
+  SmsProviderGmsUserConsent* provider() { return provider_.get(); }
+
+  NiceMock<MockObserver>* observer() { return &observer_; }
+
+ private:
+  std::unique_ptr<SmsProviderGmsUserConsent> provider_;
+  NiceMock<MockObserver> observer_;
+  base::android::ScopedJavaGlobalRef<jobject> j_fake_sms_retriever_client_;
+  base::test::ScopedFeatureList feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(SmsProviderGmsUserConsentTest);
+};
+
+}  // namespace
+
+TEST_F(SmsProviderGmsUserConsentTest, Retrieve) {
+  EXPECT_CALL(*observer(),
+              OnReceive(Origin::Create(GURL("https://google.com")), "ABC123"));
+  provider()->Retrieve();
+  TriggerUserConsentSms("Hi\[email protected] #ABC123");
+}
+
+TEST_F(SmsProviderGmsUserConsentTest, IgnoreBadSms) {
+  std::string test_url = "https://google.com";
+  std::string good_sms = "Hi\[email protected] #ABC123";
+  std::string bad_sms = "Hi\[email protected]";
+
+  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
+
+  provider()->Retrieve();
+  TriggerUserConsentSms(bad_sms);
+  TriggerUserConsentSms(good_sms);
+}
+
+TEST_F(SmsProviderGmsUserConsentTest, TaskTimedOut) {
+  EXPECT_CALL(*observer(), OnReceive(_, _)).Times(0);
+  provider()->Retrieve();
+  TriggerTimeout();
+}
+
+TEST_F(SmsProviderGmsUserConsentTest, OneObserverTwoTasks) {
+  std::string test_url = "https://google.com";
+
+  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
+
+  // Two tasks for when 1 request gets aborted but the task is still triggered.
+  provider()->Retrieve();
+  provider()->Retrieve();
+
+  // First timeout should be ignored.
+  TriggerTimeout();
+  TriggerUserConsentSms("Hi\[email protected] #ABC123");
+}
+
+}  // namespace content
diff --git a/content/browser/sms/sms_provider_gms_verification.cc b/content/browser/sms/sms_provider_gms_verification.cc
new file mode 100644
index 0000000..7caecf3
--- /dev/null
+++ b/content/browser/sms/sms_provider_gms_verification.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 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 "content/browser/sms/sms_provider_gms_verification.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+#include "content/public/android/content_jni_headers/SmsVerificationReceiver_jni.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/android/window_android.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+
+namespace content {
+
+SmsProviderGmsVerification::SmsProviderGmsVerification() {
+  // This class is constructed a single time whenever the
+  // first web page uses the SMS Retriever API to wait for
+  // SMSes.
+  JNIEnv* env = AttachCurrentThread();
+  j_sms_receiver_.Reset(Java_SmsVerificationReceiver_create(
+      env, reinterpret_cast<intptr_t>(this)));
+}
+
+SmsProviderGmsVerification::~SmsProviderGmsVerification() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_SmsVerificationReceiver_destroy(env, j_sms_receiver_);
+}
+
+void SmsProviderGmsVerification::Retrieve() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_SmsVerificationReceiver_listen(env, j_sms_receiver_);
+}
+
+void SmsProviderGmsVerification::OnReceive(JNIEnv* env, jstring message) {
+  std::string sms = ConvertJavaStringToUTF8(env, message);
+  NotifyReceive(sms);
+}
+
+void SmsProviderGmsVerification::OnTimeout(JNIEnv* env) {}
+
+base::android::ScopedJavaGlobalRef<jobject>
+SmsProviderGmsVerification::GetSmsReceiverForTesting() const {
+  return j_sms_receiver_;
+}
+
+}  // namespace content
diff --git a/content/browser/sms/sms_provider_android.h b/content/browser/sms/sms_provider_gms_verification.h
similarity index 64%
rename from content/browser/sms/sms_provider_android.h
rename to content/browser/sms/sms_provider_gms_verification.h
index 618c68f..9e9958ad 100644
--- a/content/browser/sms/sms_provider_android.h
+++ b/content/browser/sms/sms_provider_gms_verification.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
-#define CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+#ifndef CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_VERIFICATION_H_
+#define CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_VERIFICATION_H_
 
 #include <utility>
 
@@ -15,10 +15,10 @@
 
 namespace content {
 
-class CONTENT_EXPORT SmsProviderAndroid : public SmsProvider {
+class CONTENT_EXPORT SmsProviderGmsVerification : public SmsProvider {
  public:
-  SmsProviderAndroid();
-  ~SmsProviderAndroid() override;
+  SmsProviderGmsVerification();
+  ~SmsProviderGmsVerification() override;
 
   void Retrieve() override;
 
@@ -31,9 +31,9 @@
  private:
   base::android::ScopedJavaGlobalRef<jobject> j_sms_receiver_;
 
-  DISALLOW_COPY_AND_ASSIGN(SmsProviderAndroid);
+  DISALLOW_COPY_AND_ASSIGN(SmsProviderGmsVerification);
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_GMS_VERIFICATION_H_
diff --git a/content/browser/sms/sms_provider_gms_verification_unittest.cc b/content/browser/sms/sms_provider_gms_verification_unittest.cc
new file mode 100644
index 0000000..6037346
--- /dev/null
+++ b/content/browser/sms/sms_provider_gms_verification_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2019 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 "content/browser/sms/sms_service.h"
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/sms/sms_provider.h"
+#include "content/browser/sms/sms_provider_gms_verification.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/test/content_unittests_jni_headers/SmsVerificationFakes_jni.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::android::AttachCurrentThread;
+using ::testing::_;
+using ::testing::NiceMock;
+using url::Origin;
+
+namespace content {
+
+namespace {
+
+class MockObserver : public SmsProvider::Observer {
+ public:
+  MockObserver() = default;
+  ~MockObserver() override = default;
+
+  MOCK_METHOD2(OnReceive,
+               bool(const Origin&, const std::string& one_time_code));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockObserver);
+};
+
+// SmsProviderGmsVerificationTest tests the JNI bindings to the android
+// SmsVerificationReceiver and the handling of the SMS upon retrieval.
+class SmsProviderGmsVerificationTest : public RenderViewHostTestHarness {
+ protected:
+  SmsProviderGmsVerificationTest() = default;
+  ~SmsProviderGmsVerificationTest() override = default;
+
+  void SetUp() {
+    RenderViewHostTestHarness::SetUp();
+    provider_ = std::make_unique<SmsProviderGmsVerification>();
+    j_fake_sms_retriever_client_.Reset(
+        Java_FakeSmsRetrieverClient_create(AttachCurrentThread()));
+    Java_SmsVerificationFakes_setClientForTesting(
+        AttachCurrentThread(), provider_->GetSmsReceiverForTesting(),
+        j_fake_sms_retriever_client_);
+    provider_->AddObserver(&observer_);
+  }
+
+  void TriggerSmsVerificationSms(const std::string& sms) {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_FakeSmsRetrieverClient_triggerSmsVerificationSms(
+        env, j_fake_sms_retriever_client_,
+        base::android::ConvertUTF8ToJavaString(env, sms));
+  }
+
+  void TriggerTimeout() {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_FakeSmsRetrieverClient_triggerTimeout(env,
+                                               j_fake_sms_retriever_client_);
+  }
+
+  SmsProviderGmsVerification* provider() { return provider_.get(); }
+
+  NiceMock<MockObserver>* observer() { return &observer_; }
+
+ private:
+  std::unique_ptr<SmsProviderGmsVerification> provider_;
+  NiceMock<MockObserver> observer_;
+  base::android::ScopedJavaGlobalRef<jobject> j_fake_sms_retriever_client_;
+  base::test::ScopedFeatureList feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(SmsProviderGmsVerificationTest);
+};
+
+}  // namespace
+
+TEST_F(SmsProviderGmsVerificationTest, Retrieve) {
+  std::string test_url = "https://google.com";
+
+  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
+  provider()->Retrieve();
+  TriggerSmsVerificationSms("Hi\[email protected] #ABC123");
+}
+
+TEST_F(SmsProviderGmsVerificationTest, IgnoreBadSms) {
+  std::string test_url = "https://google.com";
+  std::string good_sms = "Hi\[email protected] #ABC123";
+  std::string bad_sms = "Hi\[email protected]";
+
+  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
+
+  provider()->Retrieve();
+  TriggerSmsVerificationSms(bad_sms);
+  TriggerSmsVerificationSms(good_sms);
+}
+
+TEST_F(SmsProviderGmsVerificationTest, TaskTimedOut) {
+  EXPECT_CALL(*observer(), OnReceive(_, _)).Times(0);
+  provider()->Retrieve();
+  TriggerTimeout();
+}
+
+TEST_F(SmsProviderGmsVerificationTest, OneObserverTwoTasks) {
+  std::string test_url = "https://google.com";
+
+  EXPECT_CALL(*observer(), OnReceive(Origin::Create(GURL(test_url)), "ABC123"));
+
+  // Two tasks for when 1 request gets aborted but the task is still triggered.
+  provider()->Retrieve();
+  provider()->Retrieve();
+
+  // First timeout should be ignored.
+  TriggerTimeout();
+  TriggerSmsVerificationSms("Hi\[email protected] #ABC123");
+}
+
+}  // namespace content
diff --git a/content/browser/sms/sms_service.cc b/content/browser/sms/sms_service.cc
index 7084a4cb..4a8576b 100644
--- a/content/browser/sms/sms_service.cc
+++ b/content/browser/sms/sms_service.cc
@@ -11,6 +11,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/command_line.h"
 #include "base/logging.h"
 #include "base/optional.h"
 #include "content/browser/sms/sms_metrics.h"
@@ -19,6 +20,8 @@
 #include "content/public/browser/sms_fetcher.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
 
 using blink::SmsReceiverDestroyedReason;
 using blink::mojom::SmsStatus;
@@ -100,8 +103,14 @@
   RecordSmsReceiveTime(base::TimeTicks::Now() - start_time_);
 
   one_time_code_ = one_time_code;
-  receive_time_ = base::TimeTicks::Now();
 
+  if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kWebOtpBackend) == switches::kWebOtpBackendUserConsent) {
+    Process(SmsStatus::kSuccess, one_time_code_);
+    return;
+  }
+
+  receive_time_ = base::TimeTicks::Now();
   OpenInfoBar(one_time_code);
 }
 
diff --git a/content/browser/sms/sms_service_unittest.cc b/content/browser/sms/sms_service_unittest.cc
index fcbd05e..726bc143 100644
--- a/content/browser/sms/sms_service_unittest.cc
+++ b/content/browser/sms/sms_service_unittest.cc
@@ -137,8 +137,8 @@
 
 class SmsServiceTest : public RenderViewHostTestHarness {
  protected:
-  SmsServiceTest() {}
-  ~SmsServiceTest() override {}
+  SmsServiceTest() = default;
+  ~SmsServiceTest() override = default;
 
   void ExpectDestroyedReasonCount(SmsReceiverDestroyedReason bucket,
                                   int32_t count) {
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 77116a45..b679038 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -261,7 +261,8 @@
     "java/src/org/chromium/content/browser/selection/SmartSelectionClient.java",
     "java/src/org/chromium/content/browser/selection/SmartSelectionMetricsLogger.java",
     "java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java",
-    "java/src/org/chromium/content/browser/sms/SmsReceiver.java",
+    "java/src/org/chromium/content/browser/sms/SmsUserConsentReceiver.java",
+    "java/src/org/chromium/content/browser/sms/SmsVerificationReceiver.java",
     "java/src/org/chromium/content/browser/sms/Wrappers.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java",
@@ -407,7 +408,8 @@
     "java/src/org/chromium/content/browser/input/TextSuggestionHost.java",
     "java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java",
     "java/src/org/chromium/content/browser/selection/SmartSelectionClient.java",
-    "java/src/org/chromium/content/browser/sms/SmsReceiver.java",
+    "java/src/org/chromium/content/browser/sms/SmsUserConsentReceiver.java",
+    "java/src/org/chromium/content/browser/sms/SmsVerificationReceiver.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java",
     "java/src/org/chromium/content/common/ServiceManagerConnectionImpl.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java b/content/public/android/java/src/org/chromium/content/browser/sms/SmsUserConsentReceiver.java
similarity index 60%
copy from content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java
copy to content/public/android/java/src/org/chromium/content/browser/sms/SmsUserConsentReceiver.java
index 0e854e1..958ef134 100644
--- a/content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java
+++ b/content/public/android/java/src/org/chromium/content/browser/sms/SmsUserConsentReceiver.java
@@ -4,6 +4,7 @@
 
 package org.chromium.content.browser.sms;
 
+import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -22,22 +23,24 @@
 import org.chromium.base.annotations.JNIAdditionalImport;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.ui.base.WindowAndroid;
 
 /**
- * Simple proxy that provides C++ code with an access pathway to the Android
- * SMS retriever.
+ * Simple proxy that provides C++ code with a pathway to the Android SMS
+ * Retriever to access the SMS User Consent API.
  */
 @JNINamespace("content")
 @JNIAdditionalImport(Wrappers.class)
-public class SmsReceiver extends BroadcastReceiver {
-    private static final String TAG = "SmsReceiver";
+public class SmsUserConsentReceiver extends BroadcastReceiver {
+    private static final String TAG = "SmsUserConsentReceiver";
     private static final boolean DEBUG = false;
     private final long mSmsProviderAndroid;
     private boolean mDestroyed;
     private Wrappers.SmsRetrieverClientWrapper mClient;
     private Wrappers.SmsReceiverContext mContext;
+    private WindowAndroid mWindowAndroid;
 
-    private SmsReceiver(long smsProviderAndroid) {
+    private SmsUserConsentReceiver(long smsProviderAndroid) {
         mDestroyed = false;
         mSmsProviderAndroid = smsProviderAndroid;
 
@@ -57,20 +60,22 @@
     }
 
     @CalledByNative
-    private static SmsReceiver create(long smsProviderAndroid) {
-        if (DEBUG) Log.d(TAG, "Creating SmsReceiver.");
-        return new SmsReceiver(smsProviderAndroid);
+    private static SmsUserConsentReceiver create(long smsProviderAndroid) {
+        if (DEBUG) Log.d(TAG, "Creating SmsUserConsentReceiver.");
+        return new SmsUserConsentReceiver(smsProviderAndroid);
     }
 
     @CalledByNative
     private void destroy() {
-        if (DEBUG) Log.d(TAG, "Destroying SmsReceiver.");
+        if (DEBUG) Log.d(TAG, "Destroying SmsUserConsentReceiver.");
         mDestroyed = true;
         mContext.unregisterReceiver(this);
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        assert mWindowAndroid != null;
+
         if (DEBUG) Log.d(TAG, "Received something!");
 
         if (mDestroyed) {
@@ -90,49 +95,63 @@
         try {
             status = (Status) intent.getParcelableExtra(SmsRetriever.EXTRA_STATUS);
         } catch (Throwable e) {
-            if (DEBUG) Log.d(TAG, "Error getting parceable");
+            if (DEBUG) Log.d(TAG, "Error getting parceable.");
             return;
         }
 
         switch (status.getStatusCode()) {
             case CommonStatusCodes.SUCCESS:
-                String message = intent.getExtras().getString(SmsRetriever.EXTRA_SMS_MESSAGE);
-                if (DEBUG) Log.d(TAG, "Got message: %s!", message);
-                SmsReceiverJni.get().onReceive(mSmsProviderAndroid, message);
+                Intent consentIntent =
+                        intent.getExtras().getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT);
+                try {
+                    mWindowAndroid.showIntent(consentIntent,
+                            (window, resultCode, data) -> onConsentResult(resultCode, data), null);
+                } catch (android.content.ActivityNotFoundException e) {
+                    if (DEBUG) Log.d(TAG, "Error starting activity for result.");
+                }
                 break;
             case CommonStatusCodes.TIMEOUT:
                 if (DEBUG) Log.d(TAG, "Timeout");
-                SmsReceiverJni.get().onTimeout(mSmsProviderAndroid);
+                SmsUserConsentReceiverJni.get().onTimeout(mSmsProviderAndroid);
                 break;
         }
     }
 
+    void onConsentResult(int resultCode, Intent data) {
+        if (resultCode == Activity.RESULT_OK) {
+            String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
+            SmsUserConsentReceiverJni.get().onReceive(mSmsProviderAndroid, message);
+        } else if (resultCode == Activity.RESULT_CANCELED) {
+            if (DEBUG) Log.d(TAG, "Activity result cancelled.");
+        }
+    }
+
     @CalledByNative
-    private void listen() {
-        Wrappers.SmsRetrieverClientWrapper client = getClient();
-        Task<Void> task = client.startSmsRetriever();
-
+    private void listen(WindowAndroid windowAndroid) {
+        mWindowAndroid = windowAndroid;
+        Task<Void> task = getClient().startSmsUserConsent(null);
         if (DEBUG) Log.d(TAG, "Installed task");
     }
 
     private Wrappers.SmsRetrieverClientWrapper getClient() {
-        if (mClient != null) {
-            return mClient;
-        }
+        if (mClient != null) return mClient;
         mClient = new Wrappers.SmsRetrieverClientWrapper(SmsRetriever.getClient(mContext));
         return mClient;
     }
 
     @VisibleForTesting
-    public void setClientForTesting(Wrappers.SmsRetrieverClientWrapper client) {
+    public void setClientForTesting(
+            Wrappers.SmsRetrieverClientWrapper client, WindowAndroid windowAndroid) {
         assert mClient == null;
+        assert mWindowAndroid == null;
+        mWindowAndroid = windowAndroid;
         mClient = client;
         mClient.setContext(mContext);
     }
 
     @NativeMethods
     interface Natives {
-        void onReceive(long nativeSmsProviderAndroid, String sms);
-        void onTimeout(long nativeSmsProviderAndroid);
+        void onReceive(long nativeSmsProviderGmsUserConsent, String sms);
+        void onTimeout(long nativeSmsProviderGmsUserConsent);
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java b/content/public/android/java/src/org/chromium/content/browser/sms/SmsVerificationReceiver.java
similarity index 83%
rename from content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java
rename to content/public/android/java/src/org/chromium/content/browser/sms/SmsVerificationReceiver.java
index 0e854e1..4bb9410 100644
--- a/content/public/android/java/src/org/chromium/content/browser/sms/SmsReceiver.java
+++ b/content/public/android/java/src/org/chromium/content/browser/sms/SmsVerificationReceiver.java
@@ -29,15 +29,15 @@
  */
 @JNINamespace("content")
 @JNIAdditionalImport(Wrappers.class)
-public class SmsReceiver extends BroadcastReceiver {
-    private static final String TAG = "SmsReceiver";
+public class SmsVerificationReceiver extends BroadcastReceiver {
+    private static final String TAG = "SmsVerificationReceiver";
     private static final boolean DEBUG = false;
     private final long mSmsProviderAndroid;
     private boolean mDestroyed;
     private Wrappers.SmsRetrieverClientWrapper mClient;
     private Wrappers.SmsReceiverContext mContext;
 
-    private SmsReceiver(long smsProviderAndroid) {
+    private SmsVerificationReceiver(long smsProviderAndroid) {
         mDestroyed = false;
         mSmsProviderAndroid = smsProviderAndroid;
 
@@ -57,14 +57,14 @@
     }
 
     @CalledByNative
-    private static SmsReceiver create(long smsProviderAndroid) {
-        if (DEBUG) Log.d(TAG, "Creating SmsReceiver.");
-        return new SmsReceiver(smsProviderAndroid);
+    private static SmsVerificationReceiver create(long smsProviderAndroid) {
+        if (DEBUG) Log.d(TAG, "Creating SmsVerificationReceiver.");
+        return new SmsVerificationReceiver(smsProviderAndroid);
     }
 
     @CalledByNative
     private void destroy() {
-        if (DEBUG) Log.d(TAG, "Destroying SmsReceiver.");
+        if (DEBUG) Log.d(TAG, "Destroying SmsVerificationReceiver.");
         mDestroyed = true;
         mContext.unregisterReceiver(this);
     }
@@ -98,11 +98,11 @@
             case CommonStatusCodes.SUCCESS:
                 String message = intent.getExtras().getString(SmsRetriever.EXTRA_SMS_MESSAGE);
                 if (DEBUG) Log.d(TAG, "Got message: %s!", message);
-                SmsReceiverJni.get().onReceive(mSmsProviderAndroid, message);
+                SmsVerificationReceiverJni.get().onReceive(mSmsProviderAndroid, message);
                 break;
             case CommonStatusCodes.TIMEOUT:
                 if (DEBUG) Log.d(TAG, "Timeout");
-                SmsReceiverJni.get().onTimeout(mSmsProviderAndroid);
+                SmsVerificationReceiverJni.get().onTimeout(mSmsProviderAndroid);
                 break;
         }
     }
@@ -132,7 +132,7 @@
 
     @NativeMethods
     interface Natives {
-        void onReceive(long nativeSmsProviderAndroid, String sms);
-        void onTimeout(long nativeSmsProviderAndroid);
+        void onReceive(long nativeSmsProviderGmsVerification, String sms);
+        void onTimeout(long nativeSmsProviderGmsVerification);
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/sms/Wrappers.java b/content/public/android/java/src/org/chromium/content/browser/sms/Wrappers.java
index 416a049..94027123 100644
--- a/content/public/android/java/src/org/chromium/content/browser/sms/Wrappers.java
+++ b/content/public/android/java/src/org/chromium/content/browser/sms/Wrappers.java
@@ -39,6 +39,10 @@
         public Task<Void> startSmsRetriever() {
             return mSmsRetrieverClient.startSmsRetriever();
         }
+
+        public Task<Void> startSmsUserConsent(String senderAddress) {
+            return mSmsRetrieverClient.startSmsUserConsent(senderAddress);
+        }
     }
 
     /**
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/sms/SmsUserConsentFakes.java b/content/public/android/javatests/src/org/chromium/content/browser/sms/SmsUserConsentFakes.java
new file mode 100644
index 0000000..e706835
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/sms/SmsUserConsentFakes.java
@@ -0,0 +1,108 @@
+// Copyright 2019 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.
+
+package org.chromium.content.browser.sms;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.google.android.gms.auth.api.phone.SmsRetriever;
+import com.google.android.gms.common.api.CommonStatusCodes;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.tasks.Tasks;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNIAdditionalImport;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.ui.base.WindowAndroid;
+
+@JNINamespace("content")
+@JNIAdditionalImport(Wrappers.class)
+class SmsUserConsentFakes {
+    private static final String TAG = "SmsReceiver";
+
+    /**
+     * Fakes com.google.android.gms.auth.api.phone.SmsRetrieverClient.
+     **/
+    static class FakeSmsUserConsentRetrieverClient extends Wrappers.SmsRetrieverClientWrapper {
+        @CalledByNative("FakeSmsUserConsentRetrieverClient")
+        private static FakeSmsUserConsentRetrieverClient create() {
+            Log.v(TAG, "FakeSmsUserConsentRetrieverClient.create");
+            return new FakeSmsUserConsentRetrieverClient();
+        }
+
+        private FakeSmsUserConsentRetrieverClient() {
+            super(null);
+        }
+
+        @CalledByNative("FakeSmsUserConsentRetrieverClient")
+        private Task<Void> triggerUserConsentSms(String sms) {
+            Wrappers.SmsReceiverContext context = super.getContext();
+            if (context == null) {
+                Log.v(TAG,
+                        "FakeSmsUserConsentRetrieverClient.triggerUserConsentSms failed: "
+                                + "no context was set");
+                return Tasks.forResult(null);
+            }
+
+            Intent intent = new Intent(SmsRetriever.SMS_RETRIEVED_ACTION);
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(SmsRetriever.EXTRA_STATUS, new Status(CommonStatusCodes.SUCCESS));
+            bundle.putString(SmsRetriever.EXTRA_SMS_MESSAGE, sms);
+            intent.putExtras(bundle);
+
+            BroadcastReceiver receiver = context.getRegisteredReceiver();
+            try {
+                ((SmsUserConsentReceiver) receiver).onConsentResult(Activity.RESULT_OK, intent);
+            } catch (ClassCastException e) {
+                Log.v(TAG,
+                        "FakeSmsUserConsentRetrieverClient.triggerUserConsentSms failed: "
+                                + "receiver must be an instance of SmsUserConsentReceiver");
+            }
+            return Tasks.forResult(null);
+        }
+
+        @CalledByNative("FakeSmsUserConsentRetrieverClient")
+        private Task<Void> triggerTimeout() {
+            Wrappers.SmsReceiverContext context = super.getContext();
+            if (context == null) {
+                Log.v(TAG,
+                        "FakeSmsUserConsentRetrieverClient.triggerTimeout failed: "
+                                + "no context was set");
+                return Tasks.forResult(null);
+            }
+
+            Intent intent = new Intent(SmsRetriever.SMS_RETRIEVED_ACTION);
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(SmsRetriever.EXTRA_STATUS, new Status(CommonStatusCodes.TIMEOUT));
+            intent.putExtras(bundle);
+
+            BroadcastReceiver receiver = context.getRegisteredReceiver();
+            receiver.onReceive(context, intent);
+            return Tasks.forResult(null);
+        }
+
+        // ---------------------------------------------------------------------
+        // SmsRetrieverClient overrides:
+
+        @Override
+        public Task<Void> startSmsRetriever() {
+            return Tasks.forResult(null);
+        }
+    }
+
+    /**
+     * Sets SmsRetrieverClient to SmsUserConsentReceiver to allow faking user
+     * consented SMSes from android client.
+     **/
+    @CalledByNative
+    private static void setUserConsentClientForTesting(SmsUserConsentReceiver receiver,
+            Wrappers.SmsRetrieverClientWrapper client, WindowAndroid windowAndroid) {
+        receiver.setClientForTesting(client, windowAndroid);
+    }
+}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/sms/Fakes.java b/content/public/android/javatests/src/org/chromium/content/browser/sms/SmsVerificationFakes.java
similarity index 90%
rename from content/public/android/javatests/src/org/chromium/content/browser/sms/Fakes.java
rename to content/public/android/javatests/src/org/chromium/content/browser/sms/SmsVerificationFakes.java
index 62d2e87..00093205 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/sms/Fakes.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/sms/SmsVerificationFakes.java
@@ -21,7 +21,7 @@
 
 @JNINamespace("content")
 @JNIAdditionalImport(Wrappers.class)
-class Fakes {
+class SmsVerificationFakes {
     private static final String TAG = "SmsReceiver";
 
     /**
@@ -39,10 +39,12 @@
         }
 
         @CalledByNative("FakeSmsRetrieverClient")
-        private Task<Void> triggerSms(String sms) {
+        private Task<Void> triggerSmsVerificationSms(String sms) {
             Wrappers.SmsReceiverContext context = super.getContext();
             if (context == null) {
-                Log.v(TAG, "FakeSmsRetrieverClient.triggerSms failed: no context was set");
+                Log.v(TAG,
+                        "FakeSmsRetrieverClient.triggerSmsVerificationSms failed: "
+                                + "no context was set");
                 return Tasks.forResult(null);
             }
 
@@ -90,7 +92,7 @@
      **/
     @CalledByNative
     private static void setClientForTesting(
-            SmsReceiver receiver, Wrappers.SmsRetrieverClientWrapper client) {
+            SmsVerificationReceiver receiver, Wrappers.SmsRetrieverClientWrapper client) {
         receiver.setClientForTesting(client);
     }
 }
diff --git a/content/public/browser/sms_fetcher.h b/content/public/browser/sms_fetcher.h
index 256df4a3..e0b5202 100644
--- a/content/public/browser/sms_fetcher.h
+++ b/content/public/browser/sms_fetcher.h
@@ -17,13 +17,20 @@
 namespace content {
 
 class BrowserContext;
+class RenderFrameHost;
 
 // SmsFetcher coordinates between the provisioning of SMSes coming from the
 // local device or remote devices to multiple origins.
 // There is one SmsFetcher per profile.
 class SmsFetcher {
  public:
+  // Retrieval for devices that exclusively listen for SMSes coming from other
+  // telephony devices. (eg. desktop)
   CONTENT_EXPORT static SmsFetcher* Get(BrowserContext* context);
+  // Retrieval for devices that have telephony capabilities and can receive
+  // SMSes coming from the installed device locally. (eg. Android phones)
+  CONTENT_EXPORT static SmsFetcher* Get(BrowserContext* context,
+                                        RenderFrameHost* rfh);
 
   class Subscriber : public base::CheckedObserver {
    public:
@@ -38,6 +45,8 @@
   virtual void Unsubscribe(const url::Origin& origin,
                            Subscriber* subscriber) = 0;
   virtual bool HasSubscribers() = 0;
+  // Checks if the device can receive SMSes.
+  virtual bool CanReceiveSms() = 0;
 };
 
 }  // namespace content
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index e16445a..3d82c65e 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -766,6 +766,11 @@
 const base::Feature kExperimentalProductivityFeatures{
     "ExperimentalProductivityFeatures", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// When this feature is enabled, the Web OTP API will use the User Consent
+// API to retrieve SMSes from the Android device.
+const base::Feature kWebOtpBackend{"kWebOtpBackend",
+                                   base::FEATURE_DISABLED_BY_DEFAULT};
+
 // The JavaScript API for payments on the web.
 // TODO(rouslan): Remove this.
 const base::Feature kWebPayments{"WebPayments",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 1f03f8b..3199d4c 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -158,6 +158,7 @@
 CONTENT_EXPORT extern const base::Feature kWebBundlesFromNetwork;
 CONTENT_EXPORT extern const base::Feature kWebContentsOcclusion;
 CONTENT_EXPORT extern const base::Feature kWebGLImageChromium;
+CONTENT_EXPORT extern const base::Feature kWebOtpBackend;
 CONTENT_EXPORT extern const base::Feature kWebPayments;
 CONTENT_EXPORT extern const base::Feature kWebPaymentsMinimalUI;
 CONTENT_EXPORT extern const base::Feature kWebRtcEcdsaDefault;
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index c92a55e..57229f0 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -863,6 +863,16 @@
 // Set a default sample count for webgl if msaa is enabled.
 const char kWebglMSAASampleCount[] = "webgl-msaa-sample-count";
 
+// Enables specified backend for the Web OTP API.
+const char kWebOtpBackend[] = "web-otp-backend";
+
+// Enables Sms Verification backend for Web OTP API which requires app hash in
+// SMS body.
+const char kWebOtpBackendSmsVerification[] = "web-otp-backend-sms-verification";
+
+// Enables User Consent backend for Web OTP API.
+const char kWebOtpBackendUserConsent[] = "web-otp-backend-user-consent";
+
 // Disables encryption of RTP Media for WebRTC. When Chrome embeds Content, it
 // ignores this switch on its stable and beta channels.
 const char kDisableWebRtcEncryption[]      = "disable-webrtc-encryption";
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 70d0a6c..7b3d855 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -239,6 +239,9 @@
 extern const char kWebglAntialiasingMode[];
 extern const char kWebglMSAASampleCount[];
 
+CONTENT_EXPORT extern const char kWebOtpBackend[];
+CONTENT_EXPORT extern const char kWebOtpBackendSmsVerification[];
+CONTENT_EXPORT extern const char kWebOtpBackendUserConsent[];
 CONTENT_EXPORT extern const char kDisableWebRtcEncryption[];
 CONTENT_EXPORT extern const char kDisableWebRtcHWDecoding[];
 CONTENT_EXPORT extern const char kDisableWebRtcHWEncoding[];
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 1a565c7..3bb74f72 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2227,7 +2227,8 @@
       "../browser/font_unique_name_lookup/font_unique_name_lookup_unittest.cc",
       "../browser/media/capture/screen_capture_device_android_unittest.cc",
       "../browser/renderer_host/render_widget_host_view_android_unittest.cc",
-      "../browser/sms/sms_provider_android_unittest.cc",
+      "../browser/sms/sms_provider_gms_user_consent_unittest.cc",
+      "../browser/sms/sms_provider_gms_verification_unittest.cc",
       "../renderer/java/gin_java_bridge_value_converter_unittest.cc",
       "../renderer/media/android/stream_texture_proxy_unittest.cc",
       "../renderer/media/android/stream_texture_wrapper_impl_unittest.cc",
@@ -2392,7 +2393,8 @@
 
 if (is_android) {
   content_java_sources_needing_jni = [
-    "//content/public/android/javatests/src/org/chromium/content/browser/sms/Fakes.java",
+    "//content/public/android/javatests/src/org/chromium/content/browser/sms/SmsVerificationFakes.java",
+    "//content/public/android/javatests/src/org/chromium/content/browser/sms/SmsUserConsentFakes.java",
     "//content/public/android/javatests/src/org/chromium/content/browser/fakes/TestViewAndroidDelegate.java",
   ]
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 26f183e..cc90a1f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -40778,6 +40778,7 @@
   <int value="1936810062" label="WebVrVsyncAlign:enabled"/>
   <int value="1938279796" label="PromosOnLocalNtp:disabled"/>
   <int value="1939413645" label="enable-invalid-cert-collection"/>
+  <int value="1939884866" label="web-otp-backend"/>
   <int value="1940625534" label="NewOverviewUi:disabled"/>
   <int value="1941845443" label="ScrollableTabStrip:enabled"/>
   <int value="1942911276" label="enable-grouped-history"/>