|  | // Copyright 2021 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <optional> | 
|  |  | 
|  | #include "base/android/callback_android.h" | 
|  | #include "base/android/jni_android.h" | 
|  | #include "base/android/jni_array.h" | 
|  | #include "base/android/jni_string.h" | 
|  | #include "base/android/scoped_java_ref.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/scoped_refptr.h" | 
|  | #include "base/no_destructor.h" | 
|  | #include "base/notreached.h" | 
|  | #include "base/task/cancelable_task_tracker.h" | 
|  | #include "chrome/browser/android/tab_android.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/feed/feed_service_factory.h" | 
|  | #include "chrome/browser/feed/web_feed_page_information_fetcher.h" | 
|  | #include "chrome/browser/feed/web_feed_util.h" | 
|  | #include "chrome/browser/history/history_service_factory.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/profiles/profile_manager.h" | 
|  | #include "components/country_codes/country_codes.h" | 
|  | #include "components/feed/core/v2/config.h" | 
|  | #include "components/feed/core/v2/public/feed_service.h" | 
|  | #include "components/feed/core/v2/public/types.h" | 
|  | #include "components/feed/core/v2/public/web_feed_subscriptions.h" | 
|  | #include "components/feed/feed_feature_list.h" | 
|  | #include "components/feed/mojom/rss_link_reader.mojom.h" | 
|  | #include "components/history/core/browser/history_service.h" | 
|  | #include "components/history/core/browser/history_types.h" | 
|  | #include "components/keyed_service/core/service_access_type.h" | 
|  | #include "components/variations/service/variations_service.h" | 
|  | #include "url/android/gurl_android.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | // Must come after all headers that specialize FromJniType() / ToJniType(). | 
|  | #include "chrome/browser/feed/android/jni_headers/WebFeedBridge_jni.h" | 
|  |  | 
|  | class Profile; | 
|  |  | 
|  | namespace feed { | 
|  |  | 
|  | using PageInformation = WebFeedPageInformationFetcher::PageInformation; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | base::CancelableTaskTracker& TaskTracker() { | 
|  | static base::NoDestructor<base::CancelableTaskTracker> task_tracker; | 
|  | return *task_tracker; | 
|  | } | 
|  |  | 
|  | PageInformation ToNativePageInformation( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& pageInfo) { | 
|  |  | 
|  | PageInformation result; | 
|  | result.url = url::GURLAndroid::ToNativeGURL( | 
|  | env, Java_WebFeedPageInformation_getUrl(env, pageInfo)); | 
|  | TabAndroid* tab = TabAndroid::GetNativeTab( | 
|  | env, Java_WebFeedPageInformation_getTab(env, pageInfo)); | 
|  | result.web_contents = tab ? tab->web_contents() : nullptr; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | std::string ToNativeWebFeedId( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaRef<jbyteArray>& j_web_feed_id) { | 
|  | std::string result; | 
|  | base::android::JavaByteArrayToString(env, j_web_feed_id, &result); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jbyteArray> ToJavaWebFeedId( | 
|  | JNIEnv* env, | 
|  | const std::string& web_feed_id) { | 
|  | return base::android::ToJavaByteArray(env, web_feed_id); | 
|  | } | 
|  |  | 
|  | WebFeedSubscriptions* GetSubscriptions() { | 
|  | Profile* profile = ProfileManager::GetLastUsedProfile(); | 
|  | if (!profile) | 
|  | return nullptr; | 
|  | return GetSubscriptionsForProfile(profile); | 
|  | } | 
|  |  | 
|  | FeedApi* GetStream() { | 
|  | Profile* profile = ProfileManager::GetLastUsedProfile(); | 
|  | FeedService* service = FeedServiceFactory::GetForBrowserContext(profile); | 
|  | if (!service) | 
|  | return nullptr; | 
|  | return service->GetStream(); | 
|  | } | 
|  |  | 
|  | // ToJava functions convert C++ types to Java. Used in `AdaptCallbackForJava`. | 
|  |  | 
|  | bool ToJava(JNIEnv* env, WebFeedSubscriptions::RefreshResult value) { | 
|  | return value.success; | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | const WebFeedMetadata& metadata) { | 
|  | return Java_WebFeedMetadata_Constructor( | 
|  | env, ToJavaWebFeedId(env, metadata.web_feed_id), metadata.title, | 
|  | url::GURLAndroid::FromNativeGURL(env, metadata.publisher_url), | 
|  | static_cast<int>(metadata.subscription_status), | 
|  | static_cast<int>(metadata.availability_status), metadata.is_recommended, | 
|  | url::GURLAndroid::FromNativeGURL(env, metadata.favicon_url)); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | const WebFeedSubscriptions::FollowWebFeedResult& result) { | 
|  | return Java_FollowResults_Constructor(env, | 
|  | static_cast<int>(result.request_status), | 
|  | ToJava(env, result.web_feed_metadata)); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | const WebFeedSubscriptions::UnfollowWebFeedResult& result) { | 
|  | return Java_UnfollowResults_Constructor( | 
|  | env, static_cast<int>(result.request_status)); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | std::vector<WebFeedMetadata> metadata_list) { | 
|  | std::vector<base::android::ScopedJavaLocalRef<jobject>> j_metadata_list; | 
|  | for (const WebFeedMetadata& metadata : metadata_list) { | 
|  | j_metadata_list.push_back(ToJava(env, metadata)); | 
|  | } | 
|  | return base::android::ToJavaArrayOfObjects(env, j_metadata_list); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | const WebFeedSubscriptions::QueryWebFeedResult& result) { | 
|  | return Java_QueryResult_Constructor(env, result.web_feed_id, result.title, | 
|  | result.url); | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobject> ToJava( | 
|  | JNIEnv* env, | 
|  | history::DailyVisitsResult result) { | 
|  | return base::android::ToJavaIntArray( | 
|  | env, std::vector<int>({result.total_visits, result.days_with_visits})); | 
|  | } | 
|  |  | 
|  | base::OnceCallback<void(WebFeedMetadata)> AdaptWebFeedMetadataCallback( | 
|  | const base::android::JavaParamRef<jobject>& callback) { | 
|  | auto adaptor = [](const base::android::JavaRef<jobject>& callback, | 
|  | WebFeedMetadata metadata) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | base::android::RunObjectCallbackAndroid(callback, ToJava(env, metadata)); | 
|  | }; | 
|  |  | 
|  | return base::BindOnce(adaptor, | 
|  | base::android::ScopedJavaGlobalRef<jobject>(callback)); | 
|  | } | 
|  |  | 
|  | base::OnceCallback<void(WebFeedSubscriptions::QueryWebFeedResult)> | 
|  | AdaptQueryWebFeedResultCallback( | 
|  | const base::android::JavaParamRef<jobject>& callback) { | 
|  | auto adaptor = [](const base::android::JavaRef<jobject>& callback, | 
|  | WebFeedSubscriptions::QueryWebFeedResult result) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | base::android::RunObjectCallbackAndroid(callback, ToJava(env, result)); | 
|  | }; | 
|  |  | 
|  | return base::BindOnce(adaptor, | 
|  | base::android::ScopedJavaGlobalRef<jobject>(callback)); | 
|  | } | 
|  |  | 
|  | void RunJavaCallback(const base::android::JavaRef<jobject>& callback, | 
|  | const base::android::JavaRef<jobject>& arg) { | 
|  | base::android::RunObjectCallbackAndroid(callback, arg); | 
|  | } | 
|  | void RunJavaCallback(const base::android::JavaRef<jobject>& callback, | 
|  | bool arg) { | 
|  | base::android::RunBooleanCallbackAndroid(callback, arg); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | base::OnceCallback<void(T)> AdaptCallbackForJava( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& callback) { | 
|  | auto adaptor = [](const base::android::JavaRef<jobject>& callback, T result) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | RunJavaCallback(callback, ToJava(env, std::move(result))); | 
|  | }; | 
|  |  | 
|  | return base::BindOnce(adaptor, | 
|  | base::android::ScopedJavaGlobalRef<jobject>(callback)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | static void JNI_WebFeedBridge_FollowWebFeed( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& pageInfo, | 
|  | jint change_reason, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | auto callback = | 
|  | AdaptCallbackForJava<WebFeedSubscriptions::FollowWebFeedResult>( | 
|  | env, j_callback); | 
|  |  | 
|  | PageInformation page_info = ToNativePageInformation(env, pageInfo); | 
|  | // Make sure web_contents is not NULL since the user might navigate away from | 
|  | // the current tab that is requested to follow. | 
|  | if (!page_info.web_contents) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | FollowWebFeed( | 
|  | page_info.web_contents, | 
|  | static_cast<feedwire::webfeed::WebFeedChangeReason>(change_reason), | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | static jboolean JNI_WebFeedBridge_IsCormorantEnabledForLocale(JNIEnv* env) { | 
|  | return JNI_WebFeedBridge_IsWebFeedEnabled(env); | 
|  | } | 
|  |  | 
|  | static jboolean JNI_WebFeedBridge_IsWebFeedEnabled(JNIEnv* env) { | 
|  | return feed::IsWebFeedEnabledForLocale(FeedServiceFactory::GetCountry()); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_FollowWebFeedById( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jbyteArray>& webFeedId, | 
|  | jboolean is_durable, | 
|  | jint change_reason, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | auto callback = | 
|  | AdaptCallbackForJava<WebFeedSubscriptions::FollowWebFeedResult>( | 
|  | env, j_callback); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->FollowWebFeed( | 
|  | ToNativeWebFeedId(env, webFeedId), | 
|  | /*is_durable_request=*/is_durable, | 
|  | static_cast<feedwire::webfeed::WebFeedChangeReason>(change_reason), | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_UnfollowWebFeed( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jbyteArray>& webFeedId, | 
|  | jboolean is_durable, | 
|  | jint change_reason, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | auto callback = | 
|  | AdaptCallbackForJava<WebFeedSubscriptions::UnfollowWebFeedResult>( | 
|  | env, j_callback); | 
|  | UnfollowWebFeed( | 
|  | ToNativeWebFeedId(env, webFeedId), | 
|  | /*is_durable_request=*/is_durable, | 
|  | static_cast<feedwire::webfeed::WebFeedChangeReason>(change_reason), | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_FindWebFeedInfoForPage( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& pageInfo, | 
|  | const int reason, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedMetadata)> callback = | 
|  | AdaptCallbackForJava<WebFeedMetadata>(env, j_callback); | 
|  |  | 
|  | PageInformation page_info = ToNativePageInformation(env, pageInfo); | 
|  | // Make sure web_contents is not NULL since the user might navigate away from | 
|  | // the current tab that is requested to find info. | 
|  | if (!page_info.web_contents) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | FindWebFeedInfoForPage( | 
|  | page_info.web_contents, | 
|  | static_cast<WebFeedPageInformationRequestReason>(reason), | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_FindWebFeedInfoForWebFeedId( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jbyteArray>& webFeedId, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedMetadata)> callback = | 
|  | AdaptWebFeedMetadataCallback(j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->FindWebFeedInfoForWebFeedId(ToNativeWebFeedId(env, webFeedId), | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_GetAllSubscriptions( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(std::vector<WebFeedMetadata>)> callback = | 
|  | AdaptCallbackForJava<std::vector<WebFeedMetadata>>(env, j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->GetAllSubscriptions(std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_RefreshSubscriptions( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedSubscriptions::RefreshResult)> callback = | 
|  | AdaptCallbackForJava<WebFeedSubscriptions::RefreshResult>(env, | 
|  | j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->RefreshSubscriptions(std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_RefreshRecommendedFeeds( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedSubscriptions::RefreshResult)> callback = | 
|  | AdaptCallbackForJava<WebFeedSubscriptions::RefreshResult>(env, | 
|  | j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->RefreshRecommendedFeeds(std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_GetRecentVisitCountsToHost( | 
|  | JNIEnv* env, | 
|  | const base::android::JavaParamRef<jobject>& j_url, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(history::DailyVisitsResult)> callback = | 
|  | AdaptCallbackForJava<history::DailyVisitsResult>(env, j_callback); | 
|  |  | 
|  | Profile* profile = ProfileManager::GetLastUsedProfile(); | 
|  | history::HistoryService* history_service = nullptr; | 
|  | if (profile) { | 
|  | history_service = HistoryServiceFactory::GetForProfile( | 
|  | profile, ServiceAccessType::IMPLICIT_ACCESS); | 
|  | } | 
|  | if (!history_service) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Ignore any visits within the last hour so that we do not count the current | 
|  | // visit to the page. | 
|  | auto end_time = base::Time::Now() - base::Hours(1); | 
|  | auto begin_time = | 
|  | base::Time::Now() - | 
|  | base::Days(GetFeedConfig().webfeed_accelerator_recent_visit_history_days); | 
|  | history_service->GetDailyVisitsToOrigin( | 
|  | url::Origin::Create(url::GURLAndroid::ToNativeGURL(env, j_url)), | 
|  | begin_time, end_time, std::move(callback), &TaskTracker()); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_IncrementFollowedFromWebPageMenuCount( | 
|  | JNIEnv* env) { | 
|  | FeedApi* stream = GetStream(); | 
|  | if (!stream) | 
|  | return; | 
|  |  | 
|  | stream->IncrementFollowedFromWebPageMenuCount(); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_QueryWebFeed( | 
|  | JNIEnv* env, | 
|  | std::string& url, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedSubscriptions::QueryWebFeedResult)> callback = | 
|  | AdaptQueryWebFeedResultCallback(j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->QueryWebFeed(GURL(url), std::move(callback)); | 
|  | } | 
|  |  | 
|  | static void JNI_WebFeedBridge_QueryWebFeedId( | 
|  | JNIEnv* env, | 
|  | std::string& id, | 
|  | const base::android::JavaParamRef<jobject>& j_callback) { | 
|  | base::OnceCallback<void(WebFeedSubscriptions::QueryWebFeedResult)> callback = | 
|  | AdaptQueryWebFeedResultCallback(j_callback); | 
|  | WebFeedSubscriptions* subscriptions = GetSubscriptions(); | 
|  | if (!subscriptions) { | 
|  | std::move(callback).Run({}); | 
|  | return; | 
|  | } | 
|  | subscriptions->QueryWebFeedId(id, std::move(callback)); | 
|  | } | 
|  | }  // namespace feed |