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