diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index d869fe757f0d6..0d0a8bc8d0f11 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn @@ -356,7 +356,6 @@ android_library("chrome_java") { "//chrome/browser/download/android:java_resources", "//chrome/browser/enterprise/util:java", "//chrome/browser/feature_engagement:java", - "//chrome/browser/feature_guide/notifications:factory_java", "//chrome/browser/feature_guide/notifications:java", "//chrome/browser/feed/android:java", "//chrome/browser/feedback/android:java", @@ -752,7 +751,6 @@ java_group("chrome_all_java") { "//chrome/browser/content_creation/notes/internal/android:java", "//chrome/browser/content_creation/reactions/internal/android:java", "//chrome/browser/download/internal/android:java", - "//chrome/browser/feature_guide/notifications/internal:internal_java", "//chrome/browser/page_annotations/android:java", "//chrome/browser/password_check:internal_java", "//chrome/browser/password_edit_dialog/android:java", @@ -3884,6 +3882,8 @@ generate_jni("chrome_jni_headers") { "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategory.java", "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTile.java", "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java", + "java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java", + "java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java", "java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java", "java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java", "java/src/org/chromium/chrome/browser/firstrun/FirstRunUtils.java", diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index f9934ad79d175..c166fd22ec945 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni @@ -591,6 +591,8 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/explore_sites/StableScrollLayoutManager.java", "java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java", "java/src/org/chromium/chrome/browser/externalnav/IntentWithRequestMetadataHandler.java", + "java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java", + "java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java", "java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java", "java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java", "java/src/org/chromium/chrome/browser/feedback/ConnectivityFeedbackSource.java", diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/DIR_METADATA b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/DIR_METADATA new file mode 100644 index 0000000000000..81f3a27a0d51f --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/DIR_METADATA @@ -0,0 +1,4 @@ +monorail { + component: "Upboarding" +} +team_email: "chrome-upboarding-eng@google.com" diff --git a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java similarity index 67% rename from chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java rename to chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java index 975e41d466783..ade6532f900f9 100644 --- a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java @@ -13,7 +13,6 @@ @JNINamespace("feature_guide") public final class FeatureNotificationGuideBridge implements FeatureNotificationGuideService { private long mNativeFeatureNotificationGuideBridge; - private Delegate mDelegate; @CalledByNative private static FeatureNotificationGuideBridge create(long nativePtr) { @@ -29,24 +28,16 @@ private FeatureNotificationGuideBridge(long nativePtr) { mNativeFeatureNotificationGuideBridge = nativePtr; } - @Override - public void setDelegate(Delegate delegate) { - assert mDelegate == null; - mDelegate = delegate; - } - @CalledByNative - private String getNotificationTitle(int featureType) { - return mDelegate.getNotificationTitle(featureType); + private String getNotificationTitle(@FeatureType int featureType) { + return null; } @CalledByNative - private String getNotificationMessage(int featureType) { - return mDelegate.getNotificationMessage(featureType); + private String getNotificationMessage(@FeatureType int featureType) { + return null; } @CalledByNative - private void onNotificationClick(int featureType) { - mDelegate.onNotificationClick(featureType); - } + private void onNotificationClick(@FeatureType int featureType) {} } diff --git a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java similarity index 100% rename from chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java rename to chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/OWNERS new file mode 100644 index 0000000000000..ffc05827cb775 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/OWNERS @@ -0,0 +1 @@ +shaktisahu@chromium.org diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index d498e6b942aee..9a8b9fe835e74 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -3116,7 +3116,9 @@ static_library("browser") { "enterprise/reporting/reporting_delegate_factory_android.h", "enterprise/util/android_enterprise_info.cc", "enterprise/util/android_enterprise_info.h", - "feature_guide/notifications/internal/android/feature_notification_guide_service_factory_android.cc", + "feature_guide/notifications/android/feature_notification_guide_bridge.cc", + "feature_guide/notifications/android/feature_notification_guide_bridge.h", + "feature_guide/notifications/android/feature_notification_guide_service_factory_android.cc", "feed/android/background_refresh_task.cc", "feed/android/background_refresh_task.h", "feed/android/feed_image_fetch_client.cc", @@ -3383,7 +3385,6 @@ static_library("browser") { "//chrome/browser/device_reauth/android:jni_headers", "//chrome/browser/download/internal/android", "//chrome/browser/endpoint_fetcher:jni_headers", - "//chrome/browser/feature_guide/notifications/internal:jni_headers", "//chrome/browser/feed/android:jni_headers", "//chrome/browser/feedback/android", "//chrome/browser/flags:flags_android", diff --git a/chrome/browser/feature_guide/notifications/BUILD.gn b/chrome/browser/feature_guide/notifications/BUILD.gn index 5c850d6c432d5..35172cc6ce9c5 100644 --- a/chrome/browser/feature_guide/notifications/BUILD.gn +++ b/chrome/browser/feature_guide/notifications/BUILD.gn @@ -10,6 +10,8 @@ if (is_android) { source_set("public") { sources = [ + "config.cc", + "config.h", "feature_notification_guide_service.cc", "feature_notification_guide_service.h", "feature_type.h", @@ -40,19 +42,6 @@ if (is_android) { ] srcjar_deps = [ ":jni_enums" ] } - - android_library_factory("factory_java") { - # These deps will be inherited by the resulting android_library target. - deps = [ - ":java", - "//base:base_java", - "//chrome/browser/profiles/android:java", - ] - - # This internal file will be replaced by a generated file so the resulting - # android_library target does not actually depend on this internal file. - sources = [ "internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java" ] - } } group("unit_tests") { diff --git a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.cc b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.cc similarity index 62% rename from chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.cc rename to chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.cc index 89139f8e5e6ba..1f5e45d765e9a 100644 --- a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.cc +++ b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.cc @@ -2,47 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.h" +#include "chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h" #include #include "base/android/jni_android.h" #include "base/android/jni_string.h" +#include "chrome/android/chrome_jni_headers/FeatureNotificationGuideBridge_jni.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" #include "chrome/browser/feature_guide/notifications/feature_type.h" -#include "chrome/browser/feature_guide/notifications/internal/jni_headers/FeatureNotificationGuideBridge_jni.h" namespace feature_guide { -namespace { -const char kFeatureNotificationGuideBridgeKey[] = - "feature_notification_guide_bridge"; -} // namespace - -// static -FeatureNotificationGuideBridge* -FeatureNotificationGuideBridge::GetFeatureNotificationGuideBridge( - FeatureNotificationGuideService* feature_notification_guide_service) { - if (!feature_notification_guide_service->GetUserData( - kFeatureNotificationGuideBridgeKey)) { - feature_notification_guide_service->SetUserData( - kFeatureNotificationGuideBridgeKey, - std::make_unique( - feature_notification_guide_service)); - } - - return static_cast( - feature_notification_guide_service->GetUserData( - kFeatureNotificationGuideBridgeKey)); -} ScopedJavaLocalRef FeatureNotificationGuideBridge::GetJavaObj() { return ScopedJavaLocalRef(java_obj_); } -FeatureNotificationGuideBridge::FeatureNotificationGuideBridge( - FeatureNotificationGuideService* feature_notification_guide_service) - : feature_notification_guide_service_(feature_notification_guide_service) { - DCHECK(feature_notification_guide_service); +FeatureNotificationGuideBridge::FeatureNotificationGuideBridge() { JNIEnv* env = base::android::AttachCurrentThread(); java_obj_.Reset(env, Java_FeatureNotificationGuideBridge_create( env, reinterpret_cast(this)) diff --git a/chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h new file mode 100644 index 0000000000000..633bca43e2b91 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h @@ -0,0 +1,42 @@ +// Copyright 2021 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 CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ +#define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ + +#include + +#include "base/android/jni_android.h" +#include "base/supports_user_data.h" +#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" + +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace feature_guide { +class FeatureNotificationGuideService; + +// Contains JNI methods needed by the feature notification guide. +class FeatureNotificationGuideBridge + : public base::SupportsUserData::Data, + public FeatureNotificationGuideService::Delegate { + public: + FeatureNotificationGuideBridge(); + ~FeatureNotificationGuideBridge() override; + + ScopedJavaLocalRef GetJavaObj(); + std::u16string GetNotificationTitle(FeatureType feature) override; + std::u16string GetNotificationMessage(FeatureType feature) override; + void OnNotificationClick(FeatureType feature) override; + + private: + // A reference to the Java counterpart of this class. See + // FeatureNotificationGuideBridge.java. + ScopedJavaGlobalRef java_obj_; +}; + +} // namespace feature_guide + +#endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ diff --git a/chrome/browser/feature_guide/notifications/android/feature_notification_guide_service_factory_android.cc b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_service_factory_android.cc new file mode 100644 index 0000000000000..a9b4bfc9cf2f5 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/android/feature_notification_guide_service_factory_android.cc @@ -0,0 +1,26 @@ +// Copyright 2021 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 "base/android/scoped_java_ref.h" +#include "chrome/android/chrome_jni_headers/FeatureNotificationGuideServiceFactory_jni.h" +#include "chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h" +#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.h" +#include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_android.h" + +static base::android::ScopedJavaLocalRef +JNI_FeatureNotificationGuideServiceFactory_GetForProfile( + JNIEnv* env, + const base::android::JavaParamRef& jprofile) { + Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile); + DCHECK(profile); + feature_guide::FeatureNotificationGuideServiceImpl* service = + static_cast( + feature_guide::FeatureNotificationGuideServiceFactory::GetForProfile( + profile->GetOriginalProfile())); + return static_cast( + service->GetDelegate()) + ->GetJavaObj(); +} diff --git a/chrome/browser/feature_guide/notifications/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideService.java b/chrome/browser/feature_guide/notifications/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideService.java index 3cfc084b00a81..0d8edf2d5c726 100644 --- a/chrome/browser/feature_guide/notifications/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideService.java +++ b/chrome/browser/feature_guide/notifications/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideService.java @@ -7,34 +7,4 @@ /** * Central class representing feature notification guide. */ -public interface FeatureNotificationGuideService { - /** - * A delegate containing helper methods needed by the service, such as providing notification - * texts, and handling notification interactions. - */ - public interface Delegate { - /** - * Called when the notification associated with {@code featureType} is clicked. - * @param featureType The {@link FeatureType} for the notification. - */ - void onNotificationClick(@FeatureType int featureType); - - /** - * Called to get the notification title text associated with {@code featureType}. - * @param featureType The {@link FeatureType} for the notification. - */ - String getNotificationTitle(@FeatureType int featureType); - - /** - * Called to get the notification body text associated with {@code featureType}. - * @param featureType The {@link FeatureType} for the notification. - */ - String getNotificationMessage(@FeatureType int featureType); - } - - /** - * Called by the embedder to set the delegate. - * @param delegate The {@link Delegate} to handle chrome layer logic. - */ - void setDelegate(Delegate delegate); -} \ No newline at end of file +public interface FeatureNotificationGuideService {} diff --git a/chrome/browser/feature_guide/notifications/config.cc b/chrome/browser/feature_guide/notifications/config.cc new file mode 100644 index 0000000000000..7ca93d6dae84b --- /dev/null +++ b/chrome/browser/feature_guide/notifications/config.cc @@ -0,0 +1,17 @@ +// Copyright 2021 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 "chrome/browser/feature_guide/notifications/config.h" + +namespace feature_guide { + +Config::Config() = default; + +Config::~Config() = default; + +Config::Config(const Config& other) = default; + +Config& Config::operator=(const Config& other) = default; + +} // namespace feature_guide diff --git a/chrome/browser/feature_guide/notifications/config.h b/chrome/browser/feature_guide/notifications/config.h new file mode 100644 index 0000000000000..f62e65d59b502 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/config.h @@ -0,0 +1,33 @@ +// Copyright 2021 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 CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_CONFIG_H_ +#define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_CONFIG_H_ + +#include + +#include "base/time/time.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" + +namespace feature_guide { + +// Contains various finch configuration params used by the feature notification +// guide. +struct Config { + Config(); + ~Config(); + + Config(const Config& other); + Config& operator=(const Config& other); + + // The list of features enabled via finch for showing feature notifications. + std::vector enabled_features; + + // Relative start time for launching the notification. + base::TimeDelta notification_deliver_time_delta; +}; + +} // namespace feature_guide + +#endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_CONFIG_H_ diff --git a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.cc b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.cc index 0bbd1870aa61b..46ecde90c8a45 100644 --- a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.cc +++ b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.cc @@ -17,4 +17,16 @@ FeatureNotificationGuideService::FeatureNotificationGuideService() = default; FeatureNotificationGuideService::~FeatureNotificationGuideService() = default; +FeatureNotificationGuideService::Delegate::~Delegate() = default; + +void FeatureNotificationGuideService::Delegate::SetService( + FeatureNotificationGuideService* service) { + service_ = service; +} + +FeatureNotificationGuideService* +FeatureNotificationGuideService::Delegate::GetService() { + return service_; +} + } // namespace feature_guide diff --git a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h index 904e1b351d52c..a90ea26f5e1fd 100644 --- a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h +++ b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h @@ -12,6 +12,7 @@ #include "base/feature_list.h" #include "base/supports_user_data.h" #include "chrome/browser/feature_guide/notifications/feature_type.h" +#include "chrome/browser/notifications/scheduler/public/notification_scheduler_client.h" #include "components/keyed_service/core/keyed_service.h" namespace notifications { @@ -32,6 +33,30 @@ extern const base::Feature kFeatureNotificationGuide; class FeatureNotificationGuideService : public KeyedService, public base::SupportsUserData { public: + // A delegate to help with chrome layer dependencies, such as providing + // notification texts, and handling notification interactions. + class Delegate { + public: + // Returns the notification title text associated with the |feature|. + virtual std::u16string GetNotificationTitle(FeatureType feature) = 0; + + // Called to get the notification body text associated with the |feature|. + virtual std::u16string GetNotificationMessage(FeatureType feature) = 0; + + // Called when the notification associated with the given |feature| is + // clicked. + virtual void OnNotificationClick(FeatureType feature) = 0; + + // Getter/Setter method for the service. + FeatureNotificationGuideService* GetService(); + void SetService(FeatureNotificationGuideService* service); + + virtual ~Delegate(); + + private: + FeatureNotificationGuideService* service_{nullptr}; + }; + using NotificationDataCallback = base::OnceCallback)>; @@ -58,6 +83,8 @@ class FeatureNotificationGuideService : public KeyedService, using ServiceGetter = base::RepeatingCallback; + +// Factory method to create the service. std::unique_ptr CreateFeatureNotificationGuideNotificationClient(ServiceGetter service_getter); diff --git a/chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.cc b/chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.cc index d9b39533c7776..a86d3067fb6c1 100644 --- a/chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.cc +++ b/chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.cc @@ -4,10 +4,14 @@ #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.h" +#include "base/feature_list.h" #include "base/memory/singleton.h" #include "base/time/default_clock.h" +#include "build/build_config.h" #include "chrome/browser/feature_engagement/tracker_factory.h" +#include "chrome/browser/feature_guide/notifications/config.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h" #include "chrome/browser/notifications/scheduler/notification_schedule_service_factory.h" #include "chrome/browser/profiles/profile.h" @@ -15,7 +19,32 @@ #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" +#if defined(OS_ANDROID) +#include "chrome/browser/feature_guide/notifications/android/feature_notification_guide_bridge.h" +#endif + namespace feature_guide { +namespace { + +// Default notification time interval in days. +constexpr int kDefaultNotificationTimeIntervalDays = 7; + +std::vector GetEnabledFeaturesFromVariations() { + std::vector enabled_features; + // TODO(shaktisahu): Find these from finch. + enabled_features.emplace_back(FeatureType::kIncognitoTab); + enabled_features.emplace_back(FeatureType::kVoiceSearch); + return enabled_features; +} + +base::TimeDelta GetNotificationStartTimeDeltaFromVariations() { + int notification_interval_days = base::GetFieldTrialParamByFeatureAsInt( + features::kFeatureNotificationGuide, "notification_interval_days", + kDefaultNotificationTimeIntervalDays); + return base::Days(notification_interval_days); +} + +} // namespace // static FeatureNotificationGuideServiceFactory* @@ -44,10 +73,17 @@ KeyedService* FeatureNotificationGuideServiceFactory::BuildServiceInstanceFor( NotificationScheduleServiceFactory::GetForKey(profile->GetProfileKey()); feature_engagement::Tracker* tracker = feature_engagement::TrackerFactory::GetForBrowserContext(profile); - DCHECK(notification_scheduler); - DCHECK(tracker); - // TODO(shaktisahu): Hookup dependencies. - return new FeatureNotificationGuideServiceImpl(); + Config config; + config.enabled_features = GetEnabledFeaturesFromVariations(); + config.notification_deliver_time_delta = + GetNotificationStartTimeDeltaFromVariations(); + std::unique_ptr delegate; +#if defined(OS_ANDROID) + delegate.reset(new FeatureNotificationGuideBridge()); +#endif + return new FeatureNotificationGuideServiceImpl( + std::move(delegate), config, notification_scheduler, tracker, + base::DefaultClock::GetInstance()); } bool FeatureNotificationGuideServiceFactory::ServiceIsNULLWhileTesting() const { diff --git a/chrome/browser/feature_guide/notifications/internal/BUILD.gn b/chrome/browser/feature_guide/notifications/internal/BUILD.gn index ec4cda495fb04..54c8dd49fd2aa 100644 --- a/chrome/browser/feature_guide/notifications/internal/BUILD.gn +++ b/chrome/browser/feature_guide/notifications/internal/BUILD.gn @@ -13,6 +13,8 @@ source_set("internal") { "feature_notification_guide_notification_client.h", "feature_notification_guide_service_impl.cc", "feature_notification_guide_service_impl.h", + "utils.cc", + "utils.h", ] deps = [ @@ -21,48 +23,6 @@ source_set("internal") { "//components/feature_engagement/public", "//skia", ] - - if (is_android) { - sources += [ - "android/feature_notification_guide_bridge.cc", - "android/feature_notification_guide_bridge.h", - ] - - deps += [ ":jni_headers" ] - } -} - -if (is_android) { - android_library("internal_java") { - visibility = [ - ":*", - "//chrome/android:chrome_all_java", - ] - sources = [ - "android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java", - "android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java", - ] - - deps = [ - "//base:base_java", - "//chrome/browser/feature_guide/notifications:java", - "//chrome/browser/profiles/android:java", - "//third_party/androidx:androidx_annotation_annotation_java", - ] - annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] - } - - generate_jni("jni_headers") { - visibility = [ - ":*", - "//chrome/browser", - ] - - sources = [ - "android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java", - "android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java", - ] - } } source_set("unit_tests") { @@ -74,6 +34,10 @@ source_set("unit_tests") { ":internal", "//base", "//base/test:test_support", + "//chrome/browser/feature_guide/notifications:public", + "//chrome/browser/notifications/scheduler/test:test_support", + "//components/feature_engagement/test:test_support", + "//skia", "//testing/gmock", "//testing/gtest", ] diff --git a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.h b/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.h deleted file mode 100644 index 284035e8fc17c..0000000000000 --- a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2021 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 CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ -#define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ - -#include - -#include "base/android/jni_android.h" -#include "base/memory/raw_ptr.h" -#include "base/supports_user_data.h" -#include "chrome/browser/feature_guide/notifications/feature_type.h" - -using base::android::ScopedJavaGlobalRef; -using base::android::ScopedJavaLocalRef; - -namespace feature_guide { -class FeatureNotificationGuideService; - -// Contains JNI methods needed by the feature notification guide. -class FeatureNotificationGuideBridge : public base::SupportsUserData::Data { - public: - // Returns a Java FeatureNotificationGuideBridge for |service|. - // There will be only one bridge per FeatureNotificationGuideBridge. - static FeatureNotificationGuideBridge* GetFeatureNotificationGuideBridge( - FeatureNotificationGuideService* feature_notification_guide_service); - - explicit FeatureNotificationGuideBridge( - FeatureNotificationGuideService* feature_notification_guide_service); - ~FeatureNotificationGuideBridge() override; - - ScopedJavaLocalRef GetJavaObj(); - std::u16string GetNotificationTitle(FeatureType feature); - std::u16string GetNotificationMessage(FeatureType feature); - void OnNotificationClick(FeatureType feature); - - private: - // A reference to the Java counterpart of this class. See - // FeatureNotificationGuideBridge.java. - ScopedJavaGlobalRef java_obj_; - - // Not owned. - raw_ptr feature_notification_guide_service_; -}; - -} // namespace feature_guide - -#endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_ANDROID_FEATURE_NOTIFICATION_GUIDE_BRIDGE_H_ diff --git a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_service_factory_android.cc b/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_service_factory_android.cc deleted file mode 100644 index 0d48db07956b1..0000000000000 --- a/chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_service_factory_android.cc +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2021 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 "base/android/scoped_java_ref.h" -#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" -#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.h" -#include "chrome/browser/feature_guide/notifications/internal/android/feature_notification_guide_bridge.h" -#include "chrome/browser/feature_guide/notifications/internal/jni_headers/FeatureNotificationGuideServiceFactory_jni.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/profiles/profile_android.h" - -static base::android::ScopedJavaLocalRef -JNI_FeatureNotificationGuideServiceFactory_GetForProfile( - JNIEnv* env, - const base::android::JavaParamRef& jprofile) { - Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile); - DCHECK(profile); - feature_guide::FeatureNotificationGuideService* service = - feature_guide::FeatureNotificationGuideServiceFactory::GetForProfile( - profile->GetOriginalProfile()); - return feature_guide::FeatureNotificationGuideBridge:: - GetFeatureNotificationGuideBridge(service) - ->GetJavaObj(); -} diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc index 70a387e2d5dd0..f1973e485f6e8 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc @@ -5,6 +5,7 @@ #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/feature_guide/notifications/internal/utils.h" #include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h" using ThrottleConfigCallback = @@ -39,8 +40,8 @@ void FeatureNotificationGuideNotificationClient::OnSchedulerInitialized( void FeatureNotificationGuideNotificationClient::OnUserAction( const notifications::UserActionData& action_data) { if (action_data.action_type == notifications::UserActionType::kClick) { - FeatureType feature = FeatureType::kInvalid; - // TODO(shaktisahu): Parse feature from action_data. + FeatureType feature = FeatureFromCustomData(action_data.custom_data); + DCHECK(feature != FeatureType::kInvalid); GetNotificationService()->OnClick(feature); } } diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc index 4d8b3969b5d69..13cab146d7b34 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc @@ -4,11 +4,30 @@ #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h" +#include + +#include "base/callback.h" +#include "base/containers/contains.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/clock.h" +#include "build/build_config.h" +#include "chrome/browser/feature_guide/notifications/config.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h" +#include "chrome/browser/feature_guide/notifications/internal/utils.h" +#include "chrome/browser/notifications/scheduler/public/client_overview.h" #include "chrome/browser/notifications/scheduler/public/notification_data.h" #include "chrome/browser/notifications/scheduler/public/notification_params.h" +#include "chrome/browser/notifications/scheduler/public/notification_schedule_service.h" +#include "chrome/browser/notifications/scheduler/public/schedule_params.h" +#include "components/feature_engagement/public/tracker.h" namespace feature_guide { +namespace { + +const base::TimeDelta kDeliverEndTimeDelta = base::Minutes(5); + +} // namespace std::unique_ptr CreateFeatureNotificationGuideNotificationClient(ServiceGetter service_getter) { @@ -16,19 +35,95 @@ CreateFeatureNotificationGuideNotificationClient(ServiceGetter service_getter) { service_getter); } -FeatureNotificationGuideServiceImpl::FeatureNotificationGuideServiceImpl() = - default; +FeatureNotificationGuideServiceImpl::FeatureNotificationGuideServiceImpl( + std::unique_ptr delegate, + const Config& config, + notifications::NotificationScheduleService* notification_scheduler, + feature_engagement::Tracker* tracker, + base::Clock* clock) + : delegate_(std::move(delegate)), + notification_scheduler_(notification_scheduler), + tracker_(tracker), + clock_(clock), + config_(config) { + DCHECK(notification_scheduler_); + delegate_->SetService(this); +} FeatureNotificationGuideServiceImpl::~FeatureNotificationGuideServiceImpl() = default; void FeatureNotificationGuideServiceImpl::OnSchedulerInitialized( - const std::set& guids) {} + const std::set& guids) { + for (const std::string& guid : guids) { + scheduled_features_.emplace(NotificationIdToFeature(guid)); + } + + tracker_->AddOnInitializedCallback(base::BindOnce( + &FeatureNotificationGuideServiceImpl::StartCheckingForEligibleFeatures, + weak_ptr_factory_.GetWeakPtr())); +} + +void FeatureNotificationGuideServiceImpl::StartCheckingForEligibleFeatures( + bool init_success) { + if (!init_success) + return; + + for (auto feature : config_.enabled_features) { + if (base::Contains(scheduled_features_, feature)) + continue; + + if (!tracker_->WouldTriggerHelpUI( + GetNotificationIphFeatureForFeature(feature))) { + continue; + } + + ScheduleNotification(feature); + } +} + +void FeatureNotificationGuideServiceImpl::ScheduleNotification( + FeatureType feature) { + notifications::NotificationData data; + data.title = delegate_->GetNotificationTitle(feature); + data.message = delegate_->GetNotificationMessage(feature); + + FeatureToCustomData(feature, &data.custom_data); + + notifications::ScheduleParams schedule_params; + schedule_params.priority = + notifications::ScheduleParams::Priority::kNoThrottle; + + // Show after a week. + schedule_params.deliver_time_start = + last_notification_schedule_time_.value_or(clock_->Now()) + + config_.notification_deliver_time_delta; + schedule_params.deliver_time_end = + schedule_params.deliver_time_start.value() + kDeliverEndTimeDelta; + last_notification_schedule_time_ = schedule_params.deliver_time_start.value(); + auto params = std::make_unique( + notifications::SchedulerClientType::kFeatureGuide, std::move(data), + std::move(schedule_params)); + notification_scheduler_->Schedule(std::move(params)); +} void FeatureNotificationGuideServiceImpl::BeforeShowNotification( std::unique_ptr notification_data, - NotificationDataCallback callback) {} + NotificationDataCallback callback) { + FeatureType feature = FeatureFromCustomData(notification_data->custom_data); + DCHECK(feature != FeatureType::kInvalid); + + if (!tracker_->ShouldTriggerHelpUI( + GetNotificationIphFeatureForFeature(feature))) { + std::move(callback).Run(nullptr); + return; + } -void FeatureNotificationGuideServiceImpl::OnClick(FeatureType feature) {} + std::move(callback).Run(std::move(notification_data)); +} + +void FeatureNotificationGuideServiceImpl::OnClick(FeatureType feature) { + delegate_->OnNotificationClick(feature); +} } // namespace feature_guide diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h index 5d26e4622f0d5..c61a6f7452d4c 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h @@ -5,7 +5,12 @@ #ifndef CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_FEATURE_NOTIFICATION_GUIDE_SERVICE_IMPL_H_ #define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_FEATURE_NOTIFICATION_GUIDE_SERVICE_IMPL_H_ +#include + +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/time/clock.h" +#include "chrome/browser/feature_guide/notifications/config.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" #include "chrome/browser/feature_guide/notifications/feature_type.h" @@ -23,7 +28,12 @@ namespace feature_guide { class FeatureNotificationGuideServiceImpl : public FeatureNotificationGuideService { public: - FeatureNotificationGuideServiceImpl(); + FeatureNotificationGuideServiceImpl( + std::unique_ptr delegate, + const Config& config, + notifications::NotificationScheduleService* notification_scheduler, + feature_engagement::Tracker* tracker, + base::Clock* clock); ~FeatureNotificationGuideServiceImpl() override; void OnSchedulerInitialized(const std::set& guids) override; @@ -32,7 +42,21 @@ class FeatureNotificationGuideServiceImpl NotificationDataCallback callback) override; void OnClick(FeatureType feature) override; + Delegate* GetDelegate() { return delegate_.get(); } + private: + void StartCheckingForEligibleFeatures(bool init_success); + void ScheduleNotification(FeatureType feature); + + std::unique_ptr delegate_; + raw_ptr notification_scheduler_; + raw_ptr tracker_; + base::Clock* clock_; + Config config_; + + std::set scheduled_features_; + absl::optional last_notification_schedule_time_; + base::WeakPtrFactory weak_ptr_factory_{ this}; }; diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc index d49276fdc51e2..a9f576e561570 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc @@ -4,19 +4,98 @@ #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h" +#include +#include + +#include "base/test/gmock_callback_support.h" +#include "base/test/simple_test_clock.h" +#include "base/test/task_environment.h" +#include "base/test/test_simple_task_runner.h" +#include "chrome/browser/feature_guide/notifications/config.h" +#include "chrome/browser/notifications/scheduler/test/mock_notification_schedule_service.h" +#include "components/feature_engagement/test/mock_tracker.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using ::base::test::RunOnceCallback; +using testing::_; +using testing::Return; + namespace feature_guide { namespace { +constexpr char16_t kTestNotificationTitle[] = u"test_title"; +constexpr char16_t kTestNotificationMessage[] = u"test_message"; + +class TestDelegate : public FeatureNotificationGuideService::Delegate { + public: + std::u16string GetNotificationTitle(FeatureType feature) override { + return std::u16string(kTestNotificationTitle); + } + + std::u16string GetNotificationMessage(FeatureType feature) override { + return std::u16string(kTestNotificationMessage); + } + + void OnNotificationClick(FeatureType feature) override {} + + ~TestDelegate() override = default; +}; + +class TestScheduler + : public notifications::test::MockNotificationScheduleService { + public: + void Schedule(std::unique_ptr + notification_params) override { + queued_params_.emplace_back(std::move(notification_params)); + } + + std::vector> + GetQueuedParamsAndClear() { + return std::move(queued_params_); + } + + private: + std::vector> + queued_params_; +}; class FeatureNotificationGuideServiceImplTest : public testing::Test { public: FeatureNotificationGuideServiceImplTest() = default; ~FeatureNotificationGuideServiceImplTest() override = default; + + void SetUp() override { + config_.enabled_features.emplace_back(FeatureType::kIncognitoTab); + config_.notification_deliver_time_delta = base::Days(7); + service_ = std::make_unique( + std::make_unique(), config_, ¬ifcation_scheduler_, + &tracker_, &test_clock_); + EXPECT_CALL(tracker_, AddOnInitializedCallback(_)) + .WillOnce(RunOnceCallback<0>(true)); + } + + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + TestScheduler notifcation_scheduler_; + feature_engagement::test::MockTracker tracker_; + Config config_; + base::SimpleTestClock test_clock_; + std::unique_ptr service_; }; -TEST_F(FeatureNotificationGuideServiceImplTest, Initialize) {} +TEST_F(FeatureNotificationGuideServiceImplTest, BasicFlow) { + EXPECT_CALL(tracker_, WouldTriggerHelpUI(_)).WillRepeatedly(Return(true)); + service_->OnSchedulerInitialized(std::set()); + auto queued_params = notifcation_scheduler_.GetQueuedParamsAndClear(); + EXPECT_EQ(1u, queued_params.size()); + EXPECT_EQ(notifications::SchedulerClientType::kFeatureGuide, + queued_params[0]->type); + EXPECT_EQ(kTestNotificationTitle, queued_params[0]->notification_data.title); + EXPECT_EQ(kTestNotificationMessage, + queued_params[0]->notification_data.message); + EXPECT_EQ(test_clock_.Now() + base::Days(7), + queued_params[0]->schedule_params.deliver_time_start.value()); +} } // namespace diff --git a/chrome/browser/feature_guide/notifications/internal/utils.cc b/chrome/browser/feature_guide/notifications/internal/utils.cc new file mode 100644 index 0000000000000..3b25dc968422c --- /dev/null +++ b/chrome/browser/feature_guide/notifications/internal/utils.cc @@ -0,0 +1,84 @@ +// Copyright 2021 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 "chrome/browser/feature_guide/notifications/internal/utils.h" + +#include "base/notreached.h" +#include "base/strings/string_number_conversions.h" +#include "components/feature_engagement/public/feature_list.h" + +namespace feature_guide { +namespace { + +constexpr char kCustomDataKeyForFeatureType[] = "feature_type"; + +constexpr char kNotificationIdDefaultBroser[] = "feature_guide_default_browser"; +constexpr char kNotificationIdSignIn[] = "feature_guide_sign_in"; +constexpr char kNotificationIdIncognitoTab[] = "feature_guide_incognito_tab"; +constexpr char kNotificationIdVoiceSearch[] = "feature_guide_voice_search"; +constexpr char kNotificationIdNTPSuggestionCard[] = + "feature_guide_ntp_suggestion_card"; + +} // namespace + +void FeatureToCustomData(FeatureType feature, + std::map* custom_data) { + custom_data->emplace(kCustomDataKeyForFeatureType, + base::NumberToString(static_cast(feature))); +} + +FeatureType FeatureFromCustomData( + const std::map& custom_data) { + int parsed_value = 0; + std::string feature_string = custom_data.at(kCustomDataKeyForFeatureType); + if (!base::StringToInt(feature_string, &parsed_value)) + return FeatureType::kInvalid; + + return static_cast(parsed_value); +} + +std::string NotificationIdForFeature(FeatureType feature) { + switch (feature) { + case FeatureType::kDefaultBrowser: + return kNotificationIdDefaultBroser; + case FeatureType::kSignIn: + return kNotificationIdSignIn; + case FeatureType::kIncognitoTab: + return kNotificationIdIncognitoTab; + case FeatureType::kNTPSuggestionCard: + return kNotificationIdNTPSuggestionCard; + case FeatureType::kVoiceSearch: + return kNotificationIdVoiceSearch; + default: + NOTREACHED(); + return std::string(); + } +} + +FeatureType NotificationIdToFeature(const std::string& notification_id) { + if (notification_id == kNotificationIdDefaultBroser) { + return FeatureType::kDefaultBrowser; + } else if (notification_id == kNotificationIdSignIn) { + return FeatureType::kSignIn; + } else if (notification_id == kNotificationIdIncognitoTab) { + return FeatureType::kIncognitoTab; + } else if (notification_id == kNotificationIdNTPSuggestionCard) { + return FeatureType::kNTPSuggestionCard; + } else if (notification_id == kNotificationIdVoiceSearch) { + return FeatureType::kVoiceSearch; + } + return FeatureType::kInvalid; +} + +base::Feature GetNotificationIphFeatureForFeature(FeatureType& feature) { + // TODO(shaktisahu): Implement. + switch (feature) { + case FeatureType::kIncognitoTab: + return feature_engagement::kIPHDemoMode; + default: + return feature_engagement::kIPHDemoMode; + } +} + +} // namespace feature_guide diff --git a/chrome/browser/feature_guide/notifications/internal/utils.h b/chrome/browser/feature_guide/notifications/internal/utils.h new file mode 100644 index 0000000000000..016c7596a7a47 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/internal/utils.h @@ -0,0 +1,35 @@ +// Copyright 2021 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 CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_UTILS_H_ +#define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_UTILS_H_ + +#include +#include + +#include "base/feature_list.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" + +namespace feature_guide { + +// Serialize a given FeatureType to notification custom data. +void FeatureToCustomData(FeatureType feature, + std::map* custom_data); + +// Deserialize the FeatureType from notification custom data. +FeatureType FeatureFromCustomData( + const std::map& custom_data); + +// Get a fixed notification ID for the given feature. +std::string NotificationIdForFeature(FeatureType feature); + +// Returns the feature type from the notification ID. +FeatureType NotificationIdToFeature(const std::string& notification_id); + +// Returns the notification IPH feature for the given feature. +base::Feature GetNotificationIphFeatureForFeature(FeatureType& feature); + +} // namespace feature_guide + +#endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_UTILS_H_