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"/>