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