blob: fa7ff24901b014aed880ac2357b326486a553b52 [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"
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
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
55 return false;
56}
57
58bool ShouldSkipWindow(const SessionWindow& window) {
59 for (std::vector<SessionTab*>::const_iterator tab_it = window.tabs.begin();
60 tab_it != window.tabs.end(); ++tab_it) {
61 const SessionTab &tab = **tab_it;
62 if (!ShouldSkipTab(tab))
63 return false;
64 }
65 return true;
66}
67
68bool ShouldSkipSession(const browser_sync::SyncedSession& session) {
69 for (SyncedSession::SyncedWindowMap::const_iterator it =
70 session.windows.begin(); it != session.windows.end(); ++it) {
71 const SessionWindow &window = *(it->second);
72 if (!ShouldSkipWindow(window))
73 return false;
74 }
75 return true;
76}
77
[email protected]5365e52c2013-07-31 23:07:1778void CopyTabsToJava(
79 JNIEnv* env,
[email protected]9744a15e2013-09-23 20:59:0880 const SessionWindow& window,
[email protected]5365e52c2013-07-31 23:07:1781 ScopedJavaLocalRef<jobject>& j_window) {
[email protected]9744a15e2013-09-23 20:59:0882 for (std::vector<SessionTab*>::const_iterator tab_it = window.tabs.begin();
83 tab_it != window.tabs.end(); ++tab_it) {
[email protected]5365e52c2013-07-31 23:07:1784 const SessionTab &tab = **tab_it;
85
[email protected]9744a15e2013-09-23 20:59:0886 if (ShouldSkipTab(tab))
[email protected]5365e52c2013-07-31 23:07:1787 continue;
88
[email protected]9744a15e2013-09-23 20:59:0889 int selected_index = tab.current_navigation_index;
90 DCHECK(selected_index >= 0);
91 DCHECK(selected_index < static_cast<int>(tab.navigations.size()));
92
[email protected]5365e52c2013-07-31 23:07:1793 const ::sessions::SerializedNavigationEntry& current_navigation =
[email protected]9744a15e2013-09-23 20:59:0894 tab.navigations.at(selected_index);
[email protected]5365e52c2013-07-31 23:07:1795
96 GURL tab_url = current_navigation.virtual_url();
[email protected]5365e52c2013-07-31 23:07:1797
98 Java_ForeignSessionHelper_pushTab(
99 env, j_window.obj(),
100 ConvertUTF8ToJavaString(env, tab_url.spec()).Release(),
101 ConvertUTF16ToJavaString(env, current_navigation.title()).Release(),
[email protected]9744a15e2013-09-23 20:59:08102 tab.timestamp.ToJavaTime(),
103 tab.tab_id.id());
[email protected]5365e52c2013-07-31 23:07:17104 }
105}
106
107void CopyWindowsToJava(
108 JNIEnv* env,
[email protected]9744a15e2013-09-23 20:59:08109 const SyncedSession& session,
[email protected]5365e52c2013-07-31 23:07:17110 ScopedJavaLocalRef<jobject>& j_session) {
111 for (SyncedSession::SyncedWindowMap::const_iterator it =
[email protected]9744a15e2013-09-23 20:59:08112 session.windows.begin(); it != session.windows.end(); ++it) {
113 const SessionWindow &window = *(it->second);
114
115 if (ShouldSkipWindow(window))
116 continue;
[email protected]5365e52c2013-07-31 23:07:17117
118 ScopedJavaLocalRef<jobject> last_pushed_window;
119 last_pushed_window.Reset(
120 Java_ForeignSessionHelper_pushWindow(
[email protected]9744a15e2013-09-23 20:59:08121 env, j_session.obj(),
122 window.timestamp.ToJavaTime(),
123 window.window_id.id()));
[email protected]5365e52c2013-07-31 23:07:17124
125 CopyTabsToJava(env, window, last_pushed_window);
126 }
127}
128
129} // namespace
130
131static jint Init(JNIEnv* env, jclass clazz, jobject profile) {
132 ForeignSessionHelper* foreign_session_helper = new ForeignSessionHelper(
133 ProfileAndroid::FromProfileAndroid(profile));
134 return reinterpret_cast<jint>(foreign_session_helper);
135}
136
137ForeignSessionHelper::ForeignSessionHelper(Profile* profile)
138 : profile_(profile) {
139 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
140 GetForProfile(profile);
[email protected]5365e52c2013-07-31 23:07:17141 registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
142 content::Source<ProfileSyncService>(service));
143 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
144 content::Source<Profile>(profile));
145 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
146 content::Source<Profile>(profile));
147}
148
149ForeignSessionHelper::~ForeignSessionHelper() {
150}
151
152void ForeignSessionHelper::Destroy(JNIEnv* env, jobject obj) {
153 delete this;
154}
155
156jboolean ForeignSessionHelper::IsTabSyncEnabled(JNIEnv* env, jobject obj) {
157 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
158 GetForProfile(profile_);
159 return service && service->GetActiveDataTypes().Has(syncer::PROXY_TABS);
160}
161
162void ForeignSessionHelper::SetOnForeignSessionCallback(JNIEnv* env,
163 jobject obj,
164 jobject callback) {
165 callback_.Reset(env, callback);
166}
167
168void ForeignSessionHelper::Observe(
169 int type, const content::NotificationSource& source,
170 const content::NotificationDetails& details) {
171 if (callback_.is_null())
172 return;
173
174 JNIEnv* env = AttachCurrentThread();
175
176 switch (type) {
177 case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED:
178 // Tab sync is disabled, so clean up data about collapsed sessions.
179 profile_->GetPrefs()->ClearPref(
180 prefs::kNtpCollapsedForeignSessions);
181 // Purposeful fall through.
182 case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
183 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
184 Java_ForeignSessionCallback_onUpdated(env, callback_.obj());
185 break;
186 default:
187 NOTREACHED();
188 }
189}
190
191jboolean ForeignSessionHelper::GetForeignSessions(JNIEnv* env,
192 jobject obj,
193 jobject result) {
194 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
195 if (!associator)
196 return false;
197
198 std::vector<const browser_sync::SyncedSession*> sessions;
199 if (!associator->GetAllForeignSessions(&sessions))
200 return false;
201
202 // Use a pref to keep track of sessions that were collapsed by the user.
203 // To prevent the pref from accumulating stale sessions, clear it each time
204 // and only add back sessions that are still current.
205 DictionaryPrefUpdate pref_update(profile_->GetPrefs(),
206 prefs::kNtpCollapsedForeignSessions);
207 DictionaryValue* pref_collapsed_sessions = pref_update.Get();
208 scoped_ptr<DictionaryValue> collapsed_sessions(
209 pref_collapsed_sessions->DeepCopy());
210 pref_collapsed_sessions->Clear();
211
212 ScopedJavaLocalRef<jobject> last_pushed_session;
213 ScopedJavaLocalRef<jobject> last_pushed_window;
214
215 // Note: we don't own the SyncedSessions themselves.
216 for (size_t i = 0; i < sessions.size(); ++i) {
[email protected]9744a15e2013-09-23 20:59:08217 const browser_sync::SyncedSession &session = *(sessions[i]);
218 if (ShouldSkipSession(session))
219 continue;
[email protected]5365e52c2013-07-31 23:07:17220
[email protected]9744a15e2013-09-23 20:59:08221 const bool is_collapsed = collapsed_sessions->HasKey(session.session_tag);
[email protected]5365e52c2013-07-31 23:07:17222
223 if (is_collapsed)
[email protected]9744a15e2013-09-23 20:59:08224 pref_collapsed_sessions->SetBoolean(session.session_tag, true);
[email protected]5365e52c2013-07-31 23:07:17225
226 last_pushed_session.Reset(
227 Java_ForeignSessionHelper_pushSession(
228 env,
229 result,
[email protected]9744a15e2013-09-23 20:59:08230 ConvertUTF8ToJavaString(env, session.session_tag).Release(),
231 ConvertUTF8ToJavaString(env, session.session_name).Release(),
232 session.device_type,
233 session.modified_time.ToJavaTime()));
[email protected]5365e52c2013-07-31 23:07:17234
235 CopyWindowsToJava(env, session, last_pushed_session);
236 }
237
238 return true;
239}
240
241jboolean ForeignSessionHelper::OpenForeignSessionTab(JNIEnv* env,
242 jobject obj,
243 jstring session_tag,
244 jint tab_id) {
245 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
246 if (!associator) {
247 LOG(ERROR) << "Null SessionModelAssociator returned.";
248 return false;
249 }
250
251 const SessionTab* tab;
252
253 if (!associator->GetForeignTab(ConvertJavaStringToUTF8(env, session_tag),
254 tab_id, &tab)) {
255 LOG(ERROR) << "Failed to load foreign tab.";
256 return false;
257 }
258
259 if (tab->navigations.empty()) {
260 LOG(ERROR) << "Foreign tab no longer has valid navigations.";
261 return false;
262 }
263
264 TabModel* tab_model = TabModelList::GetTabModelWithProfile(profile_);
265 DCHECK(tab_model);
266 if (!tab_model)
267 return false;
268
269 std::vector<content::NavigationEntry*> entries =
270 sessions::SerializedNavigationEntry::ToNavigationEntries(
271 tab->navigations, profile_);
272 content::WebContents* new_web_contents = content::WebContents::Create(
273 content::WebContents::CreateParams(profile_));
274 int selected_index = tab->normalized_navigation_index();
275 new_web_contents->GetController().Restore(
276 selected_index,
277 content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
278 &entries);
279 tab_model->CreateTab(new_web_contents);
280
281 return true;
282}
283
284void ForeignSessionHelper::SetForeignSessionCollapsed(JNIEnv* env, jobject obj,
285 jstring session_tag,
286 jboolean is_collapsed) {
287 // Store session tags for collapsed sessions in a preference so that the
288 // collapsed state persists.
289 PrefService* prefs = profile_->GetPrefs();
290 DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
291 if (is_collapsed)
292 update.Get()->SetBoolean(ConvertJavaStringToUTF8(env, session_tag), true);
293 else
294 update.Get()->Remove(ConvertJavaStringToUTF8(env, session_tag), NULL);
295}
296
[email protected]9744a15e2013-09-23 20:59:08297jboolean ForeignSessionHelper::GetForeignSessionCollapsed(JNIEnv* env,
298 jobject obj,
299 jstring session_tag) {
300 const DictionaryValue* dict = profile_->GetPrefs()->GetDictionary(
301 prefs::kNtpCollapsedForeignSessions);
302 return dict && dict->HasKey(ConvertJavaStringToUTF8(env, session_tag));
303}
304
[email protected]5365e52c2013-07-31 23:07:17305void ForeignSessionHelper::DeleteForeignSession(JNIEnv* env, jobject obj,
306 jstring session_tag) {
307 SessionModelAssociator* associator = GetSessionModelAssociator(profile_);
308 if (associator)
309 associator->DeleteForeignSession(ConvertJavaStringToUTF8(env, session_tag));
310}
311
312// static
313bool ForeignSessionHelper::RegisterForeignSessionHelper(JNIEnv* env) {
314 return RegisterNativesImpl(env);
315}