Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "chrome/browser/android/autofill_assistant/client_android.h" |
| 6 | |
Stephane Zermatten | 33b2410 | 2019-02-08 15:53:03 | [diff] [blame] | 7 | #include <utility> |
| 8 | #include <vector> |
| 9 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 10 | #include "base/android/jni_android.h" |
| 11 | #include "base/android/jni_array.h" |
| 12 | #include "base/android/jni_string.h" |
| 13 | #include "base/android/locale_utils.h" |
Sebastien Marchand | f1349f5 | 2019-01-25 03:16:41 | [diff] [blame] | 14 | #include "base/bind.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 15 | #include "base/command_line.h" |
| 16 | #include "base/metrics/field_trial_params.h" |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 17 | #include "base/no_destructor.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 18 | #include "base/task/post_task.h" |
Stephane Zermatten | 5d2e3b4 | 2019-03-05 10:33:30 | [diff] [blame] | 19 | #include "base/time/default_tick_clock.h" |
Andrew Grieve | 4a42c22e | 2019-06-24 16:14:29 | [diff] [blame] | 20 | #include "chrome/android/features/autofill_assistant/jni_headers/AutofillAssistantClient_jni.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 21 | #include "chrome/browser/android/chrome_feature_list.h" |
| 22 | #include "chrome/browser/autofill/android/personal_data_manager_android.h" |
| 23 | #include "chrome/browser/autofill/personal_data_manager_factory.h" |
| 24 | #include "chrome/browser/profiles/profile.h" |
| 25 | #include "chrome/browser/profiles/profile_manager.h" |
| 26 | #include "chrome/browser/signin/identity_manager_factory.h" |
| 27 | #include "chrome/common/channel_info.h" |
| 28 | #include "components/autofill_assistant/browser/access_token_fetcher.h" |
| 29 | #include "components/autofill_assistant/browser/controller.h" |
Hui(Andy) Wu | fbebf91d2 | 2019-02-01 01:23:54 | [diff] [blame] | 30 | #include "components/autofill_assistant/browser/features.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 31 | #include "components/signin/core/browser/account_info.h" |
Henrique Ferreiro | 94eb46f | 2019-07-03 14:38:56 | [diff] [blame] | 32 | #include "components/signin/public/identity_manager/identity_manager.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 33 | #include "components/version_info/channel.h" |
| 34 | #include "content/public/browser/browser_task_traits.h" |
| 35 | #include "content/public/browser/browser_thread.h" |
| 36 | #include "content/public/browser/web_contents.h" |
| 37 | #include "google_apis/google_api_keys.h" |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 38 | #include "url/gurl.h" |
| 39 | |
| 40 | using base::android::AttachCurrentThread; |
| 41 | using base::android::JavaParamRef; |
| 42 | using base::android::JavaRef; |
| 43 | |
| 44 | namespace autofill_assistant { |
| 45 | namespace switches { |
| 46 | const char* const kAutofillAssistantServerKey = "autofill-assistant-key"; |
Clemens Arbesser | d01d02e | 2019-03-01 09:18:13 | [diff] [blame] | 47 | const char* const kAutofillAssistantUrl = "autofill-assistant-url"; |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 48 | } // namespace switches |
| 49 | |
| 50 | namespace { |
| 51 | |
Clemens Arbesser | d01d02e | 2019-03-01 09:18:13 | [diff] [blame] | 52 | const char* const kDefaultAutofillAssistantServerUrl = |
| 53 | "https://automate-pa.googleapis.com"; |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 54 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 55 | // Fills a map from two Java arrays of strings of the same length. |
| 56 | void FillParametersFromJava(JNIEnv* env, |
| 57 | const JavaRef<jobjectArray>& names, |
| 58 | const JavaRef<jobjectArray>& values, |
| 59 | std::map<std::string, std::string>* parameters) { |
| 60 | std::vector<std::string> names_vector; |
| 61 | base::android::AppendJavaStringArrayToStringVector(env, names, &names_vector); |
| 62 | std::vector<std::string> values_vector; |
| 63 | base::android::AppendJavaStringArrayToStringVector(env, values, |
| 64 | &values_vector); |
| 65 | DCHECK_EQ(names_vector.size(), values_vector.size()); |
| 66 | for (size_t i = 0; i < names_vector.size(); ++i) { |
| 67 | parameters->insert(std::make_pair(names_vector[i], values_vector[i])); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | } // namespace |
| 72 | |
| 73 | static base::android::ScopedJavaLocalRef<jobject> |
| 74 | JNI_AutofillAssistantClient_FromWebContents( |
| 75 | JNIEnv* env, |
| 76 | const base::android::JavaParamRef<jobject>& jweb_contents) { |
| 77 | auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents); |
| 78 | ClientAndroid::CreateForWebContents(web_contents); |
| 79 | return ClientAndroid::FromWebContents(web_contents)->GetJavaObject(); |
| 80 | } |
| 81 | |
| 82 | ClientAndroid::ClientAndroid(content::WebContents* web_contents) |
| 83 | : web_contents_(web_contents), |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 84 | java_object_(Java_AutofillAssistantClient_create( |
| 85 | AttachCurrentThread(), |
| 86 | reinterpret_cast<intptr_t>(this))), |
Clemens Arbesser | d01d02e | 2019-03-01 09:18:13 | [diff] [blame] | 87 | weak_ptr_factory_(this) { |
| 88 | server_url_ = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| 89 | switches::kAutofillAssistantUrl); |
| 90 | if (server_url_.empty()) { |
| 91 | server_url_ = kDefaultAutofillAssistantServerUrl; |
| 92 | } |
| 93 | } |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 94 | |
| 95 | ClientAndroid::~ClientAndroid() { |
Mathias Carlen | 217b627 | 2019-01-28 15:04:18 | [diff] [blame] | 96 | if (controller_ != nullptr) { |
| 97 | // In the case of an unexpected closing of the activity or tab, controller_ |
| 98 | // will not yet have been cleaned up (since that happens when a web |
| 99 | // contents object gets destroyed). |
| 100 | Metrics::RecordDropOut(Metrics::CONTENT_DESTROYED); |
| 101 | } |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 102 | Java_AutofillAssistantClient_clearNativePtr(AttachCurrentThread(), |
| 103 | java_object_); |
| 104 | } |
| 105 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 106 | base::android::ScopedJavaLocalRef<jobject> ClientAndroid::GetJavaObject() { |
| 107 | return base::android::ScopedJavaLocalRef<jobject>(java_object_); |
| 108 | } |
| 109 | |
Jordan Demeulenaere | 825b6ba | 2019-01-17 11:24:34 | [diff] [blame] | 110 | void ClientAndroid::Start(JNIEnv* env, |
| 111 | const JavaParamRef<jobject>& jcaller, |
| 112 | const JavaParamRef<jstring>& jinitial_url, |
Mathias Carlen | cbc1f2d | 2019-04-08 10:31:31 | [diff] [blame] | 113 | const JavaParamRef<jstring>& jexperiment_ids, |
Jordan Demeulenaere | 825b6ba | 2019-01-17 11:24:34 | [diff] [blame] | 114 | const JavaParamRef<jobjectArray>& parameterNames, |
Jordan Demeulenaere | df068bfa | 2019-06-20 08:16:03 | [diff] [blame] | 115 | const JavaParamRef<jobjectArray>& parameterValues, |
| 116 | const JavaParamRef<jobject>& joverlay_coordinator) { |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 117 | CreateController(); |
Jordan Demeulenaere | df068bfa | 2019-06-20 08:16:03 | [diff] [blame] | 118 | |
| 119 | // If an overlay is already shown, then show the rest of the UI. |
| 120 | if (joverlay_coordinator) { |
| 121 | CreateUI(joverlay_coordinator); |
| 122 | } |
| 123 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 124 | GURL initial_url(base::android::ConvertJavaStringToUTF8(env, jinitial_url)); |
| 125 | std::map<std::string, std::string> parameters; |
| 126 | FillParametersFromJava(env, parameterNames, parameterValues, ¶meters); |
Stephane Zermatten | 16cb7c8c | 2019-07-08 13:09:28 | [diff] [blame] | 127 | controller_->Start(initial_url, TriggerContext::Create( |
Mathias Carlen | cbc1f2d | 2019-04-08 10:31:31 | [diff] [blame] | 128 | std::move(parameters), |
| 129 | base::android::ConvertJavaStringToUTF8( |
| 130 | env, jexperiment_ids))); |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 131 | } |
| 132 | |
Stephane Zermatten | d1442d94 | 2019-02-22 13:54:47 | [diff] [blame] | 133 | void ClientAndroid::DestroyUI( |
| 134 | JNIEnv* env, |
| 135 | const base::android::JavaParamRef<jobject>& jcaller) { |
| 136 | DestroyUI(); |
| 137 | } |
| 138 | |
| 139 | void ClientAndroid::TransferUITo( |
| 140 | JNIEnv* env, |
| 141 | const base::android::JavaParamRef<jobject>& jcaller, |
| 142 | const base::android::JavaParamRef<jobject>& jother_web_contents) { |
| 143 | if (!ui_controller_android_) |
| 144 | return; |
| 145 | |
| 146 | auto ui_ptr = std::move(ui_controller_android_); |
| 147 | // From this point on, the UIController, in ui_ptr, is either transferred or |
| 148 | // deleted. |
| 149 | |
| 150 | if (!jother_web_contents) |
| 151 | return; |
| 152 | |
| 153 | auto* other_web_contents = |
| 154 | content::WebContents::FromJavaWebContents(jother_web_contents); |
| 155 | DCHECK_NE(other_web_contents, web_contents_); |
| 156 | |
| 157 | ClientAndroid* other_client = |
| 158 | ClientAndroid::FromWebContents(other_web_contents); |
| 159 | if (!other_client || !other_client->NeedsUI()) |
| 160 | return; |
| 161 | |
| 162 | other_client->SetUI(std::move(ui_ptr)); |
| 163 | } |
| 164 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 165 | base::android::ScopedJavaLocalRef<jstring> ClientAndroid::GetPrimaryAccountName( |
| 166 | JNIEnv* env, |
| 167 | const JavaParamRef<jobject>& jcaller) { |
Sylvain Defresne | 6bd9b1c4 | 2019-02-13 16:56:01 | [diff] [blame] | 168 | CoreAccountInfo account_info = |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 169 | IdentityManagerFactory::GetForProfile( |
| 170 | Profile::FromBrowserContext(web_contents_->GetBrowserContext())) |
| 171 | ->GetPrimaryAccountInfo(); |
| 172 | return base::android::ConvertUTF8ToJavaString(env, account_info.email); |
| 173 | } |
| 174 | |
| 175 | void ClientAndroid::OnAccessToken(JNIEnv* env, |
| 176 | const JavaParamRef<jobject>& jcaller, |
| 177 | jboolean success, |
| 178 | const JavaParamRef<jstring>& access_token) { |
| 179 | if (fetch_access_token_callback_) { |
| 180 | std::move(fetch_access_token_callback_) |
| 181 | .Run(success, base::android::ConvertJavaStringToUTF8(access_token)); |
| 182 | } |
| 183 | } |
| 184 | |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 185 | void ClientAndroid::ShowUI() { |
| 186 | if (!controller_) { |
| 187 | CreateController(); |
| 188 | // TODO(crbug.com/806868): allow delaying controller creation, for |
| 189 | // onboarding. |
| 190 | } |
| 191 | if (!ui_controller_android_) { |
Jordan Demeulenaere | df068bfa | 2019-06-20 08:16:03 | [diff] [blame] | 192 | CreateUI(nullptr); |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 193 | } |
| 194 | } |
| 195 | |
Jordan Demeulenaere | df068bfa | 2019-06-20 08:16:03 | [diff] [blame] | 196 | void ClientAndroid::CreateUI( |
| 197 | const JavaParamRef<jobject>& joverlay_coordinator) { |
| 198 | std::unique_ptr<UiControllerAndroid> ui_ptr = |
| 199 | UiControllerAndroid::CreateFromWebContents(web_contents_, |
| 200 | joverlay_coordinator); |
| 201 | if (ui_ptr) |
| 202 | SetUI(std::move(ui_ptr)); |
| 203 | } |
| 204 | |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 205 | void ClientAndroid::DestroyUI() { |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 206 | ui_controller_android_.reset(); |
| 207 | } |
| 208 | |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 209 | std::string ClientAndroid::GetApiKey() { |
| 210 | std::string api_key; |
| 211 | if (google_apis::IsGoogleChromeAPIKeyUsed()) { |
| 212 | api_key = chrome::GetChannel() == version_info::Channel::STABLE |
| 213 | ? google_apis::GetAPIKey() |
| 214 | : google_apis::GetNonStableAPIKey(); |
| 215 | } |
| 216 | const auto* command_line = base::CommandLine::ForCurrentProcess(); |
| 217 | if (command_line->HasSwitch(switches::kAutofillAssistantServerKey)) { |
| 218 | api_key = command_line->GetSwitchValueASCII( |
| 219 | switches::kAutofillAssistantServerKey); |
| 220 | } |
| 221 | return api_key; |
| 222 | } |
| 223 | |
| 224 | std::string ClientAndroid::GetAccountEmailAddress() { |
| 225 | JNIEnv* env = AttachCurrentThread(); |
| 226 | return base::android::ConvertJavaStringToUTF8( |
| 227 | Java_AutofillAssistantClient_getAccountEmailAddress(env, java_object_)); |
| 228 | } |
| 229 | |
| 230 | AccessTokenFetcher* ClientAndroid::GetAccessTokenFetcher() { |
| 231 | return this; |
| 232 | } |
| 233 | |
| 234 | autofill::PersonalDataManager* ClientAndroid::GetPersonalDataManager() { |
| 235 | return autofill::PersonalDataManagerFactory::GetForProfile( |
| 236 | ProfileManager::GetLastUsedProfile()); |
| 237 | } |
| 238 | |
| 239 | std::string ClientAndroid::GetServerUrl() { |
Clemens Arbesser | d01d02e | 2019-03-01 09:18:13 | [diff] [blame] | 240 | return server_url_; |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 241 | } |
| 242 | |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 243 | UiController* ClientAndroid::GetUiController() { |
| 244 | if (ui_controller_android_) |
| 245 | return ui_controller_android_.get(); |
Jordan Demeulenaere | 825b6ba | 2019-01-17 11:24:34 | [diff] [blame] | 246 | |
Stephane Zermatten | 3471b36 | 2019-02-08 15:53:03 | [diff] [blame] | 247 | static base::NoDestructor<UiController> noop_controller_; |
| 248 | return noop_controller_.get(); |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 249 | } |
| 250 | |
| 251 | std::string ClientAndroid::GetLocale() { |
| 252 | return base::android::GetDefaultLocaleString(); |
| 253 | } |
| 254 | |
| 255 | std::string ClientAndroid::GetCountryCode() { |
| 256 | return base::android::ConvertJavaStringToUTF8( |
| 257 | Java_AutofillAssistantClient_getCountryCode(AttachCurrentThread(), |
| 258 | java_object_)); |
| 259 | } |
| 260 | |
Stephane Zermatten | 33b2410 | 2019-02-08 15:53:03 | [diff] [blame] | 261 | void ClientAndroid::Shutdown(Metrics::DropOutReason reason) { |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 262 | if (!controller_) |
| 263 | return; |
| 264 | |
Stephane Zermatten | 154c24a | 2019-04-17 16:08:00 | [diff] [blame] | 265 | // Lets the controller and the ui controller know shutdown is about to happen. |
| 266 | // TODO(b/128300038): Replace Controller::WillShutdown with a Detach call on |
| 267 | // ui_controller_android_. |
| 268 | controller_->WillShutdown(reason); |
Stephane Zermatten | 33b2410 | 2019-02-08 15:53:03 | [diff] [blame] | 269 | |
| 270 | Metrics::RecordDropOut(reason); |
Stephane Zermatten | 14482fc | 2019-02-26 16:50:15 | [diff] [blame] | 271 | |
| 272 | // Delete the controller in a separate task. This avoids tricky ordering |
| 273 | // issues when Shutdown is called from the controller. |
| 274 | base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI}, |
| 275 | base::BindOnce(&ClientAndroid::DestroyController, |
| 276 | weak_ptr_factory_.GetWeakPtr())); |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 277 | } |
| 278 | |
| 279 | void ClientAndroid::FetchAccessToken( |
| 280 | base::OnceCallback<void(bool, const std::string&)> callback) { |
| 281 | DCHECK(!fetch_access_token_callback_); |
| 282 | |
| 283 | fetch_access_token_callback_ = std::move(callback); |
| 284 | JNIEnv* env = AttachCurrentThread(); |
| 285 | Java_AutofillAssistantClient_fetchAccessToken(env, java_object_); |
| 286 | } |
| 287 | |
| 288 | void ClientAndroid::InvalidateAccessToken(const std::string& access_token) { |
| 289 | JNIEnv* env = AttachCurrentThread(); |
| 290 | Java_AutofillAssistantClient_invalidateAccessToken( |
| 291 | env, java_object_, |
| 292 | base::android::ConvertUTF8ToJavaString(env, access_token)); |
| 293 | } |
| 294 | |
| 295 | void ClientAndroid::CreateController() { |
| 296 | if (controller_) { |
| 297 | return; |
| 298 | } |
Stephane Zermatten | 5d2e3b4 | 2019-03-05 10:33:30 | [diff] [blame] | 299 | controller_ = std::make_unique<Controller>( |
| 300 | web_contents_, /* client= */ this, base::DefaultTickClock::GetInstance()); |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 301 | } |
| 302 | |
Stephane Zermatten | 14482fc | 2019-02-26 16:50:15 | [diff] [blame] | 303 | void ClientAndroid::DestroyController() { |
| 304 | controller_.reset(); |
| 305 | } |
| 306 | |
Stephane Zermatten | d1442d94 | 2019-02-22 13:54:47 | [diff] [blame] | 307 | bool ClientAndroid::NeedsUI() { |
| 308 | return !ui_controller_android_ && controller_ && controller_->NeedsUI(); |
| 309 | } |
| 310 | |
| 311 | void ClientAndroid::SetUI( |
| 312 | std::unique_ptr<UiControllerAndroid> ui_controller_android) { |
| 313 | ui_controller_android_ = std::move(ui_controller_android); |
| 314 | ui_controller_android_->Attach(web_contents_, this, controller_.get()); |
| 315 | } |
| 316 | |
Nico Weber | 6fcfd57 | 2019-02-23 03:28:03 | [diff] [blame] | 317 | WEB_CONTENTS_USER_DATA_KEY_IMPL(ClientAndroid) |
Stephane Zermatten | 40481b80 | 2019-01-09 18:50:52 | [diff] [blame] | 318 | |
| 319 | } // namespace autofill_assistant. |