Enables UKM recorder in iOS
Bug: 738938
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I75950dc5af48af4dcb8b26db9c093cc8837034fd
Reviewed-on: https://chromium-review.googlesource.com/776035
Commit-Queue: Moe Ahmadi (OOO until Nov. 27) <mahmadi@chromium.org>
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517724}diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index 8441c5a..39e88365 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -15,6 +15,7 @@
namespace metrics {
class UkmBrowserTest;
+class UkmEGTestHelper;
}
namespace ukm {
@@ -53,6 +54,7 @@
private:
friend ::metrics::UkmBrowserTest;
+ friend ::metrics::UkmEGTestHelper;
friend ::ukm::debug::DebugPage;
// UkmRecorder:
diff --git a/components/ukm/ukm_service.h b/components/ukm/ukm_service.h
index 0048ef1..5c70a842 100644
--- a/components/ukm/ukm_service.h
+++ b/components/ukm/ukm_service.h
@@ -25,6 +25,7 @@
namespace metrics {
class MetricsServiceClient;
class UkmBrowserTest;
+class UkmEGTestHelper;
}
namespace ukm {
@@ -76,8 +77,9 @@
static void RegisterPrefs(PrefRegistrySimple* registry);
private:
- friend ::ukm::debug::DebugPage;
friend ::metrics::UkmBrowserTest;
+ friend ::metrics::UkmEGTestHelper;
+ friend ::ukm::debug::DebugPage;
FRIEND_TEST_ALL_PREFIXES(UkmServiceTest, AddEntryWithEmptyMetrics);
FRIEND_TEST_ALL_PREFIXES(UkmServiceTest, EntryBuilderAndSerialization);
diff --git a/ios/chrome/browser/ios_chrome_main_parts.mm b/ios/chrome/browser/ios_chrome_main_parts.mm
index 796b8ce4..2194894 100644
--- a/ios/chrome/browser/ios_chrome_main_parts.mm
+++ b/ios/chrome/browser/ios_chrome_main_parts.mm
@@ -136,13 +136,6 @@
}
void IOSChromeMainParts::PreMainMessageLoopRun() {
- // This must occur at PreMainMessageLoopRun because |SetupMetrics()| uses the
- // blocking pool, which is disabled until the CreateThreads phase of startup.
- SetupMetrics();
-
- // Now that the file thread has been started, start recording.
- StartMetricsRecording();
-
application_context_->PreMainMessageLoopRun();
// ContentSettingsPattern need to be initialized before creating the
@@ -159,6 +152,15 @@
ios::ChromeBrowserState* last_used_browser_state =
browser_state_manager->GetLastUsedBrowserState();
+ // This must occur at PreMainMessageLoopRun because |SetupMetrics()| uses the
+ // blocking pool, which is disabled until the CreateThreads phase of startup.
+ // TODO(crbug.com/786494): Investigate whether metrics recording can be
+ // initialized consistently across iOS and non-iOS platforms
+ SetupMetrics();
+
+ // Now that the file thread has been started, start recording.
+ StartMetricsRecording();
+
#if BUILDFLAG(ENABLE_RLZ)
// Init the RLZ library. This just schedules a task on the file thread to be
// run sometime later. If this is the first run we record the installation
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index 08d4f88..5a1bbe7 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -7,6 +7,8 @@
sources = [
"field_trial_synchronizer.cc",
"field_trial_synchronizer.h",
+ "incognito_web_state_observer.h",
+ "incognito_web_state_observer.mm",
"ios_chrome_metrics_service_accessor.cc",
"ios_chrome_metrics_service_accessor.h",
"ios_chrome_metrics_service_client.h",
@@ -53,6 +55,7 @@
"//ios/chrome/browser/translate",
"//ios/chrome/browser/variations",
"//ios/chrome/browser/variations:ios_chrome_ui_string_overrider_factory",
+ "//ios/chrome/browser/web_state_list",
"//ios/chrome/common",
"//ios/web",
]
@@ -143,19 +146,34 @@
testonly = true
sources = [
"tab_usage_recorder_egtest.mm",
+ "ukm_egtest.mm",
]
deps = [
":metrics_internal",
":test_support",
"//base",
"//base/test:test_support",
+ "//components/browser_sync",
+ "//components/metrics",
+ "//components/metrics_services_manager",
"//components/strings",
+ "//components/ukm",
+ "//ios/chrome/app/strings:ios_strings_grit",
+ "//ios/chrome/browser",
+ "//ios/chrome/browser/metrics",
+ "//ios/chrome/browser/signin",
+ "//ios/chrome/browser/sync",
"//ios/chrome/browser/ui",
+ "//ios/chrome/browser/ui/authentication",
+ "//ios/chrome/browser/ui/authentication:authentication_ui",
+ "//ios/chrome/browser/ui/authentication:eg_test_support",
"//ios/chrome/browser/ui/settings",
+ "//ios/chrome/browser/ui/tab_switcher:egtest_support",
"//ios/chrome/browser/ui/toolbar/public",
"//ios/chrome/browser/ui/tools_menu",
"//ios/chrome/test/app:test_support",
"//ios/chrome/test/earl_grey:test_support",
+ "//ios/public/provider/chrome/browser/signin:test_support",
"//ios/testing:ios_test_support",
"//ios/web:earl_grey_test_support",
"//ios/web/public/test",
diff --git a/ios/chrome/browser/metrics/incognito_web_state_observer.h b/ios/chrome/browser/metrics/incognito_web_state_observer.h
new file mode 100644
index 0000000..f1d88e4
--- /dev/null
+++ b/ios/chrome/browser/metrics/incognito_web_state_observer.h
@@ -0,0 +1,57 @@
+// Copyright 2017 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.
+
+#ifndef IOS_CHROME_BROWSER_METRICS_INCOGNITO_WEB_STATE_OBSERVER_H_
+#define IOS_CHROME_BROWSER_METRICS_INCOGNITO_WEB_STATE_OBSERVER_H_
+
+#include "base/macros.h"
+
+#include "base/scoped_observer.h"
+#import "ios/chrome/browser/tabs/tab_model_list_observer.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
+
+// Interface for getting notified when WebStates get added/removed to/from an
+// incognito browser state. For example, implementations can invoke
+// TabModelList::IsOffTheRecordSessionActive() in the body of the observer
+// methods to learn if incognito session is currently active (i.e., at least one
+// incognito tab is open).
+class IncognitoWebStateObserver : public TabModelListObserver,
+ public WebStateListObserver {
+ public:
+ IncognitoWebStateObserver();
+ ~IncognitoWebStateObserver() override;
+
+ // TabModelListObserver:
+ void TabModelRegisteredWithBrowserState(
+ TabModel* tab_model,
+ ios::ChromeBrowserState* browser_state) override;
+ void TabModelUnregisteredFromBrowserState(
+ TabModel* tab_model,
+ ios::ChromeBrowserState* browser_state) override;
+
+ // WebStateListObserver:
+ void WebStateInsertedAt(WebStateList* web_state_list,
+ web::WebState* web_state,
+ int index,
+ bool activating) override;
+ void WebStateDetachedAt(WebStateList* web_state_list,
+ web::WebState* web_state,
+ int index) override;
+ void WebStateReplacedAt(WebStateList* web_state_list,
+ web::WebState* old_web_state,
+ web::WebState* new_web_state,
+ int index) override;
+
+ protected:
+ virtual void OnIncognitoWebStateAdded() = 0;
+ virtual void OnIncognitoWebStateRemoved() = 0;
+
+ private:
+ ScopedObserver<WebStateList, IncognitoWebStateObserver> scoped_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(IncognitoWebStateObserver);
+};
+
+#endif // IOS_CHROME_BROWSER_METRICS_INCOGNITO_WEB_STATE_OBSERVER_H_
diff --git a/ios/chrome/browser/metrics/incognito_web_state_observer.mm b/ios/chrome/browser/metrics/incognito_web_state_observer.mm
new file mode 100644
index 0000000..2cb9295
--- /dev/null
+++ b/ios/chrome/browser/metrics/incognito_web_state_observer.mm
@@ -0,0 +1,85 @@
+// Copyright 2017 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.
+
+#import "ios/chrome/browser/metrics/incognito_web_state_observer.h"
+
+#include <vector>
+
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#import "ios/chrome/browser/tabs/tab_model_list.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+IncognitoWebStateObserver::IncognitoWebStateObserver()
+ : scoped_observer_(this) {
+ TabModelList::AddObserver(this);
+
+ // Observe all existing off-the-record TabModels' WebStateLists.
+ std::vector<ios::ChromeBrowserState*> browser_states =
+ GetApplicationContext()
+ ->GetChromeBrowserStateManager()
+ ->GetLoadedBrowserStates();
+
+ for (ios::ChromeBrowserState* browser_state : browser_states) {
+ DCHECK(!browser_state->IsOffTheRecord());
+
+ if (!browser_state->HasOffTheRecordChromeBrowserState())
+ continue;
+ ios::ChromeBrowserState* otr_browser_state =
+ browser_state->GetOffTheRecordChromeBrowserState();
+
+ NSArray<TabModel*>* tab_models =
+ TabModelList::GetTabModelsForChromeBrowserState(otr_browser_state);
+ for (TabModel* tab_model in tab_models)
+ scoped_observer_.Add([tab_model webStateList]);
+ }
+}
+
+IncognitoWebStateObserver::~IncognitoWebStateObserver() {
+ TabModelList::RemoveObserver(this);
+}
+
+void IncognitoWebStateObserver::TabModelRegisteredWithBrowserState(
+ TabModel* tab_model,
+ ios::ChromeBrowserState* browser_state) {
+ if (browser_state->IsOffTheRecord() &&
+ !scoped_observer_.IsObserving([tab_model webStateList])) {
+ scoped_observer_.Add([tab_model webStateList]);
+ }
+}
+
+void IncognitoWebStateObserver::TabModelUnregisteredFromBrowserState(
+ TabModel* tab_model,
+ ios::ChromeBrowserState* browser_state) {
+ if (browser_state->IsOffTheRecord()) {
+ DCHECK(scoped_observer_.IsObserving([tab_model webStateList]));
+ scoped_observer_.Remove([tab_model webStateList]);
+ }
+}
+
+void IncognitoWebStateObserver::WebStateInsertedAt(WebStateList* web_state_list,
+ web::WebState* web_state,
+ int index,
+ bool activating) {
+ OnIncognitoWebStateAdded();
+}
+
+void IncognitoWebStateObserver::WebStateDetachedAt(WebStateList* web_state_list,
+ web::WebState* web_state,
+ int index) {
+ OnIncognitoWebStateRemoved();
+}
+
+void IncognitoWebStateObserver::WebStateReplacedAt(WebStateList* web_state_list,
+ web::WebState* old_web_state,
+ web::WebState* new_web_state,
+ int index) {
+ // This is invoked when a Tab is replaced by another Tab without any visible
+ // UI change. There is nothing to do since the number of Tabs haven't changed.
+}
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.cc b/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.cc
index e612332..01ec484 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.cc
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.cc
@@ -9,8 +9,26 @@
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/application_context.h"
+namespace {
+
+const bool* g_metrics_consent_for_testing = nullptr;
+
+} // namespace
+
+// static
+void IOSChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
+ const bool* value) {
+ DCHECK_NE(g_metrics_consent_for_testing == nullptr, value == nullptr)
+ << "Unpaired set/reset";
+
+ g_metrics_consent_for_testing = value;
+}
+
// static
bool IOSChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled() {
+ if (g_metrics_consent_for_testing)
+ return *g_metrics_consent_for_testing;
+
return IsMetricsReportingEnabled(GetApplicationContext()->GetLocalState());
}
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h b/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h
index f996378..203f690 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h
@@ -22,6 +22,12 @@
// Since these methods are private, each user has to be explicitly declared
// as a 'friend' below.
class IOSChromeMetricsServiceAccessor : public metrics::MetricsServiceAccessor {
+ public:
+ // If arg is non-null, the value will be returned from future calls to
+ // IsMetricsAndCrashReportingEnabled(). Pointer must be valid until it is
+ // reset to null here.
+ static void SetMetricsAndCrashReportingForTesting(const bool* value);
+
private:
friend class IOSChromeMetricsServicesManagerClient;
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
index 5b558cbc..b8701094 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
@@ -20,6 +20,7 @@
#include "components/omnibox/browser/omnibox_event_global_tracker.h"
#include "components/ukm/observers/history_delete_observer.h"
#include "components/ukm/observers/sync_disable_observer.h"
+#import "ios/chrome/browser/metrics/incognito_web_state_observer.h"
#include "ios/web/public/web_state/global_web_state_observer.h"
class IOSChromeStabilityMetricsProvider;
@@ -40,11 +41,11 @@
// IOSChromeMetricsServiceClient provides an implementation of
// MetricsServiceClient that depends on //ios/chrome/.
-class IOSChromeMetricsServiceClient
- : public metrics::MetricsServiceClient,
- public ukm::HistoryDeleteObserver,
- public ukm::SyncDisableObserver,
- public web::GlobalWebStateObserver {
+class IOSChromeMetricsServiceClient : public IncognitoWebStateObserver,
+ public metrics::MetricsServiceClient,
+ public ukm::HistoryDeleteObserver,
+ public ukm::SyncDisableObserver,
+ public web::GlobalWebStateObserver {
public:
~IOSChromeMetricsServiceClient() override;
@@ -86,6 +87,10 @@
void WebStateDidStartLoading(web::WebState* web_state) override;
void WebStateDidStopLoading(web::WebState* web_state) override;
+ // IncognitoWebStateObserver:
+ void OnIncognitoWebStateAdded() override;
+ void OnIncognitoWebStateRemoved() override;
+
metrics::EnableMetricsDefault GetMetricsReportingDefaultState() override;
private:
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
index ea4b847..c141286 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
@@ -57,7 +57,7 @@
#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
#include "ios/chrome/browser/sync/ios_chrome_sync_client.h"
#include "ios/chrome/browser/tab_parenting_global_observer.h"
-#include "ios/chrome/browser/tabs/tab_model_list.h"
+#import "ios/chrome/browser/tabs/tab_model_list.h"
#include "ios/chrome/browser/translate/translate_ranker_metrics_provider.h"
#include "ios/chrome/common/channel_info.h"
#include "ios/web/public/web_thread.h"
@@ -302,6 +302,16 @@
UpdateRunningServices();
}
+void IOSChromeMetricsServiceClient::OnIncognitoWebStateAdded() {
+ // Signal service manager to enable/disable UKM based on new state.
+ UpdateRunningServices();
+}
+
+void IOSChromeMetricsServiceClient::OnIncognitoWebStateRemoved() {
+ // Signal service manager to enable/disable UKM based on new state.
+ UpdateRunningServices();
+}
+
bool IOSChromeMetricsServiceClient::IsHistorySyncEnabledOnAllProfiles() {
return SyncDisableObserver::IsHistorySyncEnabledOnAllProfiles();
}
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
index 79be995..a16552a 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
@@ -116,9 +116,5 @@
}
bool IOSChromeMetricsServicesManagerClient::IsIncognitoSessionActive() {
- // return ::IsOffTheRecordSessionActive();
- // TODO(crbug.com/734091): Conservatively set to true until there is a test
- // to ensure it gets re-queried when an incognito tab is opened. This
- // effectively disables UKM.
- return true;
+ return TabModelList::IsOffTheRecordSessionActive();
}
diff --git a/ios/chrome/browser/metrics/ukm_egtest.mm b/ios/chrome/browser/metrics/ukm_egtest.mm
new file mode 100644
index 0000000..9878be9
--- /dev/null
+++ b/ios/chrome/browser/metrics/ukm_egtest.mm
@@ -0,0 +1,332 @@
+// Copyright 2017 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.
+
+#import <EarlGrey/EarlGrey.h>
+#import <XCTest/XCTest.h>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/ukm/ukm_service.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h"
+#import "ios/chrome/browser/ui/authentication/signin_earlgrey_utils.h"
+#import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_egtest_util.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#import "ios/chrome/test/app/chrome_test_util.h"
+#import "ios/chrome/test/app/sync_test_util.h"
+#import "ios/chrome/test/app/tab_test_util.h"
+#import "ios/chrome/test/earl_grey/chrome_actions.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
+#import "ios/chrome/test/earl_grey/chrome_matchers.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h"
+#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service.h"
+#import "ios/testing/wait_util.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using chrome_test_util::AccountsSyncButton;
+using chrome_test_util::ButtonWithAccessibilityLabelId;
+using chrome_test_util::GetIncognitoTabCount;
+using chrome_test_util::IsIncognitoMode;
+using chrome_test_util::IsSyncInitialized;
+using chrome_test_util::NavigationBarDoneButton;
+using chrome_test_util::SecondarySignInButton;
+using chrome_test_util::SettingsAccountButton;
+using chrome_test_util::SettingsAccountButton;
+using chrome_test_util::SignOutAccountsButton;
+using chrome_test_util::SyncSwitchCell;
+using chrome_test_util::TabletTabSwitcherCloseButton;
+using chrome_test_util::TabletTabSwitcherOpenTabsPanelButton;
+using chrome_test_util::TurnSyncSwitchOn;
+
+namespace metrics {
+
+// Helper class that provides access to UKM internals.
+class UkmEGTestHelper {
+ public:
+ UkmEGTestHelper() {}
+
+ static bool ukm_enabled() {
+ auto* service = ukm_service();
+ return service ? service->recording_enabled_ : false;
+ }
+
+ static uint64_t client_id() {
+ auto* service = ukm_service();
+ return service ? service->client_id_ : 0;
+ }
+
+ private:
+ static ukm::UkmService* ukm_service() {
+ return GetApplicationContext()
+ ->GetMetricsServicesManager()
+ ->GetUkmService();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UkmEGTestHelper);
+};
+
+} // namespace metrics
+
+namespace {
+
+bool g_metrics_enabled = false;
+
+// Constant for timeout while waiting for asynchronous sync and UKM operations.
+const NSTimeInterval kSyncUKMOperationsTimeout = 10.0;
+
+void AssertSyncInitialized(bool is_initialized) {
+ ConditionBlock condition = ^{
+ return IsSyncInitialized() == is_initialized;
+ };
+ GREYAssert(testing::WaitUntilConditionOrTimeout(kSyncUKMOperationsTimeout,
+ condition),
+ @"Failed to assert whether Sync was initialized or not.");
+}
+
+void AssertUKMEnabled(bool is_enabled) {
+ ConditionBlock condition = ^{
+ return metrics::UkmEGTestHelper::ukm_enabled() == is_enabled;
+ };
+ GREYAssert(testing::WaitUntilConditionOrTimeout(kSyncUKMOperationsTimeout,
+ condition),
+ @"Failed to assert whether UKM was enabled or not.");
+}
+
+void OpenNewIncognitoTab() {
+ NSUInteger incognito_tab_count = GetIncognitoTabCount();
+ chrome_test_util::OpenNewIncognitoTab();
+ [ChromeEarlGrey waitForIncognitoTabCount:(incognito_tab_count + 1)];
+ GREYAssert(IsIncognitoMode(), @"Failed to switch to incognito mode.");
+}
+
+void CloseAllIncognitoTabs() {
+ GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+ [ChromeEarlGrey waitForIncognitoTabCount:0];
+ if (IsIPadIdiom()) {
+ // Switch to the non-incognito panel and leave the tab switcher.
+ [[EarlGrey selectElementWithMatcher:TabletTabSwitcherOpenTabsPanelButton()]
+ performAction:grey_tap()];
+ [[EarlGrey selectElementWithMatcher:TabletTabSwitcherCloseButton()]
+ performAction:grey_tap()];
+ }
+ GREYAssert(!IsIncognitoMode(), @"Failed to switch to normal mode.");
+}
+
+// Signs in to sync.
+void SignIn() {
+ ChromeIdentity* identity = [SigninEarlGreyUtils fakeIdentity1];
+ ios::FakeChromeIdentityService::GetInstanceFromChromeProvider()->AddIdentity(
+ identity);
+
+ [ChromeEarlGreyUI openSettingsMenu];
+ [ChromeEarlGreyUI tapSettingsMenuButton:SecondarySignInButton()];
+ [ChromeEarlGreyUI signInToIdentityByEmail:identity.userEmail];
+ [ChromeEarlGreyUI confirmSigninConfirmationDialog];
+ [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
+ performAction:grey_tap()];
+
+ [SigninEarlGreyUtils assertSignedInWithIdentity:identity];
+}
+
+// Signs in to sync by tapping the sign-in promo view.
+void SignInWithPromo() {
+ [ChromeEarlGreyUI openSettingsMenu];
+ [SigninEarlGreyUtils
+ checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+ [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+ kSigninPromoPrimaryButtonId)]
+ performAction:grey_tap()];
+ [ChromeEarlGreyUI confirmSigninConfirmationDialog];
+ [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
+ performAction:grey_tap()];
+
+ [SigninEarlGreyUtils
+ assertSignedInWithIdentity:[SigninEarlGreyUtils fakeIdentity1]];
+}
+
+// Signs out of sync.
+void SignOut() {
+ [ChromeEarlGreyUI openSettingsMenu];
+ [[EarlGrey selectElementWithMatcher:SettingsAccountButton()]
+ performAction:grey_tap()];
+ [ChromeEarlGreyUI tapAccountsMenuButton:SignOutAccountsButton()];
+ [[EarlGrey selectElementWithMatcher:
+ ButtonWithAccessibilityLabelId(
+ IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE)]
+ performAction:grey_tap()];
+ [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
+ performAction:grey_tap()];
+
+ [SigninEarlGreyUtils assertSignedOut];
+}
+
+} // namespace
+
+// UKM tests.
+@interface UKMTestCase : ChromeTestCase
+
+@end
+
+@implementation UKMTestCase
+
++ (void)setUp {
+ [super setUp];
+ if (!base::FeatureList::IsEnabled(ukm::kUkmFeature)) {
+ // ukm::kUkmFeature feature is not enabled. You need to pass
+ // --enable-features=Ukm command line argument in order to run this test.
+ DCHECK(false);
+ }
+}
+
+- (void)setUp {
+ [super setUp];
+
+ AssertSyncInitialized(false);
+ AssertUKMEnabled(false);
+
+ // Enable sync.
+ SignIn();
+ AssertSyncInitialized(true);
+
+ // Grant metrics consent and update MetricsServicesManager.
+ GREYAssert(!g_metrics_enabled, @"Unpaired set/reset of user consent.");
+ g_metrics_enabled = true;
+ IOSChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
+ &g_metrics_enabled);
+ GetApplicationContext()->GetMetricsServicesManager()->UpdateUploadPermissions(
+ true);
+ AssertUKMEnabled(true);
+}
+
+- (void)tearDown {
+ AssertSyncInitialized(true);
+ AssertUKMEnabled(true);
+
+ // Revoke metrics consent and update MetricsServicesManager.
+ GREYAssert(g_metrics_enabled, @"Unpaired set/reset of user consent.");
+ g_metrics_enabled = false;
+ GetApplicationContext()->GetMetricsServicesManager()->UpdateUploadPermissions(
+ true);
+ IOSChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
+ nullptr);
+ AssertUKMEnabled(false);
+
+ // Disable sync.
+ SignOut();
+ AssertSyncInitialized(false);
+
+ [super tearDown];
+}
+
+// Make sure that UKM is disabled while an incognito tab is open.
+- (void)testIncognito {
+ uint64_t original_client_id = metrics::UkmEGTestHelper::client_id();
+
+ OpenNewIncognitoTab();
+
+ AssertUKMEnabled(false);
+
+ CloseAllIncognitoTabs();
+
+ AssertUKMEnabled(true);
+ // Client ID should not have been reset.
+ GREYAssert(original_client_id == metrics::UkmEGTestHelper::client_id(),
+ @"Client ID was reset.");
+}
+
+// Make sure that UKM is disabled when sync is not enabled.
+- (void)testNoSync {
+ uint64_t original_client_id = metrics::UkmEGTestHelper::client_id();
+
+ SignOut();
+
+ AssertUKMEnabled(false);
+
+ SignInWithPromo();
+
+ AssertUKMEnabled(true);
+ // Client ID should not have been reset.
+ GREYAssert(original_client_id == metrics::UkmEGTestHelper::client_id(),
+ @"Client ID was reset.");
+}
+
+// Make sure that UKM is disabled when sync is disabled.
+- (void)testDisableSync {
+ uint64_t original_client_id = metrics::UkmEGTestHelper::client_id();
+
+ [ChromeEarlGreyUI openSettingsMenu];
+ // Open accounts settings, then sync settings.
+ [[EarlGrey selectElementWithMatcher:SettingsAccountButton()]
+ performAction:grey_tap()];
+ [[EarlGrey selectElementWithMatcher:AccountsSyncButton()]
+ performAction:grey_tap()];
+ // Toggle "Sync Everything" then "History" switches off.
+ [[EarlGrey selectElementWithMatcher:SyncSwitchCell(
+ l10n_util::GetNSString(
+ IDS_IOS_SYNC_EVERYTHING_TITLE),
+ YES)]
+ performAction:TurnSyncSwitchOn(NO)];
+ [[EarlGrey
+ selectElementWithMatcher:SyncSwitchCell(l10n_util::GetNSString(
+ IDS_SYNC_DATATYPE_TYPED_URLS),
+ YES)]
+ performAction:TurnSyncSwitchOn(NO)];
+
+ AssertUKMEnabled(false);
+
+ // Toggle "History" then "Sync Everything" switches on.
+ [[EarlGrey
+ selectElementWithMatcher:SyncSwitchCell(l10n_util::GetNSString(
+ IDS_SYNC_DATATYPE_TYPED_URLS),
+ NO)]
+ performAction:TurnSyncSwitchOn(YES)];
+ [[EarlGrey selectElementWithMatcher:SyncSwitchCell(
+ l10n_util::GetNSString(
+ IDS_IOS_SYNC_EVERYTHING_TITLE),
+ NO)]
+ performAction:TurnSyncSwitchOn(YES)];
+
+ AssertUKMEnabled(true);
+ // Client ID should have been reset.
+ GREYAssert(original_client_id != metrics::UkmEGTestHelper::client_id(),
+ @"Client ID was not reset.");
+
+ [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
+ performAction:grey_tap()];
+}
+
+// Make sure that UKM is disabled when metrics consent is revoked.
+- (void)testNoConsent {
+ uint64_t original_client_id = metrics::UkmEGTestHelper::client_id();
+
+ // Revoke metrics consent and update MetricsServicesManager.
+ g_metrics_enabled = false;
+ GetApplicationContext()->GetMetricsServicesManager()->UpdateUploadPermissions(
+ true);
+
+ AssertUKMEnabled(false);
+
+ // Grant metrics consent and update MetricsServicesManager.
+ g_metrics_enabled = true;
+ GetApplicationContext()->GetMetricsServicesManager()->UpdateUploadPermissions(
+ true);
+
+ AssertUKMEnabled(true);
+ // Client ID should have been reset.
+ GREYAssert(original_client_id != metrics::UkmEGTestHelper::client_id(),
+ @"Client ID was not reset.");
+}
+
+@end
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index d9db9136..5cd9f9b 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -208,6 +208,7 @@
"//ios/chrome/browser/ui/omnibox:omnibox_internal",
"//ios/chrome/browser/ui/payments:payments_ui",
"//ios/chrome/browser/ui/settings:settings",
+ "//ios/chrome/browser/ui/settings/cells",
"//ios/chrome/browser/ui/static_content",
"//ios/chrome/browser/ui/toolbar/public",
"//ios/chrome/browser/ui/tools_menu",
diff --git a/ios/chrome/test/earl_grey/chrome_actions.h b/ios/chrome/test/earl_grey/chrome_actions.h
index 53448a5f..726893a 100644
--- a/ios/chrome/test/earl_grey/chrome_actions.h
+++ b/ios/chrome/test/earl_grey/chrome_actions.h
@@ -24,6 +24,9 @@
// state.
id<GREYAction> TurnCollectionViewSwitchOn(BOOL on);
+// Action to turn the switch of a SyncSwitchCell to the given |on| state.
+id<GREYAction> TurnSyncSwitchOn(BOOL on);
+
} // namespace chrome_test_util
#endif // IOS_CHROME_TEST_EARL_GREY_CHROME_ACTIONS_H_
diff --git a/ios/chrome/test/earl_grey/chrome_actions.mm b/ios/chrome/test/earl_grey/chrome_actions.mm
index 229479a..e0a5bac4 100644
--- a/ios/chrome/test/earl_grey/chrome_actions.mm
+++ b/ios/chrome/test/earl_grey/chrome_actions.mm
@@ -6,6 +6,7 @@
#import "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
+#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/web/public/test/earl_grey/web_view_actions.h"
@@ -36,7 +37,27 @@
base::mac::ObjCCastStrict<CollectionViewSwitchCell>(
collectionViewCell);
UISwitch* switchView = switchCell.switchView;
- if (switchView.on ^ on) {
+ if (switchView.on != on) {
+ id<GREYAction> longPressAction = [GREYActions
+ actionForLongPressWithDuration:kGREYLongPressDefaultDuration];
+ return [longPressAction perform:switchView error:errorOrNil];
+ }
+ return YES;
+ }];
+}
+
+id<GREYAction> TurnSyncSwitchOn(BOOL on) {
+ id<GREYMatcher> constraints = grey_not(grey_systemAlertViewShown());
+ NSString* actionName = [NSString
+ stringWithFormat:@"Turn sync switch to %@ state", on ? @"ON" : @"OFF"];
+ return [GREYActionBlock
+ actionWithName:actionName
+ constraints:constraints
+ performBlock:^BOOL(id syncSwitchCell, __strong NSError** errorOrNil) {
+ SyncSwitchCell* switchCell =
+ base::mac::ObjCCastStrict<SyncSwitchCell>(syncSwitchCell);
+ UISwitch* switchView = switchCell.switchView;
+ if (switchView.on != on) {
id<GREYAction> longPressAction = [GREYActions
actionForLongPressWithDuration:kGREYLongPressDefaultDuration];
return [longPressAction perform:switchView error:errorOrNil];
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index 8a5e022..374c479 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -74,6 +74,11 @@
id<GREYMatcher> CollectionViewSwitchCell(NSString* accessibilityIdentifier,
BOOL isOn);
+// Matcher for SyncSwitchCell.
+// TODO(crbug.com/684139): Update |is_on| to something more obvious from
+// callsites.
+id<GREYMatcher> SyncSwitchCell(NSString* accessibilityLabel, BOOL is_on);
+
// Matcher for the Open in New Tab option in the context menu when long pressing
// a link.
id<GREYMatcher> OpenLinkInNewTabButton();
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index 03c76402..984a505e 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -20,6 +20,7 @@
#import "ios/chrome/browser/ui/payments/payment_request_picker_view_controller.h"
#import "ios/chrome/browser/ui/payments/payment_request_view_controller.h"
#import "ios/chrome/browser/ui/settings/accounts_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/import_data_collection_view_controller.h"
#import "ios/chrome/browser/ui/settings/settings_collection_view_controller.h"
@@ -170,6 +171,14 @@
nil);
}
+id<GREYMatcher> SyncSwitchCell(NSString* accessibilityLabel, BOOL is_on) {
+ return grey_allOf(grey_accessibilityLabel(accessibilityLabel),
+ grey_accessibilityValue(
+ is_on ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+ : l10n_util::GetNSString(IDS_IOS_SETTING_OFF)),
+ grey_sufficientlyVisible(), nil);
+}
+
id<GREYMatcher> OpenLinkInNewTabButton() {
return ButtonWithAccessibilityLabelId(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
}
diff --git a/services/metrics/public/cpp/ukm_recorder.cc b/services/metrics/public/cpp/ukm_recorder.cc
index 58b87eee..70a4c51 100644
--- a/services/metrics/public/cpp/ukm_recorder.cc
+++ b/services/metrics/public/cpp/ukm_recorder.cc
@@ -13,11 +13,7 @@
namespace ukm {
-#if defined(OS_IOS)
-const base::Feature kUkmFeature = {"Ukm", base::FEATURE_DISABLED_BY_DEFAULT};
-#else
const base::Feature kUkmFeature = {"Ukm", base::FEATURE_ENABLED_BY_DEFAULT};
-#endif
UkmRecorder::UkmRecorder() = default;