Home | History | Annotate | Download | only in android
      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/android/foreign_session_helper.h"
      6 
      7 #include <jni.h>
      8 
      9 #include "base/android/jni_string.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     12 #include "chrome/browser/profiles/profile_android.h"
     13 #include "chrome/browser/sync/glue/session_model_associator.h"
     14 #include "chrome/browser/sync/profile_sync_service.h"
     15 #include "chrome/browser/sync/profile_sync_service_factory.h"
     16 #include "chrome/browser/ui/android/tab_model/tab_model.h"
     17 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
     18 #include "chrome/common/pref_names.h"
     19 #include "chrome/common/url_constants.h"
     20 #include "content/public/browser/notification_source.h"
     21 #include "content/public/browser/web_contents.h"
     22 #include "jni/ForeignSessionHelper_jni.h"
     23 
     24 using base::android::ScopedJavaGlobalRef;
     25 using base::android::ScopedJavaLocalRef;
     26 using base::android::AttachCurrentThread;
     27 using base::android::ConvertUTF16ToJavaString;
     28 using base::android::ConvertUTF8ToJavaString;
     29 using base::android::ConvertJavaStringToUTF8;
     30 using browser_sync::SessionModelAssociator;
     31 using browser_sync::SyncedSession;
     32 
     33 namespace {
     34 
     35 SessionModelAssociator* GetSessionModelAssociator(Profile* profile) {
     36   ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
     37       GetForProfile(profile);
     38 
     39   // Only return the associator if it exists and it is done syncing sessions.
     40   if (!service || !service->ShouldPushChanges())
     41     return NULL;
     42 
     43   return service->GetSessionModelAssociator();
     44 }
     45 
     46 void CopyTabsToJava(
     47     JNIEnv* env,
     48     const SessionWindow* window,
     49     ScopedJavaLocalRef<jobject>& j_window) {
     50   for (std::vector<SessionTab*>::const_iterator tab_it = window->tabs.begin();
     51       tab_it != window->tabs.end(); ++tab_it) {
     52     const SessionTab &tab = **tab_it;
     53 
     54     if (tab.navigations.empty())
     55       continue;
     56 
     57     const ::sessions::SerializedNavigationEntry& current_navigation =
     58         tab.navigations.at(tab.current_navigation_index);
     59 
     60     GURL tab_url = current_navigation.virtual_url();
     61     if (tab_url.SchemeIs(chrome::kChromeNativeScheme) ||
     62         (tab_url.SchemeIs(chrome::kChromeUIScheme) &&
     63             tab_url.host() == chrome::kChromeUINewTabHost))
     64       continue;
     65 
     66     Java_ForeignSessionHelper_pushTab(
     67         env, j_window.obj(),
     68         ConvertUTF8ToJavaString(env, tab_url.spec()).Release(),
     69         ConvertUTF16ToJavaString(env, current_navigation.title()).Release(),
     70         tab.timestamp.ToInternalValue(), tab.tab_id.id());
     71   }
     72 }
     73 
     74 void CopyWindowsToJava(
     75     JNIEnv* env,
     76     const SyncedSession* session,
     77     ScopedJavaLocalRef<jobject>& j_session) {
     78   for (SyncedSession::SyncedWindowMap::const_iterator it =
     79       session->windows.begin(); it != session->windows.end(); ++it) {
     80     const SessionWindow* window = it->second;
     81 
     82     ScopedJavaLocalRef<jobject> last_pushed_window;
     83     last_pushed_window.Reset(
     84         Java_ForeignSessionHelper_pushWindow(
     85             env, j_session.obj(), window->timestamp.ToInternalValue(),
     86             window->window_id.id()));
     87 
     88     CopyTabsToJava(env, window, last_pushed_window);
     89   }
     90 }
     91 
     92 }  // namespace
     93 
     94 static jint Init(JNIEnv* env, jclass clazz, jobject profile) {
     95   ForeignSessionHelper* foreign_session_helper = new ForeignSessionHelper(
     96       ProfileAndroid::FromProfileAndroid(profile));
     97   return reinterpret_cast<jint>(foreign_session_helper);
     98 }
     99 
    100 ForeignSessionHelper::ForeignSessionHelper(Profile* profile)
    101     : profile_(profile) {
    102   ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
    103       GetForProfile(profile);
    104 
    105   registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
    106                  content::Source<ProfileSyncService>(service));
    107   registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
    108                  content::Source<Profile>(profile));
    109   registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
    110                  content::Source<Profile>(profile));
    111 }
    112 
    113 ForeignSessionHelper::~ForeignSessionHelper() {
    114 }
    115 
    116 void ForeignSessionHelper::Destroy(JNIEnv* env, jobject obj) {
    117   delete this;
    118 }
    119 
    120 jboolean ForeignSessionHelper::IsTabSyncEnabled(JNIEnv* env, jobject obj) {
    121   ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
    122       GetForProfile(profile_);
    123   return service && service->GetActiveDataTypes().Has(syncer::PROXY_TABS);
    124 }
    125 
    126 void ForeignSessionHelper::SetOnForeignSessionCallback(JNIEnv* env,
    127                                                        jobject obj,
    128                                                        jobject callback) {
    129   callback_.Reset(env, callback);
    130 }
    131 
    132 void ForeignSessionHelper::Observe(
    133     int type, const content::NotificationSource& source,
    134     const content::NotificationDetails& details) {
    135   if (callback_.is_null())
    136     return;
    137 
    138   JNIEnv* env = AttachCurrentThread();
    139 
    140   switch (type) {
    141     case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED:
    142       // Tab sync is disabled, so clean up data about collapsed sessions.
    143       profile_->GetPrefs()->ClearPref(
    144           prefs::kNtpCollapsedForeignSessions);
    145       // Purposeful fall through.
    146     case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
    147     case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
    148       Java_ForeignSessionCallback_onUpdated(env, callback_.obj());
    149       break;
    150     default:
    151       NOTREACHED();
    152   }
    153 }
    154 
    155 jboolean ForeignSessionHelper::GetForeignSessions(JNIEnv* env,
    156                                                   jobject obj,
    157                                                   jobject result) {
    158   SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
    159   if (!associator)
    160     return false;
    161 
    162   std::vector<const browser_sync::SyncedSession*> sessions;
    163   if (!associator->GetAllForeignSessions(&sessions))
    164     return false;
    165 
    166   // Use a pref to keep track of sessions that were collapsed by the user.
    167   // To prevent the pref from accumulating stale sessions, clear it each time
    168   // and only add back sessions that are still current.
    169   DictionaryPrefUpdate pref_update(profile_->GetPrefs(),
    170                                    prefs::kNtpCollapsedForeignSessions);
    171   DictionaryValue* pref_collapsed_sessions = pref_update.Get();
    172   scoped_ptr<DictionaryValue> collapsed_sessions(
    173       pref_collapsed_sessions->DeepCopy());
    174   pref_collapsed_sessions->Clear();
    175 
    176   ScopedJavaLocalRef<jobject> last_pushed_session;
    177   ScopedJavaLocalRef<jobject> last_pushed_window;
    178 
    179   // Note: we don't own the SyncedSessions themselves.
    180   for (size_t i = 0; i < sessions.size(); ++i) {
    181     const browser_sync::SyncedSession* session = sessions[i];
    182 
    183     const bool is_collapsed = collapsed_sessions->HasKey(session->session_tag);
    184 
    185     if (is_collapsed)
    186       pref_collapsed_sessions->SetBoolean(session->session_tag, true);
    187 
    188     last_pushed_session.Reset(
    189         Java_ForeignSessionHelper_pushSession(
    190             env,
    191             result,
    192             ConvertUTF8ToJavaString(env, session->session_tag).Release(),
    193             ConvertUTF8ToJavaString(env, session->session_name).Release(),
    194             ConvertUTF8ToJavaString(env,
    195                                     session->DeviceTypeAsString()).Release(),
    196             session->modified_time.ToInternalValue()));
    197 
    198     CopyWindowsToJava(env, session, last_pushed_session);
    199   }
    200 
    201   return true;
    202 }
    203 
    204 jboolean ForeignSessionHelper::OpenForeignSessionTab(JNIEnv* env,
    205                                                      jobject obj,
    206                                                      jstring session_tag,
    207                                                      jint tab_id) {
    208   SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
    209   if (!associator) {
    210     LOG(ERROR) << "Null SessionModelAssociator returned.";
    211     return false;
    212   }
    213 
    214   const SessionTab* tab;
    215 
    216   if (!associator->GetForeignTab(ConvertJavaStringToUTF8(env, session_tag),
    217                                  tab_id, &tab)) {
    218     LOG(ERROR) << "Failed to load foreign tab.";
    219     return false;
    220   }
    221 
    222   if (tab->navigations.empty()) {
    223     LOG(ERROR) << "Foreign tab no longer has valid navigations.";
    224     return false;
    225   }
    226 
    227   TabModel* tab_model = TabModelList::GetTabModelWithProfile(profile_);
    228   DCHECK(tab_model);
    229   if (!tab_model)
    230     return false;
    231 
    232   std::vector<content::NavigationEntry*> entries =
    233       sessions::SerializedNavigationEntry::ToNavigationEntries(
    234           tab->navigations, profile_);
    235   content::WebContents* new_web_contents = content::WebContents::Create(
    236       content::WebContents::CreateParams(profile_));
    237   int selected_index = tab->normalized_navigation_index();
    238   new_web_contents->GetController().Restore(
    239       selected_index,
    240       content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
    241       &entries);
    242   tab_model->CreateTab(new_web_contents);
    243 
    244   return true;
    245 }
    246 
    247 void ForeignSessionHelper::SetForeignSessionCollapsed(JNIEnv* env, jobject obj,
    248                                                       jstring session_tag,
    249                                                       jboolean is_collapsed) {
    250   // Store session tags for collapsed sessions in a preference so that the
    251   // collapsed state persists.
    252   PrefService* prefs = profile_->GetPrefs();
    253   DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
    254   if (is_collapsed)
    255     update.Get()->SetBoolean(ConvertJavaStringToUTF8(env, session_tag), true);
    256   else
    257     update.Get()->Remove(ConvertJavaStringToUTF8(env, session_tag), NULL);
    258 }
    259 
    260 void ForeignSessionHelper::DeleteForeignSession(JNIEnv* env, jobject obj,
    261                                                 jstring session_tag) {
    262   SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
    263   if (associator)
    264     associator->DeleteForeignSession(ConvertJavaStringToUTF8(env, session_tag));
    265 }
    266 
    267 // static
    268 bool ForeignSessionHelper::RegisterForeignSessionHelper(JNIEnv* env) {
    269   return RegisterNativesImpl(env);
    270 }
    271