From fe838418e66a1b60ac2041890d81092cc8e29cb1 Mon Sep 17 00:00:00 2001 From: Serg Date: Fri, 10 Nov 2023 13:39:35 -0500 Subject: [PATCH] Adds mojo interface to interact with AI credential manager from Java --- android/brave_java_sources.gni | 1 + .../browser/brave_leo/BraveLeoCMHelper.java | 82 +++++++++++++++++++ .../browser/settings/BraveLeoPreferences.java | 47 +++++++++++ .../java/res/xml/brave_leo_preferences.xml | 12 ++- .../android/ai_chat_cm_helper_android.cc | 67 +++++++++++++++ .../android/ai_chat_cm_helper_android.h | 42 ++++++++++ browser/ai_chat/android/sources.gni | 14 ++++ browser/sources.gni | 6 ++ .../android/strings/android_brave_strings.grd | 9 ++ build/android/BUILD.gn | 1 + build/android/config.gni | 2 + components/ai_chat/core/common/mojom/BUILD.gn | 1 + .../ai_chat/core/common/mojom/ai_chat.mojom | 4 + 13 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java create mode 100644 browser/ai_chat/android/ai_chat_cm_helper_android.cc create mode 100644 browser/ai_chat/android/ai_chat_cm_helper_android.h create mode 100644 browser/ai_chat/android/sources.gni diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index fb7f309a2a2..b5d2cb8742b 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -77,6 +77,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/bookmarks/BraveBookmarkToolbarCoordinator.java", "../../brave/android/java/org/chromium/chrome/browser/bookmarks/BraveBookmarkUtils.java", "../../brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoActivity.java", + "../../brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java", "../../brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java", "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsAdapterFeedCard.java", "../../brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsBottomSheetDialogFragment.java", diff --git a/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java b/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java new file mode 100644 index 00000000000..651b18341bd --- /dev/null +++ b/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java @@ -0,0 +1,82 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package org.chromium.chrome.browser.brave_leo; + +import org.chromium.ai_chat.mojom.CredentialManagerHelper; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.content_public.browser.BrowserContextHandle; +import org.chromium.mojo.bindings.ConnectionErrorHandler; +import org.chromium.mojo.bindings.Interface; +import org.chromium.mojo.bindings.Interface.Proxy.Handler; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.impl.CoreImpl; + +/** Helper to interact with ai_chat_credential_manager. */ +@JNINamespace("ai_chat") +public class BraveLeoCMHelper implements ConnectionErrorHandler { + private long mNativeAIChatCMHelperAndroid; + CredentialManagerHelper mCredentialManagerHelper; + + private static final Object sLock = new Object(); + private static BraveLeoCMHelper sInstance; + + public static BraveLeoCMHelper getInstance(BrowserContextHandle browserContextHandle) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new BraveLeoCMHelper(browserContextHandle); + } + } + return sInstance; + } + + private BraveLeoCMHelper(BrowserContextHandle browserContextHandle) { + mNativeAIChatCMHelperAndroid = BraveLeoCMHelperJni.get().init(browserContextHandle); + long nativeHandle = + BraveLeoCMHelperJni.get() + .getInterfaceToCredentialManagerHelper(mNativeAIChatCMHelperAndroid); + MessagePipeHandle handle = + CoreImpl.getInstance().acquireNativeHandle(nativeHandle).toMessagePipeHandle(); + mCredentialManagerHelper = CredentialManagerHelper.MANAGER.attachProxy(handle, 0); + Handler handler = ((Interface.Proxy) mCredentialManagerHelper).getProxyHandler(); + handler.setErrorHandler(this); + } + + public void destroy() { + synchronized (sLock) { + sInstance = null; + if (mNativeAIChatCMHelperAndroid == 0 && mCredentialManagerHelper == null) { + return; + } + mCredentialManagerHelper.close(); + mCredentialManagerHelper = null; + BraveLeoCMHelperJni.get().destroy(mNativeAIChatCMHelperAndroid); + mNativeAIChatCMHelperAndroid = 0; + } + } + + public void getPremiumStatus(CredentialManagerHelper.GetPremiumStatus_Response callback) { + if (mCredentialManagerHelper == null) { + return; + } + mCredentialManagerHelper.getPremiumStatus(callback); + } + + @Override + public void onConnectionError(MojoException e) { + destroy(); + } + + @NativeMethods + public interface Natives { + long init(BrowserContextHandle browserContextHandle); + + void destroy(long nativeAIChatCMHelperAndroid); + + long getInterfaceToCredentialManagerHelper(long nativeAIChatCMHelperAndroid); + } +} diff --git a/android/java/org/chromium/chrome/browser/settings/BraveLeoPreferences.java b/android/java/org/chromium/chrome/browser/settings/BraveLeoPreferences.java index a9ee5e6e5f4..82257dd62d5 100644 --- a/android/java/org/chromium/chrome/browser/settings/BraveLeoPreferences.java +++ b/android/java/org/chromium/chrome/browser/settings/BraveLeoPreferences.java @@ -7,14 +7,61 @@ package org.chromium.chrome.browser.settings; import android.os.Bundle; +import org.chromium.ai_chat.mojom.PremiumStatus; +import org.chromium.base.Log; import org.chromium.chrome.R; +import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.brave_leo.BraveLeoCMHelper; +import org.chromium.chrome.browser.util.TabUtils; +import org.chromium.components.browser_ui.settings.ChromeBasePreference; import org.chromium.components.browser_ui.settings.SettingsUtils; public class BraveLeoPreferences extends BravePreferenceFragment { + private static final String TAG = "BraveLeoPreferences"; + private static final String PREF_LINK_SUBSCRIPTION = "link_subscription"; + private static final String LINK_SUBSCRIPTION_URL = "https://account.brave.com"; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { requireActivity().setTitle(R.string.menu_brave_leo); SettingsUtils.addPreferencesFromResource(this, R.xml.brave_leo_preferences); + + BraveLeoCMHelper.getInstance(getProfile()) + .getPremiumStatus( + status -> { + if (status != PremiumStatus.ACTIVE) { + ChromeBasePreference link_subscription = + findPreference(PREF_LINK_SUBSCRIPTION); + if (link_subscription == null) { + Log.e(TAG, "Subscription pref is null"); + return; + } + link_subscription.setVisible(true); + link_subscription.setOnPreferenceClickListener( + preference -> { + try { + BraveActivity.getBraveActivity() + .openNewOrSelectExistingTab( + LINK_SUBSCRIPTION_URL, true); + TabUtils.bringChromeTabbedActivityToTheTop( + getActivity()); + } catch ( + BraveActivity.BraveActivityNotFoundException + e) { + Log.e( + TAG, + "Error while opening subscription tab.", + e); + } + return true; + }); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + BraveLeoCMHelper.getInstance(getProfile()).destroy(); } } diff --git a/android/java/res/xml/brave_leo_preferences.xml b/android/java/res/xml/brave_leo_preferences.xml index ab17319ab46..7c64743e0e6 100644 --- a/android/java/res/xml/brave_leo_preferences.xml +++ b/android/java/res/xml/brave_leo_preferences.xml @@ -4,11 +4,21 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. --> - + + + diff --git a/browser/ai_chat/android/ai_chat_cm_helper_android.cc b/browser/ai_chat/android/ai_chat_cm_helper_android.cc new file mode 100644 index 00000000000..66f5e186825 --- /dev/null +++ b/browser/ai_chat/android/ai_chat_cm_helper_android.cc @@ -0,0 +1,67 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ai_chat/android/ai_chat_cm_helper_android.h" + +#include + +#include "brave/browser/skus/skus_service_factory.h" +#include "brave/build/android/jni_headers/BraveLeoCMHelper_jni.h" +#include "chrome/browser/browser_process.h" +#include "content/public/browser/android/browser_context_handle.h" +#include "content/public/browser/browser_context.h" +#include "mojo/public/cpp/bindings/pending_remote.h" + +namespace ai_chat { + +static jlong JNI_BraveLeoCMHelper_Init( + JNIEnv* env, + const base::android::JavaParamRef& jbrowser_context_handle) { + AIChatCMHelperAndroid* cm_helper = + new AIChatCMHelperAndroid(jbrowser_context_handle); + return reinterpret_cast(cm_helper); +} + +AIChatCMHelperAndroid::AIChatCMHelperAndroid( + const base::android::JavaParamRef& jbrowser_context_handle) { + content::BrowserContext* context = + content::BrowserContextFromJavaHandle(jbrowser_context_handle); + auto skus_service_getter = base::BindRepeating( + [](content::BrowserContext* context) { + return skus::SkusServiceFactory::GetForContext(context); + }, + context); + credential_manager_ = std::make_unique( + skus_service_getter, g_browser_process->local_state()); +} + +AIChatCMHelperAndroid::~AIChatCMHelperAndroid() = default; + +void AIChatCMHelperAndroid::Destroy(JNIEnv* env) { + delete this; +} + +jlong AIChatCMHelperAndroid::GetInterfaceToCredentialManagerHelper( + JNIEnv* env) { + mojo::PendingRemote remote; + receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver()); + + return static_cast(remote.PassPipe().release().value()); +} + +void AIChatCMHelperAndroid::GetPremiumStatus( + GetPremiumStatusCallback callback) { + credential_manager_->GetPremiumStatus( + base::BindOnce(&AIChatCMHelperAndroid::OnPremiumStatusReceived, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + +void AIChatCMHelperAndroid::OnPremiumStatusReceived( + mojom::PageHandler::GetPremiumStatusCallback parent_callback, + mojom::PremiumStatus premium_status) { + std::move(parent_callback).Run(premium_status); +} + +} // namespace ai_chat diff --git a/browser/ai_chat/android/ai_chat_cm_helper_android.h b/browser/ai_chat/android/ai_chat_cm_helper_android.h new file mode 100644 index 00000000000..41394cb04d2 --- /dev/null +++ b/browser/ai_chat/android/ai_chat_cm_helper_android.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_AI_CHAT_ANDROID_AI_CHAT_CM_HELPER_ANDROID_H_ +#define BRAVE_BROWSER_AI_CHAT_ANDROID_AI_CHAT_CM_HELPER_ANDROID_H_ + +#include + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/memory/raw_ptr.h" +#include "brave/components/ai_chat/core/browser/ai_chat_credential_manager.h" +#include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" +#include "mojo/public/cpp/bindings/receiver_set.h" + +namespace ai_chat { + +class AIChatCMHelperAndroid : public mojom::CredentialManagerHelper { + public: + AIChatCMHelperAndroid( + const base::android::JavaParamRef& jbrowser_context_handle); + ~AIChatCMHelperAndroid() override; + + void Destroy(JNIEnv* env); + + void GetPremiumStatus(GetPremiumStatusCallback callback) override; + jlong GetInterfaceToCredentialManagerHelper(JNIEnv* env); + + private: + void OnPremiumStatusReceived( + mojom::PageHandler::GetPremiumStatusCallback parent_callback, + mojom::PremiumStatus premium_status); + std::unique_ptr credential_manager_; + mojo::ReceiverSet receivers_; + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace ai_chat + +#endif // BRAVE_BROWSER_AI_CHAT_ANDROID_AI_CHAT_CM_HELPER_ANDROID_H_ diff --git a/browser/ai_chat/android/sources.gni b/browser/ai_chat/android/sources.gni new file mode 100644 index 00000000000..c3b2728a4e1 --- /dev/null +++ b/browser/ai_chat/android/sources.gni @@ -0,0 +1,14 @@ +# Copyright (c) 2023 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +brave_browser_ai_chat_android_sources = [ + "//brave/browser/ai_chat/android/ai_chat_cm_helper_android.cc", + "//brave/browser/ai_chat/android/ai_chat_cm_helper_android.h", +] + +brave_browser_ai_chat_android_deps = [ + "//brave/components/ai_chat/core/browser", + "//brave/components/ai_chat/core/common/mojom", +] diff --git a/browser/sources.gni b/browser/sources.gni index 2e117619857..46f33366e40 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -3,6 +3,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +import("//brave/browser/ai_chat/android/sources.gni") import("//brave/browser/autocomplete/sources.gni") import("//brave/browser/brave_adaptive_captcha/sources.gni") import("//brave/browser/brave_ads/sources.gni") @@ -292,6 +293,11 @@ if (enable_ai_chat) { "//brave/components/ai_chat/core/common", "//brave/components/ai_chat/core/common/mojom", ] + + if (is_android) { + brave_chrome_browser_sources += brave_browser_ai_chat_android_sources + brave_chrome_browser_deps += brave_browser_ai_chat_android_deps + } } if (enable_brave_vpn) { diff --git a/browser/ui/android/strings/android_brave_strings.grd b/browser/ui/android/strings/android_brave_strings.grd index f4bfe0fe722..f248506df14 100644 --- a/browser/ui/android/strings/android_brave_strings.grd +++ b/browser/ui/android/strings/android_brave_strings.grd @@ -4100,6 +4100,15 @@ If you don't accept this request, VPN will not reconnect and your internet conne ... + + Link purchase to your Brave account + + + Link your Play Store purchase to your Brave account to use Brave Leo on other devices. + + + Manage Subscription + diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn index 6b4274b310f..07e537a1646 100644 --- a/build/android/BUILD.gn +++ b/build/android/BUILD.gn @@ -212,6 +212,7 @@ generate_jni("jni_headers") { "//brave/android/java/org/chromium/chrome/browser/BraveSyncWorker.java", "//brave/android/java/org/chromium/chrome/browser/app/BraveActivity.java", "//brave/android/java/org/chromium/chrome/browser/bookmarks/BraveBookmarkBridge.java", + "//brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java", "//brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java", "//brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java", "//brave/android/java/org/chromium/chrome/browser/crypto_wallet/AssetRatioServiceFactory.java", diff --git a/build/android/config.gni b/build/android/config.gni index 23dab539dca..ddcc0ba88ed 100644 --- a/build/android/config.gni +++ b/build/android/config.gni @@ -30,6 +30,7 @@ brave_chrome_java_deps = [ "//brave/browser/ui/android/logo:java", "//brave/browser/ui/android/omnibox:java", "//brave/browser/ui/android/theme:java", + "//brave/components/ai_chat/core/common/mojom:mojom_java", "//brave/components/brave_news/common:mojom_java", "//brave/components/brave_rewards/common/mojom:mojom_java", "//brave/components/brave_shields/common:mojom_java", @@ -110,6 +111,7 @@ brave_jni_headers_sources = [ "//brave/android/java/org/chromium/chrome/browser/BraveSyncWorker.java", "//brave/android/java/org/chromium/chrome/browser/app/BraveActivity.java", "//brave/android/java/org/chromium/chrome/browser/bookmarks/BraveBookmarkBridge.java", + "//brave/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoCMHelper.java", "//brave/android/java/org/chromium/chrome/browser/brave_news/BraveNewsControllerFactory.java", "//brave/android/java/org/chromium/chrome/browser/crypto_wallet/AssetRatioServiceFactory.java", "//brave/android/java/org/chromium/chrome/browser/crypto_wallet/BlockchainRegistryFactory.java", diff --git a/components/ai_chat/core/common/mojom/BUILD.gn b/components/ai_chat/core/common/mojom/BUILD.gn index d6294180370..2d0c299fdf1 100644 --- a/components/ai_chat/core/common/mojom/BUILD.gn +++ b/components/ai_chat/core/common/mojom/BUILD.gn @@ -6,6 +6,7 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("mojom") { + generate_java = true sources = [ "ai_chat.mojom", "page_content_extractor.mojom", diff --git a/components/ai_chat/core/common/mojom/ai_chat.mojom b/components/ai_chat/core/common/mojom/ai_chat.mojom index f252f47c376..c41d5f1c6de 100644 --- a/components/ai_chat/core/common/mojom/ai_chat.mojom +++ b/components/ai_chat/core/common/mojom/ai_chat.mojom @@ -159,3 +159,7 @@ interface ChatUIPage { // This reports if there are any pending entries to be sent to the remote LLM. OnConversationEntryPending(); }; + +interface CredentialManagerHelper { + GetPremiumStatus() => (PremiumStatus result); +};