1 // Copyright 2013 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.
5 #include "chrome/browser/signin/oauth2_token_service_delegate_android.h"
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_array.h"
9 #include "base/android/jni_string.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "chrome/browser/profiles/profile_android.h"
13 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
14 #include "chrome/browser/sync/profile_sync_service_android.h"
15 #include "components/signin/core/browser/account_info.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "google_apis/gaia/gaia_auth_util.h"
18 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
19 #include "jni/OAuth2TokenService_jni.h"
21 using base::android::AttachCurrentThread
;
22 using base::android::ConvertJavaStringToUTF8
;
23 using base::android::ConvertUTF8ToJavaString
;
24 using base::android::ScopedJavaLocalRef
;
25 using content::BrowserThread
;
29 // Callback from FetchOAuth2TokenWithUsername().
31 // - the error, or NONE if the token fetch was successful.
32 // - the OAuth2 access token.
33 // - the expiry time of the token (may be null, indicating that the expiry
35 typedef base::Callback
<void(const GoogleServiceAuthError
&,
37 const base::Time
&)> FetchOAuth2TokenCallback
;
39 class AndroidAccessTokenFetcher
: public OAuth2AccessTokenFetcher
{
41 AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer
* consumer
,
42 const std::string
& account_id
);
43 ~AndroidAccessTokenFetcher() override
;
45 // Overrides from OAuth2AccessTokenFetcher:
46 void Start(const std::string
& client_id
,
47 const std::string
& client_secret
,
48 const std::vector
<std::string
>& scopes
) override
;
49 void CancelRequest() override
;
51 // Handles an access token response.
52 void OnAccessTokenResponse(const GoogleServiceAuthError
& error
,
53 const std::string
& access_token
,
54 const base::Time
& expiration_time
);
57 std::string
CombineScopes(const std::vector
<std::string
>& scopes
);
59 std::string account_id_
;
60 bool request_was_cancelled_
;
61 base::WeakPtrFactory
<AndroidAccessTokenFetcher
> weak_factory_
;
63 DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher
);
66 AndroidAccessTokenFetcher::AndroidAccessTokenFetcher(
67 OAuth2AccessTokenConsumer
* consumer
,
68 const std::string
& account_id
)
69 : OAuth2AccessTokenFetcher(consumer
),
70 account_id_(account_id
),
71 request_was_cancelled_(false),
75 AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {
78 void AndroidAccessTokenFetcher::Start(const std::string
& client_id
,
79 const std::string
& client_secret
,
80 const std::vector
<std::string
>& scopes
) {
81 JNIEnv
* env
= AttachCurrentThread();
82 std::string scope
= CombineScopes(scopes
);
83 ScopedJavaLocalRef
<jstring
> j_username
=
84 ConvertUTF8ToJavaString(env
, account_id_
);
85 ScopedJavaLocalRef
<jstring
> j_scope
= ConvertUTF8ToJavaString(env
, scope
);
86 scoped_ptr
<FetchOAuth2TokenCallback
> heap_callback(
87 new FetchOAuth2TokenCallback(
88 base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse
,
89 weak_factory_
.GetWeakPtr())));
91 // Call into Java to get a new token.
92 Java_OAuth2TokenService_getOAuth2AuthToken(
93 env
, base::android::GetApplicationContext(), j_username
.obj(),
94 j_scope
.obj(), reinterpret_cast<intptr_t>(heap_callback
.release()));
97 void AndroidAccessTokenFetcher::CancelRequest() {
98 request_was_cancelled_
= true;
101 void AndroidAccessTokenFetcher::OnAccessTokenResponse(
102 const GoogleServiceAuthError
& error
,
103 const std::string
& access_token
,
104 const base::Time
& expiration_time
) {
105 if (request_was_cancelled_
) {
106 // Ignore the callback if the request was cancelled.
109 if (error
.state() == GoogleServiceAuthError::NONE
) {
110 FireOnGetTokenSuccess(access_token
, expiration_time
);
112 FireOnGetTokenFailure(error
);
117 std::string
AndroidAccessTokenFetcher::CombineScopes(
118 const std::vector
<std::string
>& scopes
) {
119 // The Android AccountManager supports multiple scopes separated by a space:
120 // https://code.google.com/p/google-api-java-client/wiki/OAuth2#Android
122 for (std::vector
<std::string
>::const_iterator it
= scopes
.begin();
123 it
!= scopes
.end(); ++it
) {
133 bool OAuth2TokenServiceDelegateAndroid::is_testing_profile_
= false;
135 OAuth2TokenServiceDelegateAndroid::ErrorInfo::ErrorInfo()
136 : error(GoogleServiceAuthError::NONE
) {}
138 OAuth2TokenServiceDelegateAndroid::ErrorInfo::ErrorInfo(
139 const GoogleServiceAuthError
& error
)
142 OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid(
143 AccountTrackerService
* account_tracker_service
)
144 : account_tracker_service_(account_tracker_service
),
145 fire_refresh_token_loaded_(RT_LOAD_NOT_START
) {
146 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor";
147 DCHECK(account_tracker_service_
);
148 JNIEnv
* env
= AttachCurrentThread();
149 base::android::ScopedJavaLocalRef
<jobject
> local_java_ref
=
150 Java_OAuth2TokenService_create(env
,
151 base::android::GetApplicationContext(),
152 reinterpret_cast<intptr_t>(this));
153 java_ref_
.Reset(env
, local_java_ref
.obj());
155 if (account_tracker_service_
->GetMigrationState() ==
156 AccountTrackerService::MIGRATION_IN_PROGRESS
) {
157 std::vector
<std::string
> accounts
= GetAccounts();
158 std::vector
<std::string
> accounts_id
;
159 for (auto account_name
: accounts
) {
160 AccountInfo account_info
=
161 account_tracker_service_
->FindAccountInfoByEmail(account_name
);
162 DCHECK(!account_info
.gaia
.empty());
163 accounts_id
.push_back(account_info
.gaia
);
165 ScopedJavaLocalRef
<jobjectArray
> java_accounts(
166 base::android::ToJavaArrayOfStrings(env
, accounts_id
));
167 Java_OAuth2TokenService_saveStoredAccounts(
168 env
, base::android::GetApplicationContext(), java_accounts
.obj());
171 if (!is_testing_profile_
) {
172 Java_OAuth2TokenService_validateAccounts(
173 AttachCurrentThread(), java_ref_
.obj(),
174 base::android::GetApplicationContext(), JNI_TRUE
);
178 OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() {
182 ScopedJavaLocalRef
<jobject
> OAuth2TokenServiceDelegateAndroid::GetForProfile(
185 jobject j_profile_android
) {
186 Profile
* profile
= ProfileAndroid::FromProfileAndroid(j_profile_android
);
187 ProfileOAuth2TokenService
* service
=
188 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
189 OAuth2TokenServiceDelegate
* delegate
= service
->GetDelegate();
190 return ScopedJavaLocalRef
<jobject
>(
191 static_cast<OAuth2TokenServiceDelegateAndroid
*>(delegate
)->java_ref_
);
194 static ScopedJavaLocalRef
<jobject
> GetForProfile(
196 const JavaParamRef
<jclass
>& clazz
,
197 const JavaParamRef
<jobject
>& j_profile_android
) {
198 return OAuth2TokenServiceDelegateAndroid::GetForProfile(env
, clazz
,
202 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable(
203 const std::string
& account_id
) const {
204 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable"
205 << " account= " << account_id
;
206 std::string account_name
= MapAccountIdToAccountName(account_id
);
207 JNIEnv
* env
= AttachCurrentThread();
208 ScopedJavaLocalRef
<jstring
> j_account_id
=
209 ConvertUTF8ToJavaString(env
, account_name
);
210 jboolean refresh_token_is_available
=
211 Java_OAuth2TokenService_hasOAuth2RefreshToken(
212 env
, base::android::GetApplicationContext(), j_account_id
.obj());
213 return refresh_token_is_available
== JNI_TRUE
;
216 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenHasError(
217 const std::string
& account_id
) const {
218 auto it
= errors_
.find(account_id
);
219 // TODO(rogerta): should we distinguish between transient and persistent?
220 return it
== errors_
.end() ? false : IsError(it
->second
.error
);
223 void OAuth2TokenServiceDelegateAndroid::UpdateAuthError(
224 const std::string
& account_id
,
225 const GoogleServiceAuthError
& error
) {
226 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError"
227 << " account=" << account_id
228 << " error=" << error
.ToString();
229 if (error
.state() == GoogleServiceAuthError::NONE
) {
230 errors_
.erase(account_id
);
232 // TODO(rogerta): should we distinguish between transient and persistent?
233 errors_
[account_id
] = ErrorInfo(error
);
237 std::vector
<std::string
> OAuth2TokenServiceDelegateAndroid::GetAccounts() {
238 std::vector
<std::string
> accounts
;
239 JNIEnv
* env
= AttachCurrentThread();
240 ScopedJavaLocalRef
<jobjectArray
> j_accounts
=
241 Java_OAuth2TokenService_getAccounts(
242 env
, base::android::GetApplicationContext());
243 // TODO(fgorski): We may decide to filter out some of the accounts.
244 base::android::AppendJavaStringArrayToStringVector(env
, j_accounts
.obj(),
249 std::vector
<std::string
>
250 OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() {
251 std::vector
<std::string
> account_names
;
252 JNIEnv
* env
= AttachCurrentThread();
253 ScopedJavaLocalRef
<jobjectArray
> j_accounts
=
254 Java_OAuth2TokenService_getSystemAccountNames(
255 env
, base::android::GetApplicationContext());
256 base::android::AppendJavaStringArrayToStringVector(env
, j_accounts
.obj(),
258 return account_names
;
261 OAuth2AccessTokenFetcher
*
262 OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher(
263 const std::string
& account_id
,
264 net::URLRequestContextGetter
* getter
,
265 OAuth2AccessTokenConsumer
* consumer
) {
266 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher"
267 << " account= " << account_id
;
268 ValidateAccountId(account_id
);
269 return new AndroidAccessTokenFetcher(consumer
,
270 MapAccountIdToAccountName(account_id
));
273 void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken(
274 const std::string
& account_id
,
275 const std::string
& client_id
,
276 const OAuth2TokenService::ScopeSet
& scopes
,
277 const std::string
& access_token
) {
278 ValidateAccountId(account_id
);
279 JNIEnv
* env
= AttachCurrentThread();
280 ScopedJavaLocalRef
<jstring
> j_access_token
=
281 ConvertUTF8ToJavaString(env
, access_token
);
282 Java_OAuth2TokenService_invalidateOAuth2AuthToken(
283 env
, base::android::GetApplicationContext(), j_access_token
.obj());
286 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
289 jstring j_current_acc
,
290 jboolean j_force_notifications
) {
291 std::string signed_in_account
;
292 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java";
294 signed_in_account
= ConvertJavaStringToUTF8(env
, j_current_acc
);
295 if (!signed_in_account
.empty())
296 signed_in_account
= gaia::CanonicalizeEmail(signed_in_account
);
298 // Clear any auth errors so that client can retry to get access tokens.
301 ValidateAccounts(MapAccountNameToAccountId(signed_in_account
),
302 j_force_notifications
!= JNI_FALSE
);
305 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
306 const std::string
& signed_in_account
,
307 bool force_notifications
) {
308 std::vector
<std::string
> prev_ids
= GetAccounts();
309 std::vector
<std::string
> curr_ids
= GetSystemAccountNames();
310 std::vector
<std::string
> refreshed_ids
;
311 std::vector
<std::string
> revoked_ids
;
312 bool account_validation_result
= true;
314 for (size_t i
= 0; i
< curr_ids
.size(); ++i
)
315 curr_ids
[i
] = MapAccountNameToAccountId(curr_ids
[i
]);
317 for (size_t i
= 0; i
< prev_ids
.size(); ++i
)
318 ValidateAccountId(prev_ids
[i
]);
320 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
321 << " sigined_in_account=" << signed_in_account
322 << " prev_ids=" << prev_ids
.size() << " curr_ids=" << curr_ids
.size()
323 << " force=" << (force_notifications
? "true" : "false");
325 account_validation_result
=
326 ValidateAccounts(signed_in_account
, prev_ids
, curr_ids
, refreshed_ids
,
327 revoked_ids
, force_notifications
);
329 ScopedBatchChange
batch(this);
331 JNIEnv
* env
= AttachCurrentThread();
332 ScopedJavaLocalRef
<jobjectArray
> java_accounts
;
333 if (account_validation_result
) {
334 java_accounts
= base::android::ToJavaArrayOfStrings(env
, curr_ids
);
337 base::android::ToJavaArrayOfStrings(env
, std::vector
<std::string
>());
339 Java_OAuth2TokenService_saveStoredAccounts(
340 env
, base::android::GetApplicationContext(), java_accounts
.obj());
342 for (std::vector
<std::string
>::iterator it
= refreshed_ids
.begin();
343 it
!= refreshed_ids
.end(); it
++) {
344 FireRefreshTokenAvailable(*it
);
347 for (std::vector
<std::string
>::iterator it
= revoked_ids
.begin();
348 it
!= revoked_ids
.end(); it
++) {
349 FireRefreshTokenRevoked(*it
);
352 if (fire_refresh_token_loaded_
== RT_WAIT_FOR_VALIDATION
) {
353 fire_refresh_token_loaded_
= RT_LOADED
;
354 FireRefreshTokensLoaded();
355 } else if (fire_refresh_token_loaded_
== RT_LOAD_NOT_START
) {
356 fire_refresh_token_loaded_
= RT_HAS_BEEN_VALIDATED
;
359 // Clear accounts no longer exist on device from AccountTrackerService.
360 std::vector
<AccountInfo
> accounts_info
=
361 account_tracker_service_
->GetAccounts();
362 for (auto info
: accounts_info
) {
363 auto it
= curr_ids
.begin();
364 for (; it
!= curr_ids
.end(); ++it
) {
365 if (*it
== info
.account_id
)
368 if (it
== curr_ids
.end())
369 account_tracker_service_
->RemoveAccount(info
.account_id
);
372 // No need to wait for SigninManager to finish migration if not signed in.
373 if (account_tracker_service_
->GetMigrationState() ==
374 AccountTrackerService::MIGRATION_IN_PROGRESS
&&
375 signed_in_account
.empty()) {
376 account_tracker_service_
->SetMigrationDone();
380 bool OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
381 const std::string
& signed_in_account
,
382 const std::vector
<std::string
>& prev_account_ids
,
383 const std::vector
<std::string
>& curr_account_ids
,
384 std::vector
<std::string
>& refreshed_ids
,
385 std::vector
<std::string
>& revoked_ids
,
386 bool force_notifications
) {
387 if (std::find(curr_account_ids
.begin(), curr_account_ids
.end(),
388 signed_in_account
) != curr_account_ids
.end()) {
389 // Test to see if an account is removed from the Android AccountManager.
390 // If so, invoke FireRefreshTokenRevoked to notify the reconcilor.
391 for (std::vector
<std::string
>::const_iterator it
= prev_account_ids
.begin();
392 it
!= prev_account_ids
.end(); it
++) {
393 if (*it
== signed_in_account
)
396 if (std::find(curr_account_ids
.begin(), curr_account_ids
.end(), *it
) ==
397 curr_account_ids
.end()) {
398 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
399 << "revoked=" << *it
;
400 revoked_ids
.push_back(*it
);
404 if (force_notifications
||
405 std::find(prev_account_ids
.begin(), prev_account_ids
.end(),
406 signed_in_account
) == prev_account_ids
.end()) {
407 // Always fire the primary signed in account first.
408 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
409 << "refreshed=" << signed_in_account
;
410 refreshed_ids
.push_back(signed_in_account
);
413 for (std::vector
<std::string
>::const_iterator it
= curr_account_ids
.begin();
414 it
!= curr_account_ids
.end(); it
++) {
415 if (*it
!= signed_in_account
) {
416 if (force_notifications
||
417 std::find(prev_account_ids
.begin(), prev_account_ids
.end(), *it
) ==
418 prev_account_ids
.end()) {
419 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
420 << "refreshed=" << *it
;
421 refreshed_ids
.push_back(*it
);
427 // Currently signed in account does not any longer exist among accounts on
428 // system together with all other accounts.
429 if (std::find(prev_account_ids
.begin(), prev_account_ids
.end(),
430 signed_in_account
) != prev_account_ids
.end()) {
431 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
432 << "revoked=" << signed_in_account
;
433 revoked_ids
.push_back(signed_in_account
);
435 for (std::vector
<std::string
>::const_iterator it
= prev_account_ids
.begin();
436 it
!= prev_account_ids
.end(); it
++) {
437 if (*it
== signed_in_account
)
439 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
440 << "revoked=" << *it
;
441 revoked_ids
.push_back(*it
);
447 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailableFromJava(
450 const jstring account_name
) {
451 std::string account_id
=
452 MapAccountNameToAccountId(ConvertJavaStringToUTF8(env
, account_name
));
453 // Notify native observers.
454 FireRefreshTokenAvailable(account_id
);
457 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable(
458 const std::string
& account_id
) {
459 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable id="
461 JNIEnv
* env
= AttachCurrentThread();
462 ScopedJavaLocalRef
<jstring
> account_name
=
463 ConvertUTF8ToJavaString(env
, MapAccountIdToAccountName(account_id
));
464 Java_OAuth2TokenService_notifyRefreshTokenAvailable(env
, java_ref_
.obj(),
466 OAuth2TokenServiceDelegate::FireRefreshTokenAvailable(account_id
);
469 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevokedFromJava(
472 const jstring account_name
) {
473 std::string account_id
=
474 MapAccountNameToAccountId(ConvertJavaStringToUTF8(env
, account_name
));
475 // Notify native observers.
476 FireRefreshTokenRevoked(account_id
);
479 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked(
480 const std::string
& account_id
) {
481 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked id="
483 JNIEnv
* env
= AttachCurrentThread();
484 ScopedJavaLocalRef
<jstring
> account_name
=
485 ConvertUTF8ToJavaString(env
, MapAccountIdToAccountName(account_id
));
486 Java_OAuth2TokenService_notifyRefreshTokenRevoked(env
, java_ref_
.obj(),
488 OAuth2TokenServiceDelegate::FireRefreshTokenRevoked(account_id
);
491 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoadedFromJava(
494 // Notify native observers.
495 FireRefreshTokensLoaded();
498 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded() {
499 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded";
500 JNIEnv
* env
= AttachCurrentThread();
501 Java_OAuth2TokenService_notifyRefreshTokensLoaded(env
, java_ref_
.obj());
502 OAuth2TokenServiceDelegate::FireRefreshTokensLoaded();
505 void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() {
506 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials";
507 ScopedBatchChange
batch(this);
508 std::vector
<std::string
> accounts
= GetAccounts();
509 for (std::vector
<std::string
>::iterator it
= accounts
.begin();
510 it
!= accounts
.end(); it
++) {
511 FireRefreshTokenRevoked(*it
);
514 // Clear everything on the Java side as well.
515 std::vector
<std::string
> empty
;
516 JNIEnv
* env
= AttachCurrentThread();
517 ScopedJavaLocalRef
<jobjectArray
> java_accounts(
518 base::android::ToJavaArrayOfStrings(env
, empty
));
519 Java_OAuth2TokenService_saveStoredAccounts(
520 env
, base::android::GetApplicationContext(), java_accounts
.obj());
523 void OAuth2TokenServiceDelegateAndroid::LoadCredentials(
524 const std::string
& primary_account_id
) {
525 if (fire_refresh_token_loaded_
== RT_HAS_BEEN_VALIDATED
) {
526 fire_refresh_token_loaded_
= RT_LOADED
;
527 FireRefreshTokensLoaded();
528 } else if (fire_refresh_token_loaded_
== RT_LOAD_NOT_START
) {
529 fire_refresh_token_loaded_
= RT_WAIT_FOR_VALIDATION
;
533 std::string
OAuth2TokenServiceDelegateAndroid::MapAccountIdToAccountName(
534 const std::string
& account_id
) const {
535 std::string account_name
=
536 account_tracker_service_
->GetAccountInfo(account_id
).email
;
537 DCHECK(!account_name
.empty() || account_id
.empty());
541 std::string
OAuth2TokenServiceDelegateAndroid::MapAccountNameToAccountId(
542 const std::string
& account_name
) const {
543 std::string account_id
=
544 account_tracker_service_
->FindAccountInfoByEmail(account_name
).account_id
;
545 DCHECK(!account_id
.empty() || account_name
.empty());
549 // Called from Java when fetching of an OAuth2 token is finished. The
550 // |authToken| param is only valid when |result| is true.
551 void OAuth2TokenFetched(JNIEnv
* env
,
552 const JavaParamRef
<jclass
>& clazz
,
553 const JavaParamRef
<jstring
>& authToken
,
554 jboolean isTransientError
,
555 jlong nativeCallback
) {
558 token
= ConvertJavaStringToUTF8(env
, authToken
);
559 scoped_ptr
<FetchOAuth2TokenCallback
> heap_callback(
560 reinterpret_cast<FetchOAuth2TokenCallback
*>(nativeCallback
));
561 GoogleServiceAuthError
563 ? GoogleServiceAuthError::NONE
565 ? GoogleServiceAuthError::CONNECTION_FAILED
566 : GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
567 heap_callback
->Run(err
, token
, base::Time());
571 bool OAuth2TokenServiceDelegateAndroid::Register(JNIEnv
* env
) {
572 return RegisterNativesImpl(env
);