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