| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sync/profile_sync_service_android.h" |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_string.h" |
| #include "base/bind.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/signin/signin_manager.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/signin/token_service.h" |
| #include "chrome/browser/signin/token_service_factory.h" |
| #include "chrome/browser/sync/about_sync_util.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/sync/sync_prefs.h" |
| #include "chrome/browser/sync/sync_ui_util.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "google/cacheinvalidation/types.pb.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "google_apis/gaia/oauth2_token_service.h" |
| #include "grit/generated_resources.h" |
| #include "jni/ProfileSyncService_jni.h" |
| #include "sync/internal_api/public/read_transaction.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using base::android::AttachCurrentThread; |
| using base::android::CheckException; |
| using base::android::ConvertJavaStringToUTF8; |
| using base::android::ConvertUTF8ToJavaString; |
| using base::android::ScopedJavaLocalRef; |
| using content::BrowserThread; |
| |
| namespace { |
| const char kSyncDisabledStatus[] = "OFFLINE_DISABLED"; |
| |
| enum { |
| #define DEFINE_MODEL_TYPE_SELECTION(name,value) name = value, |
| #include "chrome/browser/sync/profile_sync_service_model_type_selection_android.h" |
| #undef DEFINE_MODEL_TYPE_SELECTION |
| }; |
| |
| } // namespace |
| |
| ProfileSyncServiceAndroid::ProfileSyncServiceAndroid(JNIEnv* env, jobject obj) |
| : profile_(NULL), |
| sync_service_(NULL), |
| weak_java_profile_sync_service_(env, obj) { |
| if (g_browser_process == NULL || |
| g_browser_process->profile_manager() == NULL) { |
| NOTREACHED() << "Browser process or profile manager not initialized"; |
| return; |
| } |
| |
| profile_ = g_browser_process->profile_manager()->GetDefaultProfile(); |
| if (profile_ == NULL) { |
| NOTREACHED() << "Sync Init: Profile not found."; |
| return; |
| } |
| |
| sync_prefs_.reset(new browser_sync::SyncPrefs(profile_->GetPrefs())); |
| |
| sync_service_ = |
| ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_); |
| DCHECK(sync_service_); |
| } |
| |
| void ProfileSyncServiceAndroid::Init() { |
| sync_service_->AddObserver(this); |
| } |
| |
| void ProfileSyncServiceAndroid::RemoveObserver() { |
| if (sync_service_->HasObserver(this)) { |
| sync_service_->RemoveObserver(this); |
| } |
| } |
| |
| ProfileSyncServiceAndroid::~ProfileSyncServiceAndroid() { |
| RemoveObserver(); |
| } |
| |
| void ProfileSyncServiceAndroid::SendNudgeNotification( |
| int object_source, |
| const std::string& str_object_id, |
| int64 version, |
| const std::string& state) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // TODO(nileshagrawal): Merge this with ChromeInvalidationClient::Invalidate. |
| // Construct the ModelTypeStateMap and send it over with the notification. |
| invalidation::ObjectId object_id( |
| object_source, |
| str_object_id); |
| if (version == ipc::invalidation::Constants::UNKNOWN) { |
| version = syncer::Invalidation::kUnknownVersion; |
| } else { |
| ObjectIdVersionMap::iterator it = |
| max_invalidation_versions_.find(object_id); |
| if ((it != max_invalidation_versions_.end()) && |
| (version <= it->second)) { |
| DVLOG(1) << "Dropping redundant invalidation with version " << version; |
| return; |
| } |
| max_invalidation_versions_[object_id] = version; |
| } |
| |
| syncer::ObjectIdSet object_ids; |
| object_ids.insert(object_id); |
| syncer::ObjectIdInvalidationMap object_ids_with_states = |
| syncer::ObjectIdSetToInvalidationMap(object_ids, version, state); |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_SYNC_REFRESH_REMOTE, |
| content::Source<Profile>(profile_), |
| content::Details<const syncer::ObjectIdInvalidationMap>( |
| &object_ids_with_states)); |
| } |
| |
| void ProfileSyncServiceAndroid::OnStateChanged() { |
| // Notify the java world that our sync state has changed. |
| JNIEnv* env = AttachCurrentThread(); |
| Java_ProfileSyncService_syncStateChanged( |
| env, weak_java_profile_sync_service_.get(env).obj()); |
| } |
| |
| void ProfileSyncServiceAndroid::TokenAvailable( |
| JNIEnv* env, jobject, jstring username, jstring auth_token) { |
| std::string token = ConvertJavaStringToUTF8(env, auth_token); |
| TokenServiceFactory::GetForProfile(profile_)->OnIssueAuthTokenSuccess( |
| GaiaConstants::kSyncService, token); |
| } |
| |
| void ProfileSyncServiceAndroid::EnableSync(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // Don't need to do anything if we're already enabled. |
| if (sync_prefs_->IsStartSuppressed()) |
| sync_service_->UnsuppressAndStart(); |
| else |
| DVLOG(2) << "Ignoring call to EnableSync() because sync is already enabled"; |
| } |
| |
| void ProfileSyncServiceAndroid::DisableSync(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // Don't need to do anything if we're already disabled. |
| if (!sync_prefs_->IsStartSuppressed()) { |
| sync_service_->StopAndSuppress(); |
| } else { |
| DVLOG(2) |
| << "Ignoring call to DisableSync() because sync is already disabled"; |
| } |
| } |
| |
| void ProfileSyncServiceAndroid::SignInSync(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // Just return if sync already has everything it needs to start up (sync |
| // should start up automatically as long as it has credentials). This can |
| // happen normally if (for example) the user closes and reopens the sync |
| // settings window quickly during initial startup. |
| if (sync_service_->IsSyncEnabledAndLoggedIn() && |
| sync_service_->IsOAuthRefreshTokenAvailable() && |
| sync_service_->HasSyncSetupCompleted()) { |
| return; |
| } |
| |
| // Enable sync (if we don't have credentials yet, this will enable sync but |
| // will not start it up - sync will start once credentials arrive). |
| sync_service_->UnsuppressAndStart(); |
| } |
| |
| void ProfileSyncServiceAndroid::SignOutSync(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| sync_service_->DisableForUser(); |
| |
| // Need to clear suppress start flag manually |
| sync_prefs_->SetStartSuppressed(false); |
| } |
| |
| ScopedJavaLocalRef<jstring> ProfileSyncServiceAndroid::QuerySyncStatusSummary( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| std::string status(sync_service_->QuerySyncStatusSummaryString()); |
| return ConvertUTF8ToJavaString(env, status); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::SetSyncSessionsId( |
| JNIEnv* env, jobject obj, jstring tag) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| std::string machine_tag = ConvertJavaStringToUTF8(env, tag); |
| sync_prefs_->SetSyncSessionsGUID(machine_tag); |
| return true; |
| } |
| |
| jint ProfileSyncServiceAndroid::GetAuthError(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->GetAuthError().state(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsEncryptEverythingEnabled( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->EncryptEverythingEnabled(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsSyncInitialized(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->sync_initialized(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsFirstSetupInProgress( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->FirstSetupInProgress(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsPassphraseRequired(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->IsPassphraseRequired(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForDecryption( |
| JNIEnv* env, jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // In case of CUSTOM_PASSPHRASE we always sync passwords. Prompt the user for |
| // a passphrase if cryptographer has any pending keys. |
| if (sync_service_->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE) { |
| return !IsCryptographerReady(env, obj); |
| } |
| if (sync_service_->IsPassphraseRequiredForDecryption()) { |
| // Passwords datatype should never prompt for a passphrase, except when |
| // user is using a custom passphrase. Do not prompt for a passphrase if |
| // passwords are the only encrypted datatype. This prevents a temporary |
| // notification for passphrase when PSS has not completed configuring |
| // DataTypeManager, after configuration password datatype shall be disabled. |
| const syncer::ModelTypeSet encrypted_types = |
| sync_service_->GetEncryptedDataTypes(); |
| return !encrypted_types.Equals(syncer::ModelTypeSet(syncer::PASSWORDS)); |
| } |
| return false; |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForExternalType( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return |
| sync_service_->passphrase_required_reason() == syncer::REASON_DECRYPTION; |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsUsingSecondaryPassphrase( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->IsUsingSecondaryPassphrase(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::SetDecryptionPassphrase( |
| JNIEnv* env, jobject obj, jstring passphrase) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| std::string key = ConvertJavaStringToUTF8(env, passphrase); |
| return sync_service_->SetDecryptionPassphrase(key); |
| } |
| |
| void ProfileSyncServiceAndroid::SetEncryptionPassphrase( |
| JNIEnv* env, jobject obj, jstring passphrase, jboolean is_gaia) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| std::string key = ConvertJavaStringToUTF8(env, passphrase); |
| sync_service_->SetEncryptionPassphrase( |
| key, |
| is_gaia ? ProfileSyncService::IMPLICIT : ProfileSyncService::EXPLICIT); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsCryptographerReady(JNIEnv* env, jobject) { |
| syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
| return sync_service_->IsCryptographerReady(&trans); |
| } |
| |
| jint ProfileSyncServiceAndroid::GetPassphraseType(JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->GetPassphraseType(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::HasExplicitPassphraseTime( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime(); |
| return !passphrase_time.is_null(); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| ProfileSyncServiceAndroid::GetSyncEnterGooglePassphraseBodyWithDateText( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime(); |
| string16 passphrase_time_str = base::TimeFormatShortDate(passphrase_time); |
| return base::android::ConvertUTF16ToJavaString(env, |
| l10n_util::GetStringFUTF16( |
| IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE, |
| passphrase_time_str)); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| ProfileSyncServiceAndroid::GetSyncEnterCustomPassphraseBodyWithDateText( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime(); |
| string16 passphrase_time_str = base::TimeFormatShortDate(passphrase_time); |
| return base::android::ConvertUTF16ToJavaString(env, |
| l10n_util::GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE, |
| passphrase_time_str)); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| ProfileSyncServiceAndroid::GetCurrentSignedInAccountText( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const std::string& sync_username = |
| SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername(); |
| return base::android::ConvertUTF16ToJavaString(env, |
| l10n_util::GetStringFUTF16( |
| IDS_SYNC_ACCOUNT_SYNCING_TO_USER, |
| ASCIIToUTF16(sync_username))); |
| } |
| |
| ScopedJavaLocalRef<jstring> |
| ProfileSyncServiceAndroid::GetSyncEnterCustomPassphraseBodyText( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return ConvertUTF8ToJavaString( |
| env, l10n_util::GetStringUTF8(IDS_SYNC_ENTER_PASSPHRASE_BODY)); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsSyncKeystoreMigrationDone( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| syncer::SyncStatus status; |
| bool is_status_valid = sync_service_->QueryDetailedSyncStatus(&status); |
| return is_status_valid && !status.keystore_migration_time.is_null(); |
| } |
| |
| jlong ProfileSyncServiceAndroid::GetEnabledDataTypes(JNIEnv* env, |
| jobject obj) { |
| jlong model_type_selection = 0; |
| syncer::ModelTypeSet types = sync_service_->GetActiveDataTypes(); |
| types.PutAll(syncer::ControlTypes()); |
| if (types.Has(syncer::BOOKMARKS)) { |
| model_type_selection |= BOOKMARK; |
| } |
| if (types.Has(syncer::AUTOFILL)) { |
| model_type_selection |= AUTOFILL; |
| } |
| if (types.Has(syncer::AUTOFILL_PROFILE)) { |
| model_type_selection |= AUTOFILL_PROFILE; |
| } |
| if (types.Has(syncer::PASSWORDS)) { |
| model_type_selection |= PASSWORD; |
| } |
| if (types.Has(syncer::TYPED_URLS)) { |
| model_type_selection |= TYPED_URL; |
| } |
| if (types.Has(syncer::SESSIONS)) { |
| model_type_selection |= SESSION; |
| } |
| if (types.Has(syncer::HISTORY_DELETE_DIRECTIVES)) { |
| model_type_selection |= HISTORY_DELETE_DIRECTIVE; |
| } |
| if (types.Has(syncer::PROXY_TABS)) { |
| model_type_selection |= PROXY_TABS; |
| } |
| if (types.Has(syncer::FAVICON_IMAGES)) { |
| model_type_selection |= FAVICON_IMAGE; |
| } |
| if (types.Has(syncer::FAVICON_TRACKING)) { |
| model_type_selection |= FAVICON_TRACKING; |
| } |
| if (types.Has(syncer::DEVICE_INFO)) { |
| model_type_selection |= DEVICE_INFO; |
| } |
| if (types.Has(syncer::NIGORI)) { |
| model_type_selection |= NIGORI; |
| } |
| if (types.Has(syncer::EXPERIMENTS)) { |
| model_type_selection |= EXPERIMENTS; |
| } |
| return model_type_selection; |
| } |
| |
| void ProfileSyncServiceAndroid::SetPreferredDataTypes( |
| JNIEnv* env, jobject obj, |
| jboolean sync_everything, |
| jlong model_type_selection) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| syncer::ModelTypeSet types; |
| // Note: only user selectable types should be included here. |
| if (model_type_selection & AUTOFILL) |
| types.Put(syncer::AUTOFILL); |
| if (model_type_selection & BOOKMARK) |
| types.Put(syncer::BOOKMARKS); |
| if (model_type_selection & PASSWORD) |
| types.Put(syncer::PASSWORDS); |
| if (model_type_selection & PROXY_TABS) |
| types.Put(syncer::PROXY_TABS); |
| if (model_type_selection & TYPED_URL) |
| types.Put(syncer::TYPED_URLS); |
| DCHECK(syncer::UserSelectableTypes().HasAll(types)); |
| sync_service_->OnUserChoseDatatypes(sync_everything, types); |
| } |
| |
| void ProfileSyncServiceAndroid::SetSetupInProgress( |
| JNIEnv* env, jobject obj, jboolean in_progress) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| sync_service_->SetSetupInProgress(in_progress); |
| } |
| |
| void ProfileSyncServiceAndroid::SetSyncSetupCompleted( |
| JNIEnv* env, jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| sync_service_->SetSyncSetupCompleted(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::HasSyncSetupCompleted( |
| JNIEnv* env, jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->HasSyncSetupCompleted(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::IsStartSuppressed( |
| JNIEnv* env, jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_prefs_->IsStartSuppressed(); |
| } |
| |
| void ProfileSyncServiceAndroid::EnableEncryptEverything( |
| JNIEnv* env, jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| sync_service_->EnableEncryptEverything(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::HasKeepEverythingSynced( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_prefs_->HasKeepEverythingSynced(); |
| } |
| |
| jboolean ProfileSyncServiceAndroid::HasUnrecoverableError( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return sync_service_->HasUnrecoverableError(); |
| } |
| |
| ScopedJavaLocalRef<jstring> ProfileSyncServiceAndroid::GetAboutInfoForTest( |
| JNIEnv* env, jobject) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| scoped_ptr<DictionaryValue> about_info = |
| sync_ui_util::ConstructAboutInformation(sync_service_); |
| std::string about_info_json; |
| base::JSONWriter::Write(about_info.get(), &about_info_json); |
| |
| return ConvertUTF8ToJavaString(env, about_info_json); |
| } |
| |
| jlong ProfileSyncServiceAndroid::GetLastSyncedTimeForTest( |
| JNIEnv* env, jobject obj) { |
| // Use profile preferences here instead of SyncPrefs to avoid an extra |
| // conversion, since SyncPrefs::GetLastSyncedTime() converts the stored value |
| // to to base::Time. |
| return static_cast<jlong>( |
| profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime)); |
| } |
| |
| void ProfileSyncServiceAndroid::NudgeSyncer(JNIEnv* env, |
| jobject obj, |
| jint objectSource, |
| jstring objectId, |
| jlong version, |
| jstring state) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| SendNudgeNotification(objectSource, ConvertJavaStringToUTF8(env, objectId), |
| version, ConvertJavaStringToUTF8(env, state)); |
| } |
| |
| void ProfileSyncServiceAndroid::NudgeSyncerForAllTypes(JNIEnv* env, |
| jobject obj) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| syncer::ObjectIdInvalidationMap object_ids_with_states; |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_SYNC_REFRESH_REMOTE, |
| content::Source<Profile>(profile_), |
| content::Details<const syncer::ObjectIdInvalidationMap>( |
| &object_ids_with_states)); |
| } |
| |
| // static |
| ProfileSyncServiceAndroid* |
| ProfileSyncServiceAndroid::GetProfileSyncServiceAndroid() { |
| return reinterpret_cast<ProfileSyncServiceAndroid*>( |
| Java_ProfileSyncService_getProfileSyncServiceAndroid( |
| AttachCurrentThread(), base::android::GetApplicationContext())); |
| } |
| |
| static int Init(JNIEnv* env, jobject obj) { |
| ProfileSyncServiceAndroid* profile_sync_service_android = |
| new ProfileSyncServiceAndroid(env, obj); |
| profile_sync_service_android->Init(); |
| return reinterpret_cast<jint>(profile_sync_service_android); |
| } |
| |
| // static |
| bool ProfileSyncServiceAndroid::Register(JNIEnv* env) { |
| return RegisterNativesImpl(env); |
| } |