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