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.
4
5#include "chrome/browser/sync/profile_sync_service_android.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_string.h"
9#include "base/bind.h"
10#include "base/i18n/time_formatting.h"
11#include "base/json/json_writer.h"
12#include "base/logging.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/time/time.h"
17#include "chrome/browser/browser_process.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/profiles/profile_manager.h"
20#include "chrome/browser/signin/signin_manager.h"
21#include "chrome/browser/signin/signin_manager_factory.h"
22#include "chrome/browser/sync/about_sync_util.h"
23#include "chrome/browser/sync/profile_sync_service.h"
24#include "chrome/browser/sync/profile_sync_service_factory.h"
25#include "chrome/browser/sync/sync_prefs.h"
26#include "chrome/browser/sync/sync_ui_util.h"
27#include "chrome/common/pref_names.h"
28#include "content/public/browser/notification_service.h"
29#include "content/public/browser/notification_source.h"
30#include "google/cacheinvalidation/types.pb.h"
31#include "google_apis/gaia/gaia_constants.h"
32#include "google_apis/gaia/google_service_auth_error.h"
33#include "grit/generated_resources.h"
34#include "jni/ProfileSyncService_jni.h"
35#include "sync/internal_api/public/read_transaction.h"
36#include "sync/notifier/object_id_invalidation_map.h"
37#include "ui/base/l10n/l10n_util.h"
38
39using base::android::AttachCurrentThread;
40using base::android::CheckException;
41using base::android::ConvertJavaStringToUTF8;
42using base::android::ConvertUTF8ToJavaString;
43using base::android::ScopedJavaLocalRef;
44using content::BrowserThread;
45
46namespace {
47
48enum {
49#define DEFINE_MODEL_TYPE_SELECTION(name,value)  name = value,
50#include "chrome/browser/sync/profile_sync_service_model_type_selection_android.h"
51#undef DEFINE_MODEL_TYPE_SELECTION
52};
53
54}  // namespace
55
56ProfileSyncServiceAndroid::ProfileSyncServiceAndroid(JNIEnv* env, jobject obj)
57    : profile_(NULL),
58      sync_service_(NULL),
59      weak_java_profile_sync_service_(env, obj) {
60  if (g_browser_process == NULL ||
61      g_browser_process->profile_manager() == NULL) {
62    NOTREACHED() << "Browser process or profile manager not initialized";
63    return;
64  }
65
66  profile_ = ProfileManager::GetActiveUserProfile();
67  if (profile_ == NULL) {
68    NOTREACHED() << "Sync Init: Profile not found.";
69    return;
70  }
71
72  sync_prefs_.reset(new browser_sync::SyncPrefs(profile_->GetPrefs()));
73
74  sync_service_ =
75      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_);
76  DCHECK(sync_service_);
77}
78
79void ProfileSyncServiceAndroid::Init() {
80  sync_service_->AddObserver(this);
81}
82
83void ProfileSyncServiceAndroid::RemoveObserver() {
84  if (sync_service_->HasObserver(this)) {
85    sync_service_->RemoveObserver(this);
86  }
87}
88
89ProfileSyncServiceAndroid::~ProfileSyncServiceAndroid() {
90  RemoveObserver();
91}
92
93void ProfileSyncServiceAndroid::SendNudgeNotification(
94    int object_source,
95    const std::string& str_object_id,
96    int64 version,
97    const std::string& state) {
98  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
99
100  // TODO(nileshagrawal): Merge this with ChromeInvalidationClient::Invalidate.
101  // Construct the ModelTypeStateMap and send it over with the notification.
102  invalidation::ObjectId object_id(
103      object_source,
104      str_object_id);
105  syncer::ObjectIdInvalidationMap object_ids_with_states;
106  if (version == ipc::invalidation::Constants::UNKNOWN) {
107    object_ids_with_states.Insert(
108        syncer::Invalidation::InitUnknownVersion(object_id));
109  } else {
110    ObjectIdVersionMap::iterator it =
111        max_invalidation_versions_.find(object_id);
112    if ((it != max_invalidation_versions_.end()) &&
113        (version <= it->second)) {
114      DVLOG(1) << "Dropping redundant invalidation with version " << version;
115      return;
116    }
117    max_invalidation_versions_[object_id] = version;
118    object_ids_with_states.Insert(
119        syncer::Invalidation::Init(object_id, version, state));
120  }
121
122  content::NotificationService::current()->Notify(
123      chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
124      content::Source<Profile>(profile_),
125      content::Details<const syncer::ObjectIdInvalidationMap>(
126          &object_ids_with_states));
127}
128
129void ProfileSyncServiceAndroid::OnStateChanged() {
130  // Notify the java world that our sync state has changed.
131  JNIEnv* env = AttachCurrentThread();
132  Java_ProfileSyncService_syncStateChanged(
133      env, weak_java_profile_sync_service_.get(env).obj());
134}
135
136void ProfileSyncServiceAndroid::EnableSync(JNIEnv* env, jobject) {
137  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
138  // Don't need to do anything if we're already enabled.
139  if (sync_prefs_->IsStartSuppressed())
140    sync_service_->UnsuppressAndStart();
141  else
142    DVLOG(2) << "Ignoring call to EnableSync() because sync is already enabled";
143}
144
145void ProfileSyncServiceAndroid::DisableSync(JNIEnv* env, jobject) {
146  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
147  // Don't need to do anything if we're already disabled.
148  if (!sync_prefs_->IsStartSuppressed()) {
149    sync_service_->StopAndSuppress();
150  } else {
151    DVLOG(2)
152        << "Ignoring call to DisableSync() because sync is already disabled";
153  }
154}
155
156void ProfileSyncServiceAndroid::SignInSync(JNIEnv* env, jobject) {
157  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
158  // Just return if sync already has everything it needs to start up (sync
159  // should start up automatically as long as it has credentials). This can
160  // happen normally if (for example) the user closes and reopens the sync
161  // settings window quickly during initial startup.
162  if (sync_service_->IsSyncEnabledAndLoggedIn() &&
163      sync_service_->IsOAuthRefreshTokenAvailable() &&
164      sync_service_->HasSyncSetupCompleted()) {
165    return;
166  }
167
168  // Enable sync (if we don't have credentials yet, this will enable sync but
169  // will not start it up - sync will start once credentials arrive).
170  sync_service_->UnsuppressAndStart();
171}
172
173void ProfileSyncServiceAndroid::SignOutSync(JNIEnv* env, jobject) {
174  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
175  DCHECK(profile_);
176  sync_service_->DisableForUser();
177
178  // Need to clear suppress start flag manually
179  sync_prefs_->SetStartSuppressed(false);
180}
181
182ScopedJavaLocalRef<jstring> ProfileSyncServiceAndroid::QuerySyncStatusSummary(
183    JNIEnv* env, jobject) {
184  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
185  DCHECK(profile_);
186  std::string status(sync_service_->QuerySyncStatusSummaryString());
187  return ConvertUTF8ToJavaString(env, status);
188}
189
190jboolean ProfileSyncServiceAndroid::SetSyncSessionsId(
191    JNIEnv* env, jobject obj, jstring tag) {
192  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
193  DCHECK(profile_);
194  std::string machine_tag = ConvertJavaStringToUTF8(env, tag);
195  sync_prefs_->SetSyncSessionsGUID(machine_tag);
196  return true;
197}
198
199jint ProfileSyncServiceAndroid::GetAuthError(JNIEnv* env, jobject) {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201  return sync_service_->GetAuthError().state();
202}
203
204jboolean ProfileSyncServiceAndroid::IsEncryptEverythingEnabled(
205    JNIEnv* env, jobject) {
206  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
207  return sync_service_->EncryptEverythingEnabled();
208}
209
210jboolean ProfileSyncServiceAndroid::IsSyncInitialized(JNIEnv* env, jobject) {
211  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212  return sync_service_->sync_initialized();
213}
214
215jboolean ProfileSyncServiceAndroid::IsFirstSetupInProgress(
216    JNIEnv* env, jobject) {
217  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
218  return sync_service_->FirstSetupInProgress();
219}
220
221jboolean ProfileSyncServiceAndroid::IsPassphraseRequired(JNIEnv* env, jobject) {
222  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
223  return sync_service_->IsPassphraseRequired();
224}
225
226jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForDecryption(
227    JNIEnv* env, jobject obj) {
228  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
229  // In case of CUSTOM_PASSPHRASE we always sync passwords. Prompt the user for
230  // a passphrase if cryptographer has any pending keys.
231  if (sync_service_->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE) {
232    return !IsCryptographerReady(env, obj);
233  }
234  if (sync_service_->IsPassphraseRequiredForDecryption()) {
235    // Passwords datatype should never prompt for a passphrase, except when
236    // user is using a custom passphrase. Do not prompt for a passphrase if
237    // passwords are the only encrypted datatype. This prevents a temporary
238    // notification for passphrase  when PSS has not completed configuring
239    // DataTypeManager, after configuration password datatype shall be disabled.
240    const syncer::ModelTypeSet encrypted_types =
241        sync_service_->GetEncryptedDataTypes();
242    return !encrypted_types.Equals(syncer::ModelTypeSet(syncer::PASSWORDS));
243  }
244  return false;
245}
246
247jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForExternalType(
248    JNIEnv* env, jobject) {
249  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
250  return
251      sync_service_->passphrase_required_reason() == syncer::REASON_DECRYPTION;
252}
253
254jboolean ProfileSyncServiceAndroid::IsUsingSecondaryPassphrase(
255    JNIEnv* env, jobject) {
256  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257  return sync_service_->IsUsingSecondaryPassphrase();
258}
259
260jboolean ProfileSyncServiceAndroid::SetDecryptionPassphrase(
261    JNIEnv* env, jobject obj, jstring passphrase) {
262  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
263  std::string key = ConvertJavaStringToUTF8(env, passphrase);
264  return sync_service_->SetDecryptionPassphrase(key);
265}
266
267void ProfileSyncServiceAndroid::SetEncryptionPassphrase(
268    JNIEnv* env, jobject obj, jstring passphrase, jboolean is_gaia) {
269  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270  std::string key = ConvertJavaStringToUTF8(env, passphrase);
271  sync_service_->SetEncryptionPassphrase(
272      key,
273      is_gaia ? ProfileSyncService::IMPLICIT : ProfileSyncService::EXPLICIT);
274}
275
276jboolean ProfileSyncServiceAndroid::IsCryptographerReady(JNIEnv* env, jobject) {
277  syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
278  return sync_service_->IsCryptographerReady(&trans);
279}
280
281jint ProfileSyncServiceAndroid::GetPassphraseType(JNIEnv* env, jobject) {
282  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
283  return sync_service_->GetPassphraseType();
284}
285
286jboolean ProfileSyncServiceAndroid::HasExplicitPassphraseTime(
287    JNIEnv* env, jobject) {
288  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
289  base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime();
290  return !passphrase_time.is_null();
291}
292
293ScopedJavaLocalRef<jstring>
294    ProfileSyncServiceAndroid::GetSyncEnterGooglePassphraseBodyWithDateText(
295        JNIEnv* env, jobject) {
296  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
297  base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime();
298  base::string16 passphrase_time_str =
299      base::TimeFormatShortDate(passphrase_time);
300  return base::android::ConvertUTF16ToJavaString(env,
301      l10n_util::GetStringFUTF16(
302        IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE,
303        passphrase_time_str));
304}
305
306ScopedJavaLocalRef<jstring>
307    ProfileSyncServiceAndroid::GetSyncEnterCustomPassphraseBodyWithDateText(
308        JNIEnv* env, jobject) {
309  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310  base::Time passphrase_time = sync_service_->GetExplicitPassphraseTime();
311  base::string16 passphrase_time_str =
312      base::TimeFormatShortDate(passphrase_time);
313  return base::android::ConvertUTF16ToJavaString(env,
314      l10n_util::GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE,
315        passphrase_time_str));
316}
317
318ScopedJavaLocalRef<jstring>
319    ProfileSyncServiceAndroid::GetCurrentSignedInAccountText(
320        JNIEnv* env, jobject) {
321  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322  const std::string& sync_username =
323      SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername();
324  return base::android::ConvertUTF16ToJavaString(env,
325      l10n_util::GetStringFUTF16(
326          IDS_SYNC_ACCOUNT_SYNCING_TO_USER,
327          ASCIIToUTF16(sync_username)));
328}
329
330ScopedJavaLocalRef<jstring>
331    ProfileSyncServiceAndroid::GetSyncEnterCustomPassphraseBodyText(
332        JNIEnv* env, jobject) {
333  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
334  return ConvertUTF8ToJavaString(
335      env, l10n_util::GetStringUTF8(IDS_SYNC_ENTER_PASSPHRASE_BODY));
336}
337
338jboolean ProfileSyncServiceAndroid::IsSyncKeystoreMigrationDone(
339      JNIEnv* env, jobject) {
340  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
341  syncer::SyncStatus status;
342  bool is_status_valid = sync_service_->QueryDetailedSyncStatus(&status);
343  return is_status_valid && !status.keystore_migration_time.is_null();
344}
345
346jlong ProfileSyncServiceAndroid::GetEnabledDataTypes(JNIEnv* env,
347                                                     jobject obj) {
348  jlong model_type_selection = 0;
349  syncer::ModelTypeSet types = sync_service_->GetActiveDataTypes();
350  types.PutAll(syncer::ControlTypes());
351  if (types.Has(syncer::BOOKMARKS)) {
352    model_type_selection |= BOOKMARK;
353  }
354  if (types.Has(syncer::AUTOFILL)) {
355    model_type_selection |= AUTOFILL;
356  }
357  if (types.Has(syncer::AUTOFILL_PROFILE)) {
358    model_type_selection |= AUTOFILL_PROFILE;
359  }
360  if (types.Has(syncer::PASSWORDS)) {
361    model_type_selection |= PASSWORD;
362  }
363  if (types.Has(syncer::TYPED_URLS)) {
364    model_type_selection |= TYPED_URL;
365  }
366  if (types.Has(syncer::SESSIONS)) {
367    model_type_selection |= SESSION;
368  }
369  if (types.Has(syncer::HISTORY_DELETE_DIRECTIVES)) {
370    model_type_selection |= HISTORY_DELETE_DIRECTIVE;
371  }
372  if (types.Has(syncer::PROXY_TABS)) {
373    model_type_selection |= PROXY_TABS;
374  }
375  if (types.Has(syncer::FAVICON_IMAGES)) {
376    model_type_selection |= FAVICON_IMAGE;
377  }
378  if (types.Has(syncer::FAVICON_TRACKING)) {
379    model_type_selection |= FAVICON_TRACKING;
380  }
381  if (types.Has(syncer::DEVICE_INFO)) {
382    model_type_selection |= DEVICE_INFO;
383  }
384  if (types.Has(syncer::NIGORI)) {
385    model_type_selection |= NIGORI;
386  }
387  if (types.Has(syncer::EXPERIMENTS)) {
388    model_type_selection |= EXPERIMENTS;
389  }
390  return model_type_selection;
391}
392
393void ProfileSyncServiceAndroid::SetPreferredDataTypes(
394    JNIEnv* env, jobject obj,
395    jboolean sync_everything,
396    jlong model_type_selection) {
397  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
398  syncer::ModelTypeSet types;
399  // Note: only user selectable types should be included here.
400  if (model_type_selection & AUTOFILL)
401    types.Put(syncer::AUTOFILL);
402  if (model_type_selection & BOOKMARK)
403    types.Put(syncer::BOOKMARKS);
404  if (model_type_selection & PASSWORD)
405    types.Put(syncer::PASSWORDS);
406  if (model_type_selection & PROXY_TABS)
407    types.Put(syncer::PROXY_TABS);
408  if (model_type_selection & TYPED_URL)
409    types.Put(syncer::TYPED_URLS);
410  DCHECK(syncer::UserSelectableTypes().HasAll(types));
411  sync_service_->OnUserChoseDatatypes(sync_everything, types);
412}
413
414void ProfileSyncServiceAndroid::SetSetupInProgress(
415    JNIEnv* env, jobject obj, jboolean in_progress) {
416  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
417  sync_service_->SetSetupInProgress(in_progress);
418}
419
420void ProfileSyncServiceAndroid::SetSyncSetupCompleted(
421    JNIEnv* env, jobject obj) {
422  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
423  sync_service_->SetSyncSetupCompleted();
424}
425
426jboolean ProfileSyncServiceAndroid::HasSyncSetupCompleted(
427    JNIEnv* env, jobject obj) {
428  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
429  return sync_service_->HasSyncSetupCompleted();
430}
431
432jboolean ProfileSyncServiceAndroid::IsStartSuppressed(
433    JNIEnv* env, jobject obj) {
434  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
435  return sync_prefs_->IsStartSuppressed();
436}
437
438void ProfileSyncServiceAndroid::EnableEncryptEverything(
439    JNIEnv* env, jobject obj) {
440  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
441  sync_service_->EnableEncryptEverything();
442}
443
444jboolean ProfileSyncServiceAndroid::HasKeepEverythingSynced(
445    JNIEnv* env, jobject) {
446  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
447  return sync_prefs_->HasKeepEverythingSynced();
448}
449
450jboolean ProfileSyncServiceAndroid::HasUnrecoverableError(
451    JNIEnv* env, jobject) {
452  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
453  return sync_service_->HasUnrecoverableError();
454}
455
456ScopedJavaLocalRef<jstring> ProfileSyncServiceAndroid::GetAboutInfoForTest(
457    JNIEnv* env, jobject) {
458  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
459
460  scoped_ptr<DictionaryValue> about_info =
461      sync_ui_util::ConstructAboutInformation(sync_service_);
462  std::string about_info_json;
463  base::JSONWriter::Write(about_info.get(), &about_info_json);
464
465  return ConvertUTF8ToJavaString(env, about_info_json);
466}
467
468jlong ProfileSyncServiceAndroid::GetLastSyncedTimeForTest(
469    JNIEnv* env, jobject obj) {
470  // Use profile preferences here instead of SyncPrefs to avoid an extra
471  // conversion, since SyncPrefs::GetLastSyncedTime() converts the stored value
472  // to to base::Time.
473  return static_cast<jlong>(
474      profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));
475}
476
477void ProfileSyncServiceAndroid::NudgeSyncer(JNIEnv* env,
478                                            jobject obj,
479                                            jint objectSource,
480                                            jstring objectId,
481                                            jlong version,
482                                            jstring state) {
483  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
484  SendNudgeNotification(objectSource, ConvertJavaStringToUTF8(env, objectId),
485                        version, ConvertJavaStringToUTF8(env, state));
486}
487
488void ProfileSyncServiceAndroid::NudgeSyncerForAllTypes(JNIEnv* env,
489                                                       jobject obj) {
490  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
491  syncer::ObjectIdInvalidationMap object_ids_with_states;
492  content::NotificationService::current()->Notify(
493        chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
494        content::Source<Profile>(profile_),
495        content::Details<const syncer::ObjectIdInvalidationMap>(
496            &object_ids_with_states));
497}
498
499// static
500ProfileSyncServiceAndroid*
501    ProfileSyncServiceAndroid::GetProfileSyncServiceAndroid() {
502  return reinterpret_cast<ProfileSyncServiceAndroid*>(
503          Java_ProfileSyncService_getProfileSyncServiceAndroid(
504      AttachCurrentThread(), base::android::GetApplicationContext()));
505}
506
507static jlong Init(JNIEnv* env, jobject obj) {
508  ProfileSyncServiceAndroid* profile_sync_service_android =
509      new ProfileSyncServiceAndroid(env, obj);
510  profile_sync_service_android->Init();
511  return reinterpret_cast<intptr_t>(profile_sync_service_android);
512}
513
514// static
515bool ProfileSyncServiceAndroid::Register(JNIEnv* env) {
516  return RegisterNativesImpl(env);
517}
518