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()).Release(), 110 ConvertUTF16ToJavaString(env, current_navigation.title()).Release(), 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 DictionaryValue* pref_collapsed_sessions = pref_update.Get(); 217 scoped_ptr<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).Release(), 240 ConvertUTF8ToJavaString(env, session.session_name).Release(), 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