- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / android / foreign_session_helper.cc
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/scoped_user_pref_update.h"
11 #include "chrome/browser/chrome_notification_types.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 bool 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     const ::sessions::SerializedNavigationEntry& current_navigation =
56         tab.navigations.at(selected_index);
57
58     if (current_navigation.virtual_url().is_empty())
59       return true;
60
61     return false;
62 }
63
64 bool 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
74 bool 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
84 void CopyTabsToJava(
85     JNIEnv* env,
86     const SessionWindow& window,
87     ScopedJavaLocalRef<jobject>& j_window) {
88   for (std::vector<SessionTab*>::const_iterator tab_it = window.tabs.begin();
89       tab_it != window.tabs.end(); ++tab_it) {
90     const SessionTab &tab = **tab_it;
91
92     if (ShouldSkipTab(tab))
93       continue;
94
95     int selected_index = tab.current_navigation_index;
96     DCHECK(selected_index >= 0);
97     DCHECK(selected_index < static_cast<int>(tab.navigations.size()));
98
99     const ::sessions::SerializedNavigationEntry& current_navigation =
100         tab.navigations.at(selected_index);
101
102     GURL tab_url = current_navigation.virtual_url();
103
104     Java_ForeignSessionHelper_pushTab(
105         env, j_window.obj(),
106         ConvertUTF8ToJavaString(env, tab_url.spec()).Release(),
107         ConvertUTF16ToJavaString(env, current_navigation.title()).Release(),
108         tab.timestamp.ToJavaTime(),
109         tab.tab_id.id());
110   }
111 }
112
113 void CopyWindowsToJava(
114     JNIEnv* env,
115     const SyncedSession& session,
116     ScopedJavaLocalRef<jobject>& j_session) {
117   for (SyncedSession::SyncedWindowMap::const_iterator it =
118       session.windows.begin(); it != session.windows.end(); ++it) {
119     const SessionWindow &window = *(it->second);
120
121     if (ShouldSkipWindow(window))
122       continue;
123
124     ScopedJavaLocalRef<jobject> last_pushed_window;
125     last_pushed_window.Reset(
126         Java_ForeignSessionHelper_pushWindow(
127             env, j_session.obj(),
128             window.timestamp.ToJavaTime(),
129             window.window_id.id()));
130
131     CopyTabsToJava(env, window, last_pushed_window);
132   }
133 }
134
135 }  // namespace
136
137 static 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
143 ForeignSessionHelper::ForeignSessionHelper(Profile* profile)
144     : profile_(profile) {
145   ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
146       GetForProfile(profile);
147   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
155 ForeignSessionHelper::~ForeignSessionHelper() {
156 }
157
158 void ForeignSessionHelper::Destroy(JNIEnv* env, jobject obj) {
159   delete this;
160 }
161
162 jboolean 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
168 void ForeignSessionHelper::SetOnForeignSessionCallback(JNIEnv* env,
169                                                        jobject obj,
170                                                        jobject callback) {
171   callback_.Reset(env, callback);
172 }
173
174 void 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
197 jboolean 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) {
223     const browser_sync::SyncedSession &session = *(sessions[i]);
224     if (ShouldSkipSession(session))
225       continue;
226
227     const bool is_collapsed = collapsed_sessions->HasKey(session.session_tag);
228
229     if (is_collapsed)
230       pref_collapsed_sessions->SetBoolean(session.session_tag, true);
231
232     last_pushed_session.Reset(
233         Java_ForeignSessionHelper_pushSession(
234             env,
235             result,
236             ConvertUTF8ToJavaString(env, session.session_tag).Release(),
237             ConvertUTF8ToJavaString(env, session.session_name).Release(),
238             session.device_type,
239             session.modified_time.ToJavaTime()));
240
241     CopyWindowsToJava(env, session, last_pushed_session);
242   }
243
244   return true;
245 }
246
247 jboolean 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
290 void 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
303 jboolean 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
311 void 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
319 bool ForeignSessionHelper::RegisterForeignSessionHelper(JNIEnv* env) {
320   return RegisterNativesImpl(env);
321 }