blob: 7d7f48684fe29e25978c518271bb5836399ea8e2 [file] [log] [blame]
[email protected]5365e52c2013-07-31 23:07:171// 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"
[email protected]9eec53fe2013-10-30 20:21:1710#include "base/prefs/scoped_user_pref_update.h"
[email protected]5365e52c2013-07-31 23:07:1711#include "chrome/browser/chrome_notification_types.h"
[email protected]5365e52c2013-07-31 23:07:1712#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
24using base::android::ScopedJavaGlobalRef;
25using base::android::ScopedJavaLocalRef;
26using base::android::AttachCurrentThread;
27using base::android::ConvertUTF16ToJavaString;
28using base::android::ConvertUTF8ToJavaString;
29using base::android::ConvertJavaStringToUTF8;
30using browser_sync::SessionModelAssociator;
31using browser_sync::SyncedSession;
32
33namespace {
34
35SessionModelAssociator* 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
[email protected]9744a15e2013-09-23 20:59:0846bool ShouldSkipTab(const SessionTab& tab) {
47 if (tab.navigations.empty())
48 return true;
49
50 int selected_index = tab.current_navigation_index;
51 if (selected_index < 0 ||
52 selected_index >= static_cast<int>(tab.navigations.size()))
53 return true;
54
[email protected]c068ceb2013-10-30 09:30:1855 const ::sessions::SerializedNavigationEntry& current_navigation =
56 tab.navigations.at(selected_index);
57
58 if (current_navigation.virtual_url().is_empty())
59 return true;
60
[email protected]9744a15e2013-09-23 20:59:0861 return false;
62}
63
64bool ShouldSkipWindow(const SessionWindow& window) {
65 for (std::vector<SessionTab*>::const_iterator tab_it = window.tabs.begin();
66 tab_it != window.tabs.end(); ++tab_it) {
67 const SessionTab &tab = **tab_it;
68 if (!ShouldSkipTab(tab))
69 return false;
70 }
71 return true;
72}
73
74bool ShouldSkipSession(const browser_sync::SyncedSession& session) {
75 for (SyncedSession::SyncedWindowMap::const_iterator it =
76 session.windows.begin(); it != session.windows.end(); ++it) {
77 const SessionWindow &window = *(it->second);
78 if (!ShouldSkipWindow(window))
79 return false;
80 }
81 return true;
82}
83
[email protected]5365e52c2013-07-31 23:07:1784void CopyTabsToJava(
85 JNIEnv* env,
[email protected]9744a15e2013-09-23 20:59:0886 const SessionWindow& window,
[email protected]5365e52c2013-07-31 23:07:1787 ScopedJavaLocalRef<jobject>& j_window) {
[email protected]9744a15e2013-09-23 20:59:0888 for (std::vector<SessionTab*>::const_iterator tab_it = window.tabs.begin();
89 tab_it != window.tabs.end(); ++tab_it) {
[email protected]5365e52c2013-07-31 23:07:1790 const SessionTab &tab = **tab_it;
91
[email protected]9744a15e2013-09-23 20:59:0892 if (ShouldSkipTab(tab))
[email protected]5365e52c2013-07-31 23:07:1793 continue;
94
[email protected]9744a15e2013-09-23 20:59:0895 int selected_index = tab.current_navigation_index;
96 DCHECK(selected_index >= 0);
97 DCHECK(selected_index < static_cast<int>(tab.navigations.size()));
98
[email protected]5365e52c2013-07-31 23:07:1799 const ::sessions::SerializedNavigationEntry& current_navigation =
[email protected]9744a15e2013-09-23 20:59:08100 tab.navigations.at(selected_index);
[email protected]5365e52c2013-07-31 23:07:17101
102 GURL tab_url = current_navigation.virtual_url();
[email protected]5365e52c2013-07-31 23:07:17103
104 Java_ForeignSessionHelper_pushTab(
105 env, j_window.obj(),
106 ConvertUTF8ToJavaString(env, tab_url.spec()).Release(),
107 ConvertUTF16ToJavaString(env, current_navigation.title()).Release(),
[email protected]9744a15e2013-09-23 20:59:08108 tab.timestamp.ToJavaTime(),
109 tab.tab_id.id());
[email protected]5365e52c2013-07-31 23:07:17110 }
111}
112
113void CopyWindowsToJava(
114 JNIEnv* env,
[email protected]9744a15e2013-09-23 20:59:08115 const SyncedSession& session,
[email protected]5365e52c2013-07-31 23:07:17116 ScopedJavaLocalRef<jobject>& j_session) {
117 for (SyncedSession::SyncedWindowMap::const_iterator it =
[email protected]9744a15e2013-09-23 20:59:08118 session.windows.begin(); it != session.windows.end(); ++it) {
119 const SessionWindow &window = *(it->second);
120
121 if (ShouldSkipWindow(window))
122 continue;
[email protected]5365e52c2013-07-31 23:07:17123
124 ScopedJavaLocalRef<jobject> last_pushed_window;
125 last_pushed_window.Reset(
126 Java_ForeignSessionHelper_pushWindow(
[email protected]9744a15e2013-09-23 20:59:08127 env, j_session.obj(),
128 window.timestamp.ToJavaTime(),
129 window.window_id.id()));
[email protected]5365e52c2013-07-31 23:07:17130
131 CopyTabsToJava(env, window, last_pushed_window);
132 }
133}
134
135} // namespace
136
137static jint Init(JNIEnv* env, jclass clazz, jobject profile) {
138 ForeignSessionHelper* foreign_session_helper = new ForeignSessionHelper(
139 ProfileAndroid::FromProfileAndroid(profile));
140 return reinterpret_cast<jint>(foreign_session_helper);
141}
142
143ForeignSessionHelper::ForeignSessionHelper(Profile* profile)
144 : profile_(profile) {
145 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
146 GetForProfile(profile);
[email protected]5365e52c2013-07-31 23:07:17147 registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
148 content::Source<ProfileSyncService>(service));
149 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
150 content::Source<Profile>(profile));
151 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
152 content::Source<Profile>(profile));
153}
154
155ForeignSessionHelper::~ForeignSessionHelper() {
156}
157
158void ForeignSessionHelper::Destroy(JNIEnv* env, jobject obj) {
159 delete this;
160}
161
162jboolean ForeignSessionHelper::IsTabSyncEnabled(JNIEnv* env, jobject obj) {
163 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
164 GetForProfile(profile_);
165 return service && service->GetActiveDataTypes().Has(syncer::PROXY_TABS);
166}
167
168void ForeignSessionHelper::SetOnForeignSessionCallback(JNIEnv* env,
169 jobject obj,
170 jobject callback) {
171 callback_.Reset(env, callback);
172}
173
174void ForeignSessionHelper::Observe(
175 int type, const content::NotificationSource& source,
176 const content::NotificationDetails& details) {
177 if (callback_.is_null())
178 return;
179
180 JNIEnv* env = AttachCurrentThread();
181
182 switch (type) {
183 case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED:
184 // Tab sync is disabled, so clean up data about collapsed sessions.
185 profile_->GetPrefs()->ClearPref(
186 prefs::kNtpCollapsedForeignSessions);
187 // Purposeful fall through.
188 case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
189 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
190 Java_ForeignSessionCallback_onUpdated(env, callback_.obj());
191 break;
192 default:
193 NOTREACHED();
194 }
195}
196
197jboolean ForeignSessionHelper::GetForeignSessions(JNIEnv* env,
198 jobject obj,
199 jobject result) {
200 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
201 if (!associator)
202 return false;
203
204 std::vector<const browser_sync::SyncedSession*> sessions;
205 if (!associator->GetAllForeignSessions(&sessions))
206 return false;
207
208 // Use a pref to keep track of sessions that were collapsed by the user.
209 // To prevent the pref from accumulating stale sessions, clear it each time
210 // and only add back sessions that are still current.
211 DictionaryPrefUpdate pref_update(profile_->GetPrefs(),
212 prefs::kNtpCollapsedForeignSessions);
213 DictionaryValue* pref_collapsed_sessions = pref_update.Get();
214 scoped_ptr<DictionaryValue> collapsed_sessions(
215 pref_collapsed_sessions->DeepCopy());
216 pref_collapsed_sessions->Clear();
217
218 ScopedJavaLocalRef<jobject> last_pushed_session;
219 ScopedJavaLocalRef<jobject> last_pushed_window;
220
221 // Note: we don't own the SyncedSessions themselves.
222 for (size_t i = 0; i < sessions.size(); ++i) {
[email protected]9744a15e2013-09-23 20:59:08223 const browser_sync::SyncedSession &session = *(sessions[i]);
224 if (ShouldSkipSession(session))
225 continue;
[email protected]5365e52c2013-07-31 23:07:17226
[email protected]9744a15e2013-09-23 20:59:08227 const bool is_collapsed = collapsed_sessions->HasKey(session.session_tag);
[email protected]5365e52c2013-07-31 23:07:17228
229 if (is_collapsed)
[email protected]9744a15e2013-09-23 20:59:08230 pref_collapsed_sessions->SetBoolean(session.session_tag, true);
[email protected]5365e52c2013-07-31 23:07:17231
232 last_pushed_session.Reset(
233 Java_ForeignSessionHelper_pushSession(
234 env,
235 result,
[email protected]9744a15e2013-09-23 20:59:08236 ConvertUTF8ToJavaString(env, session.session_tag).Release(),
237 ConvertUTF8ToJavaString(env, session.session_name).Release(),
238 session.device_type,
239 session.modified_time.ToJavaTime()));
[email protected]5365e52c2013-07-31 23:07:17240
241 CopyWindowsToJava(env, session, last_pushed_session);
242 }
243
244 return true;
245}
246
247jboolean ForeignSessionHelper::OpenForeignSessionTab(JNIEnv* env,
248 jobject obj,
249 jstring session_tag,
250 jint tab_id) {
251 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
252 if (!associator) {
253 LOG(ERROR) << "Null SessionModelAssociator returned.";
254 return false;
255 }
256
257 const SessionTab* tab;
258
259 if (!associator->GetForeignTab(ConvertJavaStringToUTF8(env, session_tag),
260 tab_id, &tab)) {
261 LOG(ERROR) << "Failed to load foreign tab.";
262 return false;
263 }
264
265 if (tab->navigations.empty()) {
266 LOG(ERROR) << "Foreign tab no longer has valid navigations.";
267 return false;
268 }
269
270 TabModel* tab_model = TabModelList::GetTabModelWithProfile(profile_);
271 DCHECK(tab_model);
272 if (!tab_model)
273 return false;
274
275 std::vector<content::NavigationEntry*> entries =
276 sessions::SerializedNavigationEntry::ToNavigationEntries(
277 tab->navigations, profile_);
278 content::WebContents* new_web_contents = content::WebContents::Create(
279 content::WebContents::CreateParams(profile_));
280 int selected_index = tab->normalized_navigation_index();
281 new_web_contents->GetController().Restore(
282 selected_index,
283 content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
284 &entries);
285 tab_model->CreateTab(new_web_contents);
286
287 return true;
288}
289
290void ForeignSessionHelper::SetForeignSessionCollapsed(JNIEnv* env, jobject obj,
291 jstring session_tag,
292 jboolean is_collapsed) {
293 // Store session tags for collapsed sessions in a preference so that the
294 // collapsed state persists.
295 PrefService* prefs = profile_->GetPrefs();
296 DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
297 if (is_collapsed)
298 update.Get()->SetBoolean(ConvertJavaStringToUTF8(env, session_tag), true);
299 else
300 update.Get()->Remove(ConvertJavaStringToUTF8(env, session_tag), NULL);
301}
302
[email protected]9744a15e2013-09-23 20:59:08303jboolean ForeignSessionHelper::GetForeignSessionCollapsed(JNIEnv* env,
304 jobject obj,
305 jstring session_tag) {
306 const DictionaryValue* dict = profile_->GetPrefs()->GetDictionary(
307 prefs::kNtpCollapsedForeignSessions);
308 return dict && dict->HasKey(ConvertJavaStringToUTF8(env, session_tag));
309}
310
[email protected]5365e52c2013-07-31 23:07:17311void ForeignSessionHelper::DeleteForeignSession(JNIEnv* env, jobject obj,
312 jstring session_tag) {
313 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
314 if (associator)
315 associator->DeleteForeignSession(ConvertJavaStringToUTF8(env, session_tag));
316}
317
318// static
319bool ForeignSessionHelper::RegisterForeignSessionHelper(JNIEnv* env) {
320 return RegisterNativesImpl(env);
321}