1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
6
7 #include <stdint.h>
8
9 #include <string>
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/macros.h"
15 #include "base/run_loop.h"
16 #include "base/strings/string16.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/test/scoped_feature_list.h"
20 #include "build/build_config.h"
21 #include "chrome/browser/autofill/mock_address_accessory_controller.h"
22 #include "chrome/browser/autofill/mock_manual_filling_view.h"
23 #include "chrome/browser/autofill/mock_password_accessory_controller.h"
24 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
25 #include "chrome/browser/password_manager/password_store_factory.h"
26 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
27 #include "chrome/browser/safe_browsing/user_interaction_observer.h"
28 #include "chrome/browser/sync/profile_sync_service_factory.h"
29 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
30 #include "chrome/test/base/testing_profile.h"
31 #include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
32 #include "components/autofill/core/browser/logging/log_manager.h"
33 #include "components/autofill/core/browser/logging/log_receiver.h"
34 #include "components/autofill/core/browser/logging/log_router.h"
35 #include "components/autofill/core/browser/test_autofill_client.h"
36 #include "components/autofill/core/common/autofill_features.h"
37 #include "components/favicon/core/test/mock_favicon_service.h"
38 #include "components/password_manager/content/browser/content_password_manager_driver.h"
39 #include "components/password_manager/content/browser/password_manager_log_router_factory.h"
40 #include "components/password_manager/core/browser/credential_cache.h"
41 #include "components/password_manager/core/browser/credentials_filter.h"
42 #include "components/password_manager/core/browser/mock_password_store.h"
43 #include "components/password_manager/core/browser/password_form.h"
44 #include "components/password_manager/core/browser/password_manager.h"
45 #include "components/password_manager/core/browser/password_manager_test_utils.h"
46 #include "components/password_manager/core/browser/stub_password_manager_client.h"
47 #include "components/password_manager/core/common/credential_manager_types.h"
48 #include "components/password_manager/core/common/password_manager_features.h"
49 #include "components/password_manager/core/common/password_manager_pref_names.h"
50 #include "components/prefs/pref_registry_simple.h"
51 #include "components/prefs/pref_service.h"
52 #include "components/prefs/testing_pref_service.h"
53 #include "components/safe_browsing/buildflags.h"
54 #include "components/safe_browsing/core/features.h"
55 #include "components/sessions/content/content_record_password_state.h"
56 #include "components/sync/driver/test_sync_service.h"
57 #include "components/sync_preferences/testing_pref_service_syncable.h"
58 #include "content/public/browser/browser_context.h"
59 #include "content/public/browser/navigation_entry.h"
60 #include "content/public/browser/web_contents.h"
61 #include "content/public/browser/web_contents_observer.h"
62 #include "content/public/common/content_switches.h"
63 #include "content/public/common/url_constants.h"
64 #include "content/public/test/mock_navigation_handle.h"
65 #include "content/public/test/navigation_simulator.h"
66 #include "content/public/test/web_contents_tester.h"
67 #include "mojo/public/cpp/bindings/associated_receiver.h"
68 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
69 #include "mojo/public/cpp/bindings/pending_receiver.h"
70 #include "services/service_manager/public/cpp/interface_provider.h"
71 #include "testing/gmock/include/gmock/gmock.h"
72 #include "testing/gtest/include/gtest/gtest.h"
73 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
74 #include "url/url_constants.h"
75
76 #if BUILDFLAG(ENABLE_EXTENSIONS)
77 #include "extensions/common/constants.h"
78 #endif
79
80 #if BUILDFLAG(FULL_SAFE_BROWSING)
81 #include "components/safe_browsing/content/password_protection/mock_password_protection_service.h"
82 #endif
83
84 #if defined(OS_ANDROID)
85 #include "chrome/browser/autofill/manual_filling_controller_impl.h"
86 #include "chrome/browser/autofill/mock_address_accessory_controller.h"
87 #include "chrome/browser/autofill/mock_credit_card_accessory_controller.h"
88 #include "chrome/browser/autofill/mock_manual_filling_view.h"
89 #include "chrome/browser/password_manager/android/password_accessory_controller_impl.h"
90 #include "chrome/browser/password_manager/android/password_generation_controller.h"
91 #endif // defined(OS_ANDROID)
92
93 using autofill::mojom::FocusedFieldType;
94 using content::BrowserContext;
95 using content::WebContents;
96 using password_manager::PasswordForm;
97 using password_manager::PasswordManagerClient;
98 using sessions::GetPasswordStateFromNavigation;
99 using sessions::SerializedNavigationEntry;
100 using testing::_;
101 using testing::NiceMock;
102 using testing::Return;
103 using testing::StrictMock;
104
105 #if defined(OS_ANDROID)
106 using password_manager::CredentialCache;
107 #endif
108
109 namespace {
110 // TODO(crbug.com/474577): Get rid of the mocked client in the client's own
111 // test.
112 class MockChromePasswordManagerClient : public ChromePasswordManagerClient {
113 public:
114 MOCK_CONST_METHOD0(GetMainFrameCertStatus, net::CertStatus());
115
MockChromePasswordManagerClient(content::WebContents * web_contents)116 explicit MockChromePasswordManagerClient(content::WebContents* web_contents)
117 : ChromePasswordManagerClient(web_contents, nullptr) {
118 ON_CALL(*this, GetMainFrameCertStatus()).WillByDefault(testing::Return(0));
119 #if BUILDFLAG(FULL_SAFE_BROWSING)
120 password_protection_service_ =
121 std::make_unique<safe_browsing::MockPasswordProtectionService>();
122 #endif
123 }
124
125 #if BUILDFLAG(FULL_SAFE_BROWSING)
GetPasswordProtectionService() const126 safe_browsing::PasswordProtectionService* GetPasswordProtectionService()
127 const override {
128 return password_protection_service_.get();
129 }
130
password_protection_service()131 safe_browsing::MockPasswordProtectionService* password_protection_service() {
132 return password_protection_service_.get();
133 }
134 #endif
135
136 private:
137 #if BUILDFLAG(FULL_SAFE_BROWSING)
138 std::unique_ptr<safe_browsing::MockPasswordProtectionService>
139 password_protection_service_;
140 #endif
141 DISALLOW_COPY_AND_ASSIGN(MockChromePasswordManagerClient);
142 };
143
144 class DummyLogReceiver : public autofill::LogReceiver {
145 public:
146 DummyLogReceiver() = default;
147
LogEntry(const base::Value & entry)148 void LogEntry(const base::Value& entry) override {}
149
150 private:
151 DISALLOW_COPY_AND_ASSIGN(DummyLogReceiver);
152 };
153
154 class FakePasswordAutofillAgent
155 : public autofill::mojom::PasswordAutofillAgent {
156 public:
BindReceiver(mojo::ScopedInterfaceEndpointHandle handle)157 void BindReceiver(mojo::ScopedInterfaceEndpointHandle handle) {
158 receiver_.Bind(
159 mojo::PendingAssociatedReceiver<autofill::mojom::PasswordAutofillAgent>(
160 std::move(handle)));
161 }
162
called_set_logging_state()163 bool called_set_logging_state() { return called_set_logging_state_; }
164
logging_state_active()165 bool logging_state_active() { return logging_state_active_; }
166
reset_data()167 void reset_data() {
168 called_set_logging_state_ = false;
169 logging_state_active_ = false;
170 }
171
172 private:
173 // autofill::mojom::PasswordAutofillAgent:
FillPasswordForm(const autofill::PasswordFormFillData & form_data)174 void FillPasswordForm(
175 const autofill::PasswordFormFillData& form_data) override {}
176
InformNoSavedCredentials(bool should_show_popup_without_passwords)177 void InformNoSavedCredentials(
178 bool should_show_popup_without_passwords) override {}
179
FillIntoFocusedField(bool is_password,const base::string16 & credential)180 void FillIntoFocusedField(bool is_password,
181 const base::string16& credential) override {}
AnnotateFieldsWithParsingResult(const autofill::ParsingResult & parsing_result)182 void AnnotateFieldsWithParsingResult(
183 const autofill::ParsingResult& parsing_result) override {}
184
SetLoggingState(bool active)185 void SetLoggingState(bool active) override {
186 called_set_logging_state_ = true;
187 logging_state_active_ = active;
188 }
TouchToFillClosed(bool show_virtual_keyboard)189 void TouchToFillClosed(bool show_virtual_keyboard) override {}
190
191 // Records whether SetLoggingState() gets called.
192 bool called_set_logging_state_ = false;
193 // Records data received via SetLoggingState() call.
194 bool logging_state_active_ = false;
195
196 mojo::AssociatedReceiver<autofill::mojom::PasswordAutofillAgent> receiver_{
197 this};
198 };
199
CreateTestSyncService(content::BrowserContext * context)200 std::unique_ptr<KeyedService> CreateTestSyncService(
201 content::BrowserContext* context) {
202 return std::make_unique<syncer::TestSyncService>();
203 }
204
205 } // namespace
206
207 class ChromePasswordManagerClientTest : public ChromeRenderViewHostTestHarness {
208 public:
209 void SetUp() override;
210 void TearDown() override;
211
prefs()212 sync_preferences::TestingPrefServiceSyncable* prefs() {
213 return profile()->GetTestingPrefService();
214 }
215
216 // Caller does not own the returned pointer.
SetupBasicTestSync()217 syncer::TestSyncService* SetupBasicTestSync() {
218 syncer::TestSyncService* sync_service =
219 static_cast<syncer::TestSyncService*>(
220 ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
221 profile(), base::BindRepeating(&CreateTestSyncService)));
222 return sync_service;
223 }
224
225 // Make a navigation entry that will accept an annotation.
SetupNavigationForAnnotation()226 void SetupNavigationForAnnotation() {
227 syncer::TestSyncService* sync_service = SetupBasicTestSync();
228 sync_service->SetIsUsingSecondaryPassphrase(false);
229 metrics_enabled_ = true;
230 NavigateAndCommit(GURL("about:blank"));
231 }
232
233 protected:
234 ChromePasswordManagerClient* GetClient();
235
236 // If autofill::mojom::PasswordAutofillAgent::SetLoggingState() got called,
237 // copies its argument into |activation_flag| and returns true. Otherwise
238 // returns false.
239 bool WasLoggingActivationMessageSent(bool* activation_flag);
240
241 FakePasswordAutofillAgent fake_agent_;
242
243 TestingPrefServiceSimple prefs_;
244 bool metrics_enabled_ = false;
245
246 base::test::ScopedFeatureList scoped_feature_list_;
247 };
248
SetUp()249 void ChromePasswordManagerClientTest::SetUp() {
250 ChromeRenderViewHostTestHarness::SetUp();
251
252 blink::AssociatedInterfaceProvider* remote_interfaces =
253 web_contents()->GetMainFrame()->GetRemoteAssociatedInterfaces();
254 remote_interfaces->OverrideBinderForTesting(
255 autofill::mojom::PasswordAutofillAgent::Name_,
256 base::BindRepeating(&FakePasswordAutofillAgent::BindReceiver,
257 base::Unretained(&fake_agent_)));
258
259 prefs_.registry()->RegisterBooleanPref(
260 password_manager::prefs::kCredentialsEnableService, true);
261 ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
262 web_contents(), nullptr);
263
264 // Connect our bool for testing.
265 ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
266 &metrics_enabled_);
267
268 scoped_feature_list_.InitAndEnableFeature(safe_browsing::kDelayedWarnings);
269 }
270
TearDown()271 void ChromePasswordManagerClientTest::TearDown() {
272 ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(nullptr);
273 ChromeRenderViewHostTestHarness::TearDown();
274 }
275
GetClient()276 ChromePasswordManagerClient* ChromePasswordManagerClientTest::GetClient() {
277 return ChromePasswordManagerClient::FromWebContents(web_contents());
278 }
279
WasLoggingActivationMessageSent(bool * activation_flag)280 bool ChromePasswordManagerClientTest::WasLoggingActivationMessageSent(
281 bool* activation_flag) {
282 base::RunLoop().RunUntilIdle();
283 if (!fake_agent_.called_set_logging_state())
284 return false;
285
286 if (activation_flag)
287 *activation_flag = fake_agent_.logging_state_active();
288 fake_agent_.reset_data();
289 return true;
290 }
291
TEST_F(ChromePasswordManagerClientTest,LogEntryNotifyRenderer)292 TEST_F(ChromePasswordManagerClientTest, LogEntryNotifyRenderer) {
293 bool logging_active = true;
294 // Ensure the existence of a driver, which will send the IPCs we listen for
295 // below.
296 NavigateAndCommit(GURL("about:blank"));
297
298 // Initially, the logging should be off, so no IPC messages.
299 EXPECT_TRUE(!WasLoggingActivationMessageSent(&logging_active) ||
300 !logging_active)
301 << "logging_active=" << logging_active;
302
303 DummyLogReceiver log_receiver;
304 autofill::LogRouter* log_router =
305 password_manager::PasswordManagerLogRouterFactory::GetForBrowserContext(
306 profile());
307 EXPECT_EQ(std::vector<base::Value>(),
308 log_router->RegisterReceiver(&log_receiver));
309 EXPECT_TRUE(WasLoggingActivationMessageSent(&logging_active));
310 EXPECT_TRUE(logging_active);
311
312 log_router->UnregisterReceiver(&log_receiver);
313 EXPECT_TRUE(WasLoggingActivationMessageSent(&logging_active));
314 EXPECT_FALSE(logging_active);
315 }
316
TEST_F(ChromePasswordManagerClientTest,GetPasswordSyncState)317 TEST_F(ChromePasswordManagerClientTest, GetPasswordSyncState) {
318 syncer::TestSyncService* sync_service = SetupBasicTestSync();
319
320 sync_service->SetActiveDataTypes(syncer::ModelTypeSet(syncer::PASSWORDS));
321 sync_service->SetIsUsingSecondaryPassphrase(false);
322
323 ChromePasswordManagerClient* client = GetClient();
324
325 // Passwords are syncing and custom passphrase isn't used.
326 EXPECT_EQ(password_manager::SYNCING_NORMAL_ENCRYPTION,
327 client->GetPasswordSyncState());
328
329 // Again, using a custom passphrase.
330 sync_service->SetIsUsingSecondaryPassphrase(true);
331
332 EXPECT_EQ(password_manager::SYNCING_WITH_CUSTOM_PASSPHRASE,
333 client->GetPasswordSyncState());
334
335 // Report correctly if we aren't syncing passwords.
336 sync_service->SetActiveDataTypes(syncer::ModelTypeSet(syncer::BOOKMARKS));
337
338 EXPECT_EQ(password_manager::NOT_SYNCING, client->GetPasswordSyncState());
339
340 // Again, without a custom passphrase.
341 sync_service->SetIsUsingSecondaryPassphrase(false);
342
343 EXPECT_EQ(password_manager::NOT_SYNCING, client->GetPasswordSyncState());
344 }
345
TEST_F(ChromePasswordManagerClientTest,SavingDependsOnManagerEnabledPreference)346 TEST_F(ChromePasswordManagerClientTest,
347 SavingDependsOnManagerEnabledPreference) {
348 // Test that saving passwords depends on the password manager enabled
349 // preference.
350 ChromePasswordManagerClient* client = GetClient();
351 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
352 std::make_unique<base::Value>(true));
353 const GURL kUrlOn("https://accounts.google.com");
354 EXPECT_TRUE(client->IsSavingAndFillingEnabled(kUrlOn));
355 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
356 std::make_unique<base::Value>(false));
357 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
358 }
359
TEST_F(ChromePasswordManagerClientTest,SavingAndFillingEnabledConditionsTest)360 TEST_F(ChromePasswordManagerClientTest, SavingAndFillingEnabledConditionsTest) {
361 std::unique_ptr<WebContents> test_web_contents(
362 content::WebContentsTester::CreateTestWebContents(
363 web_contents()->GetBrowserContext(), nullptr));
364 std::unique_ptr<MockChromePasswordManagerClient> client(
365 new MockChromePasswordManagerClient(test_web_contents.get()));
366 // Functionality disabled if there is an SSL error.
367 EXPECT_CALL(*client, GetMainFrameCertStatus())
368 .WillRepeatedly(Return(net::CERT_STATUS_AUTHORITY_INVALID));
369 const GURL kUrlOn("https://accounts.google.com");
370 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
371 EXPECT_FALSE(client->IsFillingEnabled(kUrlOn));
372 EXPECT_FALSE(client->IsFillingFallbackEnabled(kUrlOn));
373
374 // Functionality disabled if there are SSL errors and the manager itself is
375 // disabled.
376 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
377 std::make_unique<base::Value>(false));
378 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
379 EXPECT_FALSE(client->IsFillingEnabled(kUrlOn));
380 EXPECT_FALSE(client->IsFillingFallbackEnabled(kUrlOn));
381
382 // Saving disabled if there are no SSL errors, but the manager itself is
383 // disabled.
384 EXPECT_CALL(*client, GetMainFrameCertStatus()).WillRepeatedly(Return(0));
385 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
386 std::make_unique<base::Value>(false));
387 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
388 EXPECT_TRUE(client->IsFillingEnabled(kUrlOn));
389 EXPECT_TRUE(client->IsFillingFallbackEnabled(kUrlOn));
390
391 // Functionality enabled if there are no SSL errors and the manager is
392 // enabled.
393 EXPECT_CALL(*client, GetMainFrameCertStatus()).WillRepeatedly(Return(0));
394 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
395 std::make_unique<base::Value>(true));
396 EXPECT_TRUE(client->IsSavingAndFillingEnabled(kUrlOn));
397 EXPECT_TRUE(client->IsFillingEnabled(kUrlOn));
398 EXPECT_TRUE(client->IsFillingFallbackEnabled(kUrlOn));
399 }
400
TEST_F(ChromePasswordManagerClientTest,SavingAndFillingDisabledConditionsInIncognito)401 TEST_F(ChromePasswordManagerClientTest,
402 SavingAndFillingDisabledConditionsInIncognito) {
403 std::unique_ptr<WebContents> incognito_web_contents(
404 content::WebContentsTester::CreateTestWebContents(
405 profile()->GetPrimaryOTRProfile(), nullptr));
406 std::unique_ptr<MockChromePasswordManagerClient> client(
407 new MockChromePasswordManagerClient(incognito_web_contents.get()));
408 EXPECT_CALL(*client, GetMainFrameCertStatus()).WillRepeatedly(Return(0));
409
410 // Saving disabled in Incognito mode.
411 const GURL kUrlOn("https://accounts.google.com");
412 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
413 EXPECT_TRUE(client->IsFillingEnabled(kUrlOn));
414 EXPECT_TRUE(client->IsFillingFallbackEnabled(kUrlOn));
415
416 // Saving disabled in Incognito mode also when manager itself is
417 // enabled.
418 prefs()->SetUserPref(password_manager::prefs::kCredentialsEnableService,
419 std::make_unique<base::Value>(true));
420 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
421 EXPECT_TRUE(client->IsFillingEnabled(kUrlOn));
422 EXPECT_TRUE(client->IsFillingFallbackEnabled(kUrlOn));
423
424 // In guest mode saving is disabled, filling is enabled but there is in fact
425 // nothing to fill, manual filling is disabled.
426 profile()->SetGuestSession(true);
427 profile()->GetPrimaryOTRProfile()->AsTestingProfile()->SetGuestSession(true);
428 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
429 EXPECT_TRUE(client->IsFillingEnabled(kUrlOn));
430 EXPECT_FALSE(client->IsFillingFallbackEnabled(kUrlOn));
431 }
432
TEST_F(ChromePasswordManagerClientTest,SavingDependsOnAutomation)433 TEST_F(ChromePasswordManagerClientTest, SavingDependsOnAutomation) {
434 // Test that saving passwords UI is disabled for automated tests.
435 ChromePasswordManagerClient* client = GetClient();
436 const GURL kUrlOn("https://accounts.google.com");
437 EXPECT_TRUE(client->IsSavingAndFillingEnabled(kUrlOn));
438 base::CommandLine::ForCurrentProcess()->AppendSwitch(
439 switches::kEnableAutomation);
440 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
441 }
442
443 // Check that password manager is disabled on about:blank pages.
444 // See https://crbug.com/756587.
TEST_F(ChromePasswordManagerClientTest,SavingAndFillingDisabledForAboutBlank)445 TEST_F(ChromePasswordManagerClientTest, SavingAndFillingDisabledForAboutBlank) {
446 const GURL kUrl(url::kAboutBlankURL);
447 NavigateAndCommit(kUrl);
448 EXPECT_TRUE(GetClient()->GetLastCommittedOrigin().opaque());
449 EXPECT_FALSE(GetClient()->IsSavingAndFillingEnabled(kUrl));
450 EXPECT_FALSE(GetClient()->IsFillingEnabled(kUrl));
451 EXPECT_FALSE(GetClient()->IsFillingFallbackEnabled(kUrl));
452 }
453
TEST_F(ChromePasswordManagerClientTest,IsFillingAndSavingOnGooglePasswordPage)454 TEST_F(ChromePasswordManagerClientTest,
455 IsFillingAndSavingOnGooglePasswordPage) {
456 PasswordManagerClient* client = GetClient();
457 EXPECT_FALSE(client->IsSavingAndFillingEnabled(
458 GURL("https://passwords.google.com/path?query=1")));
459 EXPECT_FALSE(client->IsFillingEnabled(
460 GURL("https://passwords.google.com/path?query=1")));
461 }
462
463 namespace {
464
465 struct SchemeTestCase {
466 const char* scheme;
467 bool password_manager_works;
468 };
469 const SchemeTestCase kTestCases[] = {
470 {url::kHttpScheme, true},
471 {url::kHttpsScheme, true},
472 {url::kFtpScheme, true},
473 {url::kDataScheme, true},
474 {"feed", true},
475
476 {"invalid-scheme-i-just-made-up", false},
477 {content::kChromeDevToolsScheme, false},
478 {content::kChromeUIScheme, false},
479 {url::kMailToScheme, false},
480 };
481
482 // Parameterized test that takes a URL scheme as a parameter. Every scheme
483 // requires a separate test because NavigateAndCommit can be called only once.
484 class ChromePasswordManagerClientSchemeTest
485 : public ChromePasswordManagerClientTest,
486 public ::testing::WithParamInterface<const char*> {
487 public:
GetSchemes()488 static std::vector<const char*> GetSchemes() {
489 std::vector<const char*> result;
490 for (const SchemeTestCase& test_case : kTestCases) {
491 result.push_back(test_case.scheme);
492 }
493 return result;
494 }
495 };
496
TEST_P(ChromePasswordManagerClientSchemeTest,SavingAndFillingOnDifferentSchemes)497 TEST_P(ChromePasswordManagerClientSchemeTest,
498 SavingAndFillingOnDifferentSchemes) {
499 const GURL url(base::StringPrintf("%s://example.org", GetParam()));
500 VLOG(0) << url.possibly_invalid_spec();
501 NavigateAndCommit(url);
502 EXPECT_EQ(url::Origin::Create(url).GetURL(),
503 GetClient()->GetLastCommittedOrigin().GetURL());
504
505 auto* it = std::find_if(
506 std::begin(kTestCases), std::end(kTestCases),
507 [](auto test_case) { return strcmp(test_case.scheme, GetParam()) == 0; });
508 ASSERT_FALSE(it == std::end(kTestCases));
509 EXPECT_EQ(it->password_manager_works,
510 GetClient()->IsSavingAndFillingEnabled(url));
511 EXPECT_EQ(it->password_manager_works, GetClient()->IsFillingEnabled(url));
512 EXPECT_EQ(it->password_manager_works,
513 GetClient()->IsFillingFallbackEnabled(url));
514 }
515
516 INSTANTIATE_TEST_SUITE_P(
517 All,
518 ChromePasswordManagerClientSchemeTest,
519 ::testing::ValuesIn(ChromePasswordManagerClientSchemeTest::GetSchemes()));
520
521 } // namespace
522
TEST_F(ChromePasswordManagerClientTest,GetLastCommittedEntryURL_Empty)523 TEST_F(ChromePasswordManagerClientTest, GetLastCommittedEntryURL_Empty) {
524 EXPECT_TRUE(GetClient()->GetLastCommittedOrigin().opaque());
525 }
526
TEST_F(ChromePasswordManagerClientTest,GetLastCommittedEntryURL)527 TEST_F(ChromePasswordManagerClientTest, GetLastCommittedEntryURL) {
528 GURL kUrl(
529 "https://accounts.google.com/ServiceLogin?continue="
530 "https://passwords.google.com/settings");
531 NavigateAndCommit(kUrl);
532 EXPECT_EQ(url::Origin::Create(kUrl), GetClient()->GetLastCommittedOrigin());
533 }
534
TEST_F(ChromePasswordManagerClientTest,WebUINoLogging)535 TEST_F(ChromePasswordManagerClientTest, WebUINoLogging) {
536 // Make sure that logging is active.
537 autofill::LogRouter* log_router =
538 password_manager::PasswordManagerLogRouterFactory::GetForBrowserContext(
539 profile());
540 DummyLogReceiver log_receiver;
541 EXPECT_EQ(std::vector<base::Value>(),
542 log_router->RegisterReceiver(&log_receiver));
543
544 // But then navigate to a WebUI, there the logging should not be active.
545 NavigateAndCommit(GURL("chrome://password-manager-internals/"));
546 EXPECT_FALSE(GetClient()->GetLogManager()->IsLoggingActive());
547
548 log_router->UnregisterReceiver(&log_receiver);
549 }
550
551 // Metrics enabled, syncing with non-custom passphrase: Do not annotate.
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryWithMetricsNoCustom)552 TEST_F(ChromePasswordManagerClientTest,
553 AnnotateNavigationEntryWithMetricsNoCustom) {
554 syncer::TestSyncService* sync_service = SetupBasicTestSync();
555 sync_service->SetIsUsingSecondaryPassphrase(false);
556 metrics_enabled_ = true;
557
558 NavigateAndCommit(GURL("about:blank"));
559 GetClient()->AnnotateNavigationEntry(true);
560
561 EXPECT_EQ(
562 SerializedNavigationEntry::HAS_PASSWORD_FIELD,
563 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
564 }
565
566 // Metrics disabled, syncing with non-custom passphrase: Do not annotate.
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryNoMetricsNoCustom)567 TEST_F(ChromePasswordManagerClientTest,
568 AnnotateNavigationEntryNoMetricsNoCustom) {
569 syncer::TestSyncService* sync_service = SetupBasicTestSync();
570 sync_service->SetIsUsingSecondaryPassphrase(false);
571 metrics_enabled_ = false;
572
573 NavigateAndCommit(GURL("about:blank"));
574 GetClient()->AnnotateNavigationEntry(true);
575
576 EXPECT_EQ(
577 SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN,
578 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
579 }
580
581 // Metrics enabled, syncing with custom passphrase: Do not annotate.
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryWithMetricsWithCustom)582 TEST_F(ChromePasswordManagerClientTest,
583 AnnotateNavigationEntryWithMetricsWithCustom) {
584 syncer::TestSyncService* sync_service = SetupBasicTestSync();
585 sync_service->SetIsUsingSecondaryPassphrase(true);
586 metrics_enabled_ = true;
587
588 NavigateAndCommit(GURL("about:blank"));
589 GetClient()->AnnotateNavigationEntry(true);
590
591 EXPECT_EQ(
592 SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN,
593 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
594 }
595
596 // Metrics disabled, syncing with custom passphrase: Do not annotate.
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryNoMetricsWithCustom)597 TEST_F(ChromePasswordManagerClientTest,
598 AnnotateNavigationEntryNoMetricsWithCustom) {
599 syncer::TestSyncService* sync_service = SetupBasicTestSync();
600 sync_service->SetIsUsingSecondaryPassphrase(true);
601 metrics_enabled_ = false;
602
603 NavigateAndCommit(GURL("about:blank"));
604 GetClient()->AnnotateNavigationEntry(true);
605
606 EXPECT_EQ(
607 SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN,
608 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
609 }
610
611 // State transition: Unannotated
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryUnannotated)612 TEST_F(ChromePasswordManagerClientTest, AnnotateNavigationEntryUnannotated) {
613 SetupNavigationForAnnotation();
614
615 EXPECT_EQ(
616 SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN,
617 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
618 }
619
620 // State transition: unknown->false
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryToFalse)621 TEST_F(ChromePasswordManagerClientTest, AnnotateNavigationEntryToFalse) {
622 SetupNavigationForAnnotation();
623
624 GetClient()->AnnotateNavigationEntry(false);
625 EXPECT_EQ(
626 SerializedNavigationEntry::NO_PASSWORD_FIELD,
627 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
628 }
629
630 // State transition: false->true
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryToTrue)631 TEST_F(ChromePasswordManagerClientTest, AnnotateNavigationEntryToTrue) {
632 SetupNavigationForAnnotation();
633
634 GetClient()->AnnotateNavigationEntry(false);
635 GetClient()->AnnotateNavigationEntry(true);
636 EXPECT_EQ(
637 SerializedNavigationEntry::HAS_PASSWORD_FIELD,
638 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
639 }
640
641 // State transition: true->false (retains true)
TEST_F(ChromePasswordManagerClientTest,AnnotateNavigationEntryTrueToFalse)642 TEST_F(ChromePasswordManagerClientTest, AnnotateNavigationEntryTrueToFalse) {
643 SetupNavigationForAnnotation();
644
645 GetClient()->AnnotateNavigationEntry(true);
646 GetClient()->AnnotateNavigationEntry(false);
647 EXPECT_EQ(
648 SerializedNavigationEntry::HAS_PASSWORD_FIELD,
649 GetPasswordStateFromNavigation(controller().GetLastCommittedEntry()));
650 }
651
652 // Handle missing ChromePasswordManagerClient instance in BindCredentialManager
653 // gracefully.
TEST_F(ChromePasswordManagerClientTest,BindCredentialManager_MissingInstance)654 TEST_F(ChromePasswordManagerClientTest, BindCredentialManager_MissingInstance) {
655 // Create a WebContent without tab helpers.
656 std::unique_ptr<content::WebContents> web_contents =
657 content::WebContents::Create(
658 content::WebContents::CreateParams(profile()));
659 // In particular, this WebContent should not have the
660 // ChromePasswordManagerClient.
661 ASSERT_FALSE(
662 ChromePasswordManagerClient::FromWebContents(web_contents.get()));
663
664 // This call should not crash.
665 ChromePasswordManagerClient::BindCredentialManager(
666 web_contents->GetMainFrame(), mojo::NullReceiver());
667 }
668
TEST_F(ChromePasswordManagerClientTest,CanShowBubbleOnURL)669 TEST_F(ChromePasswordManagerClientTest, CanShowBubbleOnURL) {
670 struct TestCase {
671 const char* scheme;
672 bool can_show_bubble;
673 } kTestCases[] = {
674 {url::kHttpScheme, true},
675 {url::kHttpsScheme, true},
676 {url::kFtpScheme, true},
677 {url::kDataScheme, true},
678 {"feed", true},
679 {url::kBlobScheme, true},
680 {url::kFileSystemScheme, true},
681
682 {"invalid-scheme-i-just-made-up", false},
683 #if BUILDFLAG(ENABLE_EXTENSIONS)
684 {extensions::kExtensionScheme, false},
685 #endif
686 {url::kAboutScheme, false},
687 {content::kChromeDevToolsScheme, false},
688 {content::kChromeUIScheme, false},
689 {url::kJavaScriptScheme, false},
690 {url::kMailToScheme, false},
691 {content::kViewSourceScheme, false},
692 };
693
694 for (const TestCase& test_case : kTestCases) {
695 // CanShowBubbleOnURL currently only depends on the scheme.
696 GURL url(base::StringPrintf("%s://example.org", test_case.scheme));
697 SCOPED_TRACE(url.possibly_invalid_spec());
698 EXPECT_EQ(test_case.can_show_bubble,
699 ChromePasswordManagerClient::CanShowBubbleOnURL(url));
700 }
701 }
702
703 #if BUILDFLAG(FULL_SAFE_BROWSING)
TEST_F(ChromePasswordManagerClientTest,VerifyMaybeStartPasswordFieldOnFocusRequestCalled)704 TEST_F(ChromePasswordManagerClientTest,
705 VerifyMaybeStartPasswordFieldOnFocusRequestCalled) {
706 std::unique_ptr<WebContents> test_web_contents(
707 content::WebContentsTester::CreateTestWebContents(
708 web_contents()->GetBrowserContext(), nullptr));
709 std::unique_ptr<MockChromePasswordManagerClient> client(
710 new MockChromePasswordManagerClient(test_web_contents.get()));
711 EXPECT_CALL(*client->password_protection_service(),
712 MaybeStartPasswordFieldOnFocusRequest(_, _, _, _, _))
713 .Times(1);
714 PasswordManagerClient* mojom_client = client.get();
715 mojom_client->CheckSafeBrowsingReputation(GURL("http://foo.com/submit"),
716 GURL("http://foo.com/iframe.html"));
717 }
718
719 // SafeBrowsing Delayed Warnings experiment can delay certain SafeBrowsing
720 // warnings until user interaction. This test checks that when a SafeBrowsing
721 // warning is delayed, password saving and filling is disabled on the page.
TEST_F(ChromePasswordManagerClientTest,SavingAndFillingDisabledConditionsDelayedWarnings)722 TEST_F(ChromePasswordManagerClientTest,
723 SavingAndFillingDisabledConditionsDelayedWarnings) {
724 std::unique_ptr<WebContents> test_web_contents(
725 content::WebContentsTester::CreateTestWebContents(
726 web_contents()->GetBrowserContext(), nullptr));
727
728 // Warnings are delayed until the user interacts with the page. This is
729 // achieved by attaching an observer (SafeBrowsingUserInteractionObserver) to
730 // the current WebContents. Create an observer and attach it to simulate a
731 // delayed warning.
732 auto safe_browsing_service =
733 base::MakeRefCounted<safe_browsing::TestSafeBrowsingService>();
734 auto ui_manager =
735 base::MakeRefCounted<safe_browsing::TestSafeBrowsingUIManager>(
736 safe_browsing_service);
737 security_interstitials::UnsafeResource resource;
738 safe_browsing::SafeBrowsingUserInteractionObserver::CreateForWebContents(
739 test_web_contents.get(), resource, /* is_main_frame= */ true, ui_manager);
740
741 auto client = std::make_unique<MockChromePasswordManagerClient>(
742 test_web_contents.get());
743 // Saving is disabled when the page has a delayed SafeBrowsing warning.
744 const GURL kUrlOn("https://accounts.google.com");
745 EXPECT_FALSE(client->IsSavingAndFillingEnabled(kUrlOn));
746 EXPECT_FALSE(client->IsFillingEnabled(kUrlOn));
747 EXPECT_FALSE(client->IsFillingFallbackEnabled(kUrlOn));
748 }
749
TEST_F(ChromePasswordManagerClientTest,VerifyMaybeProtectedPasswordEntryRequestCalled)750 TEST_F(ChromePasswordManagerClientTest,
751 VerifyMaybeProtectedPasswordEntryRequestCalled) {
752 std::unique_ptr<WebContents> test_web_contents(
753 content::WebContentsTester::CreateTestWebContents(
754 web_contents()->GetBrowserContext(), nullptr));
755 std::unique_ptr<MockChromePasswordManagerClient> client(
756 new MockChromePasswordManagerClient(test_web_contents.get()));
757
758 EXPECT_CALL(
759 *client->password_protection_service(),
760 MaybeStartProtectedPasswordEntryRequest(_, _, "username", _, _, true))
761 .Times(4);
762 std::vector<password_manager::MatchingReusedCredential> credentials = {
763 {"saved_domain.com", base::ASCIIToUTF16("username")}};
764
765 client->CheckProtectedPasswordEntry(
766 password_manager::metrics_util::PasswordType::SAVED_PASSWORD, "username",
767 credentials, true);
768 client->CheckProtectedPasswordEntry(
769 password_manager::metrics_util::PasswordType::PRIMARY_ACCOUNT_PASSWORD,
770 "username", credentials, true);
771 client->CheckProtectedPasswordEntry(
772 password_manager::metrics_util::PasswordType::OTHER_GAIA_PASSWORD,
773 "username", credentials, true);
774 client->CheckProtectedPasswordEntry(
775 password_manager::metrics_util::PasswordType::ENTERPRISE_PASSWORD,
776 "username", credentials, true);
777 }
778
TEST_F(ChromePasswordManagerClientTest,VerifyLogPasswordReuseDetectedEvent)779 TEST_F(ChromePasswordManagerClientTest, VerifyLogPasswordReuseDetectedEvent) {
780 std::unique_ptr<WebContents> test_web_contents(
781 content::WebContentsTester::CreateTestWebContents(
782 web_contents()->GetBrowserContext(), nullptr));
783 std::unique_ptr<MockChromePasswordManagerClient> client(
784 new MockChromePasswordManagerClient(test_web_contents.get()));
785 EXPECT_CALL(*client->password_protection_service(),
786 MaybeLogPasswordReuseDetectedEvent(test_web_contents.get()))
787 .Times(1);
788 client->LogPasswordReuseDetectedEvent();
789 }
790 #endif
791
TEST_F(ChromePasswordManagerClientTest,MissingUIDelegate)792 TEST_F(ChromePasswordManagerClientTest, MissingUIDelegate) {
793 // Checks that the saving fallback methods don't crash if there is no UI
794 // delegate. It can happen on ChromeOS login form, for example.
795 GURL kUrl("https://example.com/");
796 NavigateAndCommit(kUrl);
797 PasswordManagerClient* client = GetClient();
798 client->ShowManualFallbackForSaving(nullptr, false, false);
799 client->HideManualFallbackForSaving();
800 }
801
802 #if defined(OS_ANDROID)
803 class ChromePasswordManagerClientAndroidTest
804 : public ChromePasswordManagerClientTest {
805 protected:
806 std::unique_ptr<password_manager::ContentPasswordManagerDriver>
807 CreateContentPasswordManagerDriver(content::RenderFrameHost* rfh);
808
809 void SetUp() override;
810
811 void CreateManualFillingController(content::WebContents* web_contents);
812
controller()813 ManualFillingControllerImpl* controller() {
814 return ManualFillingControllerImpl::FromWebContents(web_contents());
815 }
816
view()817 MockManualFillingView* view() {
818 return static_cast<MockManualFillingView*>(controller()->view());
819 }
820
821 private:
822 autofill::TestAutofillClient test_autofill_client_;
823 NiceMock<MockPasswordAccessoryController> mock_pwd_controller_;
824 NiceMock<MockAddressAccessoryController> mock_address_controller_;
825 NiceMock<MockCreditCardAccessoryController> mock_cc_controller_;
826 };
827
SetUp()828 void ChromePasswordManagerClientAndroidTest::SetUp() {
829 ChromePasswordManagerClientTest::SetUp();
830 PasswordStoreFactory::GetInstance()->SetTestingFactory(
831 GetBrowserContext(),
832 base::BindRepeating(
833 &password_manager::BuildPasswordStore<
834 content::BrowserContext, password_manager::MockPasswordStore>));
835 }
836
837 std::unique_ptr<password_manager::ContentPasswordManagerDriver>
CreateContentPasswordManagerDriver(content::RenderFrameHost * rfh)838 ChromePasswordManagerClientAndroidTest::CreateContentPasswordManagerDriver(
839 content::RenderFrameHost* rfh) {
840 return std::make_unique<password_manager::ContentPasswordManagerDriver>(
841 rfh, GetClient(), &test_autofill_client_);
842 }
843
CreateManualFillingController(content::WebContents * web_contents)844 void ChromePasswordManagerClientAndroidTest::CreateManualFillingController(
845 content::WebContents* web_contents) {
846 ManualFillingControllerImpl::CreateForWebContentsForTesting(
847 web_contents, mock_pwd_controller_.AsWeakPtr(),
848 mock_address_controller_.AsWeakPtr(), mock_cc_controller_.AsWeakPtr(),
849 std::make_unique<NiceMock<MockManualFillingView>>());
850 }
851
TEST_F(ChromePasswordManagerClientAndroidTest,FocusedInputChangedNoFrameFillableField)852 TEST_F(ChromePasswordManagerClientAndroidTest,
853 FocusedInputChangedNoFrameFillableField) {
854 CreateManualFillingController(web_contents());
855 ASSERT_FALSE(web_contents()->GetFocusedFrame());
856
857 ChromePasswordManagerClient* client = GetClient();
858
859 std::unique_ptr<password_manager::ContentPasswordManagerDriver> driver =
860 CreateContentPasswordManagerDriver(main_rfh());
861 client->FocusedInputChanged(driver.get(),
862 FocusedFieldType::kFillablePasswordField);
863
864 PasswordGenerationController* pwd_generation_controller =
865 PasswordGenerationController::GetIfExisting(web_contents());
866 EXPECT_FALSE(pwd_generation_controller);
867 }
868
TEST_F(ChromePasswordManagerClientAndroidTest,FocusedInputChangedNoFrameNoField)869 TEST_F(ChromePasswordManagerClientAndroidTest,
870 FocusedInputChangedNoFrameNoField) {
871 CreateManualFillingController(web_contents());
872 std::unique_ptr<password_manager::ContentPasswordManagerDriver> driver =
873 CreateContentPasswordManagerDriver(main_rfh());
874
875 // Simulate that an element was focused before as far as the generation
876 // controller is concerned.
877 PasswordGenerationController* pwd_generation_controller =
878 PasswordGenerationController::GetOrCreate(web_contents());
879 pwd_generation_controller->FocusedInputChanged(
880 FocusedFieldType::kFillablePasswordField, driver.get()->AsWeakPtr());
881
882 ChromePasswordManagerClient* client = GetClient();
883
884 ASSERT_FALSE(web_contents()->GetFocusedFrame());
885 ASSERT_TRUE(pwd_generation_controller->GetActiveFrameDriver());
886 client->FocusedInputChanged(driver.get(), FocusedFieldType::kUnknown);
887
888 // Check that the event was processed by the generation controller and that
889 // the active frame driver was unset.
890 EXPECT_FALSE(pwd_generation_controller->GetActiveFrameDriver());
891 }
892
TEST_F(ChromePasswordManagerClientAndroidTest,FocusedInputChangedWrongFrame)893 TEST_F(ChromePasswordManagerClientAndroidTest, FocusedInputChangedWrongFrame) {
894 ChromePasswordManagerClient* client = GetClient();
895
896 // Set up the main frame.
897 NavigateAndCommit(GURL("https://example.com"));
898 FocusWebContentsOnMainFrame();
899 CreateManualFillingController(web_contents());
900
901 content::RenderFrameHost* subframe =
902 content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
903 std::unique_ptr<password_manager::ContentPasswordManagerDriver> driver =
904 CreateContentPasswordManagerDriver(subframe);
905 client->FocusedInputChanged(driver.get(),
906 FocusedFieldType::kFillablePasswordField);
907
908 PasswordGenerationController* pwd_generation_controller =
909 PasswordGenerationController::GetIfExisting(web_contents());
910
911 // Check that no generation controller was created, since this event should be
912 // rejected and not passed on to a generation controller.
913 EXPECT_FALSE(pwd_generation_controller);
914 }
915
TEST_F(ChromePasswordManagerClientAndroidTest,FocusedInputChangedGoodFrame)916 TEST_F(ChromePasswordManagerClientAndroidTest, FocusedInputChangedGoodFrame) {
917 ChromePasswordManagerClient* client = GetClient();
918 CreateManualFillingController(web_contents());
919
920 std::unique_ptr<password_manager::ContentPasswordManagerDriver> driver =
921 CreateContentPasswordManagerDriver(main_rfh());
922 FocusWebContentsOnMainFrame();
923 client->FocusedInputChanged(driver.get(),
924 FocusedFieldType::kFillablePasswordField);
925
926 PasswordGenerationController* pwd_generation_controller =
927 PasswordGenerationController::GetIfExisting(web_contents());
928 EXPECT_TRUE(pwd_generation_controller);
929 }
930
TEST_F(ChromePasswordManagerClientAndroidTest,SameDocumentNavigationDoesNotClearCache)931 TEST_F(ChromePasswordManagerClientAndroidTest,
932 SameDocumentNavigationDoesNotClearCache) {
933 auto origin = url::Origin::Create(GURL("https://example.com"));
934 PasswordForm form;
935 form.url = origin.GetURL();
936 form.username_value = base::ASCIIToUTF16("alice");
937 form.password_value = base::ASCIIToUTF16("S3cr3t");
938 GetClient()
939 ->GetCredentialCacheForTesting()
940 ->SaveCredentialsAndBlacklistedForOrigin(
941 {&form}, CredentialCache::IsOriginBlacklisted(false), origin);
942
943 // Check that a navigation within the same document does not clear the cache.
944 content::MockNavigationHandle handle(web_contents());
945 handle.set_is_same_document(true);
946 handle.set_has_committed(true);
947 static_cast<content::WebContentsObserver*>(GetClient())
948 ->DidFinishNavigation(&handle);
949
950 EXPECT_FALSE(GetClient()
951 ->GetCredentialCacheForTesting()
952 ->GetCredentialStore(origin)
953 .GetCredentials()
954 .empty());
955
956 // Check that a navigation to a different origin clears the cache.
957 NavigateAndCommit(GURL("https://example.org"));
958 EXPECT_TRUE(GetClient()
959 ->GetCredentialCacheForTesting()
960 ->GetCredentialStore(origin)
961 .GetCredentials()
962 .empty());
963 }
964
TEST_F(ChromePasswordManagerClientAndroidTest,HideFillingUIOnNavigatingAway)965 TEST_F(ChromePasswordManagerClientAndroidTest, HideFillingUIOnNavigatingAway) {
966 CreateManualFillingController(web_contents());
967 // Navigate to a URL with a bubble/popup.
968 GURL kUrl1("https://example.com/");
969 NavigateAndCommit(kUrl1);
970 EXPECT_TRUE(ChromePasswordManagerClient::CanShowBubbleOnURL(kUrl1));
971
972 // Navigating away should call Hide.
973 EXPECT_CALL(*view(), Hide());
974 GURL kUrl2("https://accounts.google.com");
975 NavigateAndCommit(kUrl2);
976 }
977 #endif // defined(OS_ANDROID)
978