Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / history / android / android_provider_backend.cc
1 // Copyright (c) 2012 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/history/android/android_provider_backend.h"
6
7 #include "base/i18n/case_conversion.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/history/android/android_time.h"
10 #include "chrome/browser/history/android/android_urls_sql_handler.h"
11 #include "chrome/browser/history/android/bookmark_model_sql_handler.h"
12 #include "chrome/browser/history/android/favicon_sql_handler.h"
13 #include "chrome/browser/history/android/urls_sql_handler.h"
14 #include "chrome/browser/history/android/visit_sql_handler.h"
15 #include "chrome/browser/history/history_backend.h"
16 #include "chrome/browser/history/history_database.h"
17 #include "chrome/browser/history/thumbnail_database.h"
18 #include "components/history/core/browser/history_client.h"
19 #include "components/history/core/browser/keyword_search_term.h"
20 #include "sql/connection.h"
21
22
23 namespace history {
24
25
26 // Helpers --------------------------------------------------------------------
27
28 namespace {
29
30 const char kVirtualHistoryAndBookmarkTable[] =
31     "SELECT android_urls.id AS _id, "
32         "android_cache_db.bookmark_cache.created_time AS created, "
33         "urls.title AS title, android_urls.raw_url AS url, "
34         "urls.visit_count AS visits, "
35         "android_cache_db.bookmark_cache.last_visit_time AS date, "
36         "android_cache_db.bookmark_cache.bookmark AS bookmark, "
37         "android_cache_db.bookmark_cache.favicon_id AS favicon, "
38         "urls.id AS url_id, urls.url AS urls_url, "
39     // TODO (michaelbai) : Remove folder column once we remove it from Android
40     // framework.
41     // Android framework assumes 'folder' column exist in the table, the row is
42     // the bookmark once folder is 0, though it is not part of public API, it
43     // has to be added and set as 0 when the row is bookmark.
44         "(CASE WHEN android_cache_db.bookmark_cache.bookmark IS 0 "
45         "THEN 1 ELSE 0 END) as folder "
46     "FROM (android_urls JOIN urls on (android_urls.url_id = urls.id) "
47         "LEFT JOIN android_cache_db.bookmark_cache "
48         "on (android_urls.url_id = android_cache_db.bookmark_cache.url_id))";
49
50 const char kURLUpdateClause[] =
51     "SELECT urls.id, urls.last_visit_time, created_time, urls.url "
52     "FROM urls LEFT JOIN "
53         "(SELECT url as visit_url, min(visit_time) as created_time"
54         " FROM visits GROUP BY url) ON (visit_url = urls.id) ";
55
56 const char kSearchTermUpdateClause[] =
57     "SELECT keyword_search_terms.term, max(urls.last_visit_time) "
58     "FROM keyword_search_terms JOIN urls ON "
59         "(keyword_search_terms.url_id = urls.id) "
60     "GROUP BY keyword_search_terms.term";
61
62 void BindStatement(const std::vector<base::string16>& selection_args,
63                    sql::Statement* statement,
64                    int* col_index) {
65   for (std::vector<base::string16>::const_iterator i = selection_args.begin();
66        i != selection_args.end(); ++i) {
67     // Using the same method as Android, binding all argument as String.
68     statement->BindString16(*col_index, *i);
69     ++(*col_index);
70   }
71 }
72
73 bool IsHistoryAndBookmarkRowValid(const HistoryAndBookmarkRow& row) {
74   // The caller should make sure both/neither Raw URL and/nor URL should be set.
75   DCHECK(row.is_value_set_explicitly(HistoryAndBookmarkRow::RAW_URL) ==
76          row.is_value_set_explicitly(HistoryAndBookmarkRow::URL));
77
78   // The following cases are checked:
79   // a. Last visit time or created time is large than now.
80   // b. Last visit time is less than created time.
81   // c. Created time and last visit time is different, but visit count is less
82   //    than 2.
83   // d. The difference between created and last visit time is less than
84   //    visit_count.
85   // e. Visit count is 0 or 1 and both last visit time and created time are set
86   //    explicitly, but the time is different or created time is not UnixEpoch.
87   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
88       row.last_visit_time() > base::Time::Now())
89     return false;
90
91   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
92       row.created() > base::Time::Now())
93     return false;
94
95   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
96       row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED)) {
97     if (row.created() > row.last_visit_time())
98       return false;
99
100     if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) &&
101         row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
102         row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
103       if (row.created() != row.last_visit_time() &&
104           row.created() != base::Time::UnixEpoch() &&
105           (row.visit_count() == 0 || row.visit_count() == 1))
106         return false;
107
108       if (row.last_visit_time().ToInternalValue() -
109           row.created().ToInternalValue() < row.visit_count())
110         return false;
111     }
112   }
113   return true;
114 }
115
116 }  // namespace
117
118 // AndroidProviderBackend::ScopedTransaction ----------------------------------
119
120 AndroidProviderBackend::ScopedTransaction::ScopedTransaction(
121     HistoryDatabase* history_db,
122     ThumbnailDatabase* thumbnail_db)
123     : history_db_(history_db),
124       thumbnail_db_(thumbnail_db),
125       committed_(false),
126       history_transaction_nesting_(history_db_->transaction_nesting()),
127       thumbnail_transaction_nesting_(
128           thumbnail_db_ ? thumbnail_db_->transaction_nesting() : 0) {
129   // Commit all existing transactions since the AndroidProviderBackend's
130   // transaction is very like to be rolled back when compared with the others.
131   // The existing transactions have been scheduled to commit by
132   // ScheduleCommit in HistoryBackend and the same number of transaction
133   // will be created after this scoped transaction ends, there should have no
134   // issue to directly commit all transactions here.
135   int count = history_transaction_nesting_;
136   while (count--)
137     history_db_->CommitTransaction();
138   history_db_->BeginTransaction();
139
140   if (thumbnail_db_) {
141     count = thumbnail_transaction_nesting_;
142     while (count--)
143       thumbnail_db_->CommitTransaction();
144     thumbnail_db_->BeginTransaction();
145   }
146 }
147
148 AndroidProviderBackend::ScopedTransaction::~ScopedTransaction() {
149   if (!committed_) {
150     history_db_->RollbackTransaction();
151     if (thumbnail_db_)
152       thumbnail_db_->RollbackTransaction();
153   }
154   // There is no transaction now.
155   DCHECK_EQ(0, history_db_->transaction_nesting());
156   DCHECK(!thumbnail_db_ || 0 == thumbnail_db_->transaction_nesting());
157
158   int count = history_transaction_nesting_;
159   while (count--)
160     history_db_->BeginTransaction();
161
162   if (thumbnail_db_) {
163     count = thumbnail_transaction_nesting_;
164     while (count--)
165       thumbnail_db_->BeginTransaction();
166   }
167 }
168
169 void AndroidProviderBackend::ScopedTransaction::Commit() {
170   DCHECK(!committed_);
171   history_db_->CommitTransaction();
172   if (thumbnail_db_)
173     thumbnail_db_->CommitTransaction();
174   committed_ = true;
175 }
176
177
178 // AndroidProviderBackend -----------------------------------------------------
179
180 AndroidProviderBackend::AndroidProviderBackend(
181     const base::FilePath& db_name,
182     HistoryDatabase* history_db,
183     ThumbnailDatabase* thumbnail_db,
184     HistoryClient* history_client,
185     HistoryBackend::Delegate* delegate)
186     : android_cache_db_filename_(db_name),
187       db_(&history_db->GetDB()),
188       history_db_(history_db),
189       thumbnail_db_(thumbnail_db),
190       history_client_(history_client),
191       initialized_(false),
192       delegate_(delegate) {
193   DCHECK(delegate_);
194 }
195
196 AndroidProviderBackend::~AndroidProviderBackend() {
197 }
198
199 AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarks(
200     const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
201     const std::string& selection,
202     const std::vector<base::string16>& selection_args,
203     const std::string& sort_order) {
204   if (projections.empty())
205     return NULL;
206
207   ScopedTransaction transaction(history_db_, thumbnail_db_);
208
209   if (!EnsureInitializedAndUpdated())
210     return NULL;
211
212   transaction.Commit();
213
214   return QueryHistoryAndBookmarksInternal(projections, selection,
215                                           selection_args, sort_order);
216 }
217
218 bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
219     const HistoryAndBookmarkRow& row,
220     const std::string& selection,
221     const std::vector<base::string16>& selection_args,
222     int* updated_count) {
223   HistoryNotifications notifications;
224
225   ScopedTransaction transaction(history_db_, thumbnail_db_);
226
227   if (!UpdateHistoryAndBookmarks(row, selection, selection_args, updated_count,
228                                  &notifications))
229     return false;
230
231   transaction.Commit();
232   BroadcastNotifications(&notifications);
233   return true;
234 }
235
236 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
237     const HistoryAndBookmarkRow& values) {
238   HistoryNotifications notifications;
239
240   ScopedTransaction transaction(history_db_, thumbnail_db_);
241
242   AndroidURLID id = InsertHistoryAndBookmark(values, true, &notifications);
243   if (!id)
244     return 0;
245
246   transaction.Commit();
247   BroadcastNotifications(&notifications);
248   return id;
249 }
250
251 bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
252     const std::string& selection,
253     const std::vector<base::string16>& selection_args,
254     int* deleted_count) {
255   HistoryNotifications notifications;
256
257   ScopedTransaction transaction(history_db_, thumbnail_db_);
258
259   if (!DeleteHistoryAndBookmarks(selection, selection_args, deleted_count,
260                                  &notifications))
261     return false;
262
263   transaction.Commit();
264   BroadcastNotifications(&notifications);
265   return true;
266 }
267
268 bool AndroidProviderBackend::DeleteHistory(
269     const std::string& selection,
270     const std::vector<base::string16>& selection_args,
271     int* deleted_count) {
272   HistoryNotifications notifications;
273
274   ScopedTransaction transaction(history_db_, thumbnail_db_);
275
276   if (!DeleteHistory(selection, selection_args, deleted_count, &notifications))
277     return false;
278
279   transaction.Commit();
280   BroadcastNotifications(&notifications);
281   return true;
282 }
283
284 bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
285     const HistoryAndBookmarkRow& row,
286     const std::string& selection,
287     const std::vector<base::string16>& selection_args,
288     int* updated_count,
289     HistoryNotifications* notifications) {
290   if (!IsHistoryAndBookmarkRowValid(row))
291     return false;
292
293   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::ID))
294     return false;
295
296   if (!EnsureInitializedAndUpdated())
297     return false;
298
299   TableIDRows ids_set;
300   if (!GetSelectedURLs(selection, selection_args, &ids_set))
301     return false;
302
303   if (ids_set.empty()) {
304     *updated_count = 0;
305     return true;
306   }
307
308   // URL can not be updated, we simulate the update.
309   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::URL)) {
310     // Only one row's URL can be updated at a time as we can't have multiple
311     // rows have the same URL.
312     if (ids_set.size() != 1)
313       return false;
314
315     HistoryAndBookmarkRow new_row = row;
316     if (!SimulateUpdateURL(new_row, ids_set, notifications))
317       return false;
318     *updated_count = 1;
319     return true;
320   }
321
322   for (std::vector<SQLHandler*>::iterator i =
323        sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
324     if ((*i)->HasColumnIn(row)) {
325       if (!(*i)->Update(row, ids_set))
326         return false;
327     }
328   }
329   *updated_count = ids_set.size();
330
331   scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
332   std::set<GURL> favicon;
333
334   for (const auto& id : ids_set) {
335     if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE) ||
336         row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) ||
337         row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
338       URLRow url_row;
339       if (!history_db_->GetURLRow(id.url_id, &url_row))
340         return false;
341       modified->changed_urls.push_back(url_row);
342     }
343     if (thumbnail_db_ &&
344         row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON))
345       favicon.insert(id.url);
346   }
347
348   if (!modified->changed_urls.empty()) {
349     scoped_ptr<HistoryDetails> details = modified.Pass();
350     notifications->push_back(
351         base::Bind(&HistoryBackend::Delegate::BroadcastNotifications,
352                    base::Unretained(delegate_),
353                    chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
354                    base::Passed(&details)));
355   }
356
357   if (!favicon.empty()) {
358     notifications->push_back(
359         base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
360                    base::Unretained(delegate_),
361                    favicon));
362   }
363
364   return true;
365 }
366
367 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
368     const HistoryAndBookmarkRow& values,
369     bool ensure_initialized_and_updated,
370     HistoryNotifications* notifications) {
371   if (!IsHistoryAndBookmarkRowValid(values))
372     return false;
373
374   if (ensure_initialized_and_updated && !EnsureInitializedAndUpdated())
375     return 0;
376
377   DCHECK(values.is_value_set_explicitly(HistoryAndBookmarkRow::URL));
378   // Make a copy of values as we need change it during insert.
379   HistoryAndBookmarkRow row = values;
380   for (std::vector<SQLHandler*>::iterator i =
381        sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
382     if (!(*i)->Insert(&row))
383       return 0;
384   }
385
386   URLRow url_row;
387   if (!history_db_->GetURLRow(row.url_id(), &url_row))
388     return false;
389
390   scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
391   modified->changed_urls.push_back(url_row);
392
393   std::set<GURL> favicon;
394   // No favicon should be changed if the thumbnail_db_ is not available.
395   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON) &&
396       row.favicon_valid() && thumbnail_db_) {
397     favicon.insert(url_row.url());
398   }
399
400   scoped_ptr<HistoryDetails> details = modified.Pass();
401   notifications->push_back(
402       base::Bind(&HistoryBackend::Delegate::BroadcastNotifications,
403                  base::Unretained(delegate_),
404                  chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
405                  base::Passed(&details)));
406
407   if (!favicon.empty()) {
408     notifications->push_back(
409         base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
410                    base::Unretained(delegate_),
411                    favicon));
412   }
413
414   return row.id();
415 }
416
417 bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
418     const std::string& selection,
419     const std::vector<base::string16>& selection_args,
420     int * deleted_count,
421     HistoryNotifications* notifications) {
422   if (!EnsureInitializedAndUpdated())
423     return false;
424
425   TableIDRows ids_set;
426   if (!GetSelectedURLs(selection, selection_args, &ids_set))
427     return false;
428
429   if (ids_set.empty()) {
430     *deleted_count = 0;
431     return true;
432   }
433
434   if (!DeleteHistoryInternal(ids_set, true, notifications))
435     return false;
436
437   *deleted_count = ids_set.size();
438
439   return true;
440 }
441
442 bool AndroidProviderBackend::DeleteHistory(
443     const std::string& selection,
444     const std::vector<base::string16>& selection_args,
445     int* deleted_count,
446     HistoryNotifications* notifications) {
447   if (!EnsureInitializedAndUpdated())
448     return false;
449
450   TableIDRows ids_set;
451   if (!GetSelectedURLs(selection, selection_args, &ids_set))
452     return false;
453
454   if (ids_set.empty()) {
455     *deleted_count = 0;
456     return true;
457   }
458
459   *deleted_count = ids_set.size();
460
461   // Get the bookmarked rows.
462   std::vector<HistoryAndBookmarkRow> bookmarks;
463   for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
464        ++i) {
465     if (i->bookmarked) {
466       AndroidURLRow android_url_row;
467       if (!history_db_->GetAndroidURLRow(i->url_id, &android_url_row))
468         return false;
469       HistoryAndBookmarkRow row;
470       row.set_raw_url(android_url_row.raw_url);
471       row.set_url(i->url);
472       // Set the visit time to the UnixEpoch since that's when the Android
473       // system time starts. The Android have a CTS testcase for this.
474       row.set_last_visit_time(base::Time::UnixEpoch());
475       row.set_visit_count(0);
476       // We don't want to change the bookmark model, so set_is_bookmark() is
477       // not called.
478       bookmarks.push_back(row);
479     }
480   }
481
482   // Don't delete the bookmark from bookmark model when deleting the history.
483   if (!DeleteHistoryInternal(ids_set, false, notifications))
484     return false;
485
486   for (std::vector<HistoryAndBookmarkRow>::const_iterator i = bookmarks.begin();
487        i != bookmarks.end(); ++i) {
488     // Don't update the tables, otherwise, the bookmarks will be added to
489     // database during UpdateBookmark(), then the insertion will fail.
490     // We can't rely on UpdateBookmark() to insert the bookmarks into history
491     // database as the raw_url will be lost.
492     if (!InsertHistoryAndBookmark(*i, false, notifications))
493       return false;
494   }
495   return true;
496 }
497
498 AndroidStatement* AndroidProviderBackend::QuerySearchTerms(
499     const std::vector<SearchRow::ColumnID>& projections,
500     const std::string& selection,
501     const std::vector<base::string16>& selection_args,
502     const std::string& sort_order) {
503   if (projections.empty())
504     return NULL;
505
506   if (!EnsureInitializedAndUpdated())
507     return NULL;
508
509   std::string sql;
510   sql.append("SELECT ");
511   AppendSearchResultColumn(projections, &sql);
512   sql.append(" FROM android_cache_db.search_terms ");
513
514   if (!selection.empty()) {
515     sql.append(" WHERE ");
516     sql.append(selection);
517   }
518
519   if (!sort_order.empty()) {
520     sql.append(" ORDER BY ");
521     sql.append(sort_order);
522   }
523
524   scoped_ptr<sql::Statement> statement(new sql::Statement(
525       db_->GetUniqueStatement(sql.c_str())));
526   int count = 0;
527   BindStatement(selection_args, statement.get(), &count);
528   if (!statement->is_valid()) {
529     LOG(ERROR) << db_->GetErrorMessage();
530     return NULL;
531   }
532   sql::Statement* result = statement.release();
533   return new AndroidStatement(result, -1);
534 }
535
536 bool AndroidProviderBackend::UpdateSearchTerms(
537     const SearchRow& row,
538     const std::string& selection,
539     const std::vector<base::string16>& selection_args,
540     int* update_count) {
541   if (!EnsureInitializedAndUpdated())
542     return false;
543
544   SearchTerms search_terms;
545   if (!GetSelectedSearchTerms(selection, selection_args, &search_terms))
546     return false;
547
548   // We can not update search term if multiple row selected.
549   if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM) &&
550       search_terms.size() > 1)
551     return false;
552
553   *update_count = search_terms.size();
554
555   if (search_terms.empty())
556     return true;
557
558   if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM)) {
559     SearchTermRow search_term_row;
560     SearchRow search_row = row;
561     if (!history_db_->GetSearchTerm(search_terms[0], &search_term_row))
562       return false;
563
564     search_term_row.term = search_row.search_term();
565     if (!search_row.is_value_set_explicitly(SearchRow::SEARCH_TIME))
566       search_row.set_search_time(search_term_row.last_visit_time);
567     else
568       search_term_row.last_visit_time = search_row.search_time();
569
570     // Delete the original search term.
571     if (!history_db_->DeleteKeywordSearchTerm(search_terms[0]))
572       return false;
573
574     // Add the new one.
575     if (!AddSearchTerm(search_row))
576       return false;
577
578     // Update the cache table so the id will not be changed.
579     if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
580       return false;
581
582      return true;
583   }
584
585   for (SearchTerms::const_iterator i = search_terms.begin();
586        i != search_terms.end(); ++i) {
587     SearchTermRow search_term_row;
588     if (!history_db_->GetSearchTerm(*i, &search_term_row))
589       return false;
590
591     // Check whether the given search time less than the existing one.
592     if (search_term_row.last_visit_time > row.search_time())
593       return false;
594
595     std::vector<KeywordSearchTermRow> search_term_rows;
596     if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) ||
597         search_term_rows.empty())
598       return false;
599
600     // Actually only search_time update. As there might multiple URLs
601     // asocciated with the keyword, Just update the first one's last_visit_time.
602     URLRow url_row;
603     if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row))
604       return false;
605
606     HistoryAndBookmarkRow bookmark_row;
607     bookmark_row.set_last_visit_time(row.search_time());
608     TableIDRow table_id_row;
609     table_id_row.url_id = url_row.id();
610     TableIDRows table_id_rows;
611     table_id_rows.push_back(table_id_row);
612     if (!urls_handler_->Update(bookmark_row, table_id_rows))
613       return false;
614
615     if (!visit_handler_->Update(bookmark_row, table_id_rows))
616       return false;
617   }
618   return true;
619 }
620
621 SearchTermID AndroidProviderBackend::InsertSearchTerm(
622     const SearchRow& values) {
623   if (!EnsureInitializedAndUpdated())
624     return 0;
625
626   if (!AddSearchTerm(values))
627     return 0;
628
629   SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL);
630   if (!id)
631     // Note the passed in Time() will be changed in UpdateSearchTermTable().
632     id = history_db_->AddSearchTerm(values.search_term(), base::Time());
633   return id;
634 }
635
636 bool AndroidProviderBackend::DeleteSearchTerms(
637     const std::string& selection,
638     const std::vector<base::string16>& selection_args,
639     int * deleted_count) {
640   if (!EnsureInitializedAndUpdated())
641     return false;
642
643   SearchTerms rows;
644   if (!GetSelectedSearchTerms(selection, selection_args, &rows))
645     return false;
646
647   *deleted_count = rows.size();
648   if (rows.empty())
649     return true;
650
651   for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i)
652     if (!history_db_->DeleteKeywordSearchTerm(*i))
653       return false;
654   // We don't delete the rows in search_terms table, as once the
655   // search_terms table is updated with keyword_search_terms, all
656   // keyword cache not found in the keyword_search_terms will be removed.
657   return true;
658 }
659
660 bool AndroidProviderBackend::EnsureInitializedAndUpdated() {
661   if (!initialized_) {
662     if (!Init())
663       return false;
664   }
665   return UpdateTables();
666 }
667
668 bool AndroidProviderBackend::Init() {
669   urls_handler_.reset(new UrlsSQLHandler(history_db_));
670   visit_handler_.reset(new VisitSQLHandler(history_db_));
671   android_urls_handler_.reset(new AndroidURLsSQLHandler(history_db_));
672   if (thumbnail_db_)
673     favicon_handler_.reset(new FaviconSQLHandler(thumbnail_db_));
674   bookmark_model_handler_.reset(new BookmarkModelSQLHandler(history_db_));
675   // The urls_handler must be pushed first, because the subsequent handlers
676   // depend on its output.
677   sql_handlers_.push_back(urls_handler_.get());
678   sql_handlers_.push_back(visit_handler_.get());
679   sql_handlers_.push_back(android_urls_handler_.get());
680   if (favicon_handler_.get())
681     sql_handlers_.push_back(favicon_handler_.get());
682   sql_handlers_.push_back(bookmark_model_handler_.get());
683
684   if (!history_db_->CreateAndroidURLsTable())
685     return false;
686   if (sql::INIT_OK != history_db_->InitAndroidCacheDatabase(
687           android_cache_db_filename_))
688     return false;
689   initialized_ = true;
690   return true;
691 }
692
693 bool AndroidProviderBackend::UpdateTables() {
694   if (!UpdateVisitedURLs()) {
695     LOG(ERROR) << "Update of the visisted URLS failed";
696     return false;
697   }
698
699   if (!UpdateRemovedURLs()) {
700     LOG(ERROR) << "Update of the removed URLS failed";
701     return false;
702   }
703
704   if (!UpdateBookmarks()) {
705     LOG(ERROR) << "Update of the bookmarks failed";
706     return false;
707   }
708
709   if (!UpdateFavicon()) {
710     LOG(ERROR) << "Update of the icons failed";
711     return false;
712   }
713
714   if (!UpdateSearchTermTable()) {
715     LOG(ERROR) << "Update of the search_terms failed";
716     return false;
717   }
718   return true;
719 }
720
721 bool AndroidProviderBackend::UpdateVisitedURLs() {
722   std::string sql(kURLUpdateClause);
723   sql.append("WHERE urls.id NOT IN (SELECT url_id FROM android_urls)");
724   sql::Statement urls_statement(db_->GetCachedStatement(SQL_FROM_HERE,
725                                                         sql.c_str()));
726   if (!urls_statement.is_valid()) {
727     LOG(ERROR) << db_->GetErrorMessage();
728     return false;
729   }
730
731   while (urls_statement.Step()) {
732     if (history_db_->GetAndroidURLRow(urls_statement.ColumnInt64(0), NULL))
733       continue;
734     if (!history_db_->AddAndroidURLRow(urls_statement.ColumnString(3),
735                                        urls_statement.ColumnInt64(0)))
736       return false;
737   }
738
739   if (!history_db_->ClearAllBookmarkCache())
740     return false;
741
742   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
743                                                    kURLUpdateClause));
744   while (statement.Step()) {
745     // The last_visit_time and the created time should be same when the visit
746     // count is 0, this behavior is also required by the Android CTS.
747     // The created_time could be set to the last_visit_time only when the type
748     // of the 'created' column is NULL because the left join is used in query
749     // and there is no row in the visit table when the visit count is 0.
750     base::Time last_visit_time =
751         base::Time::FromInternalValue(statement.ColumnInt64(1));
752     base::Time created_time = last_visit_time;
753
754     if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL)
755       created_time = base::Time::FromInternalValue(statement.ColumnInt64(2));
756
757     if (!history_db_->AddBookmarkCacheRow(created_time, last_visit_time,
758                                           statement.ColumnInt64(0)))
759       return false;
760   }
761   return true;
762 }
763
764 bool AndroidProviderBackend::UpdateRemovedURLs() {
765   return history_db_->DeleteUnusedAndroidURLs();
766 }
767
768 bool AndroidProviderBackend::UpdateBookmarks() {
769   if (history_client_ == NULL) {
770     LOG(ERROR) << "HistoryClient is not available";
771     return false;
772   }
773
774   std::vector<URLAndTitle> bookmarks;
775   history_client_->GetBookmarks(&bookmarks);
776
777   if (bookmarks.empty())
778     return true;
779
780   std::vector<URLID> url_ids;
781   for (std::vector<URLAndTitle>::const_iterator i =
782            bookmarks.begin(); i != bookmarks.end(); ++i) {
783     URLID url_id = history_db_->GetRowForURL(i->url, NULL);
784     if (url_id == 0) {
785       URLRow url_row(i->url);
786       url_row.set_title(i->title);
787       // Set the visit time to the UnixEpoch since that's when the Android
788       // system time starts. The Android have a CTS testcase for this.
789       url_row.set_last_visit(base::Time::UnixEpoch());
790       url_row.set_hidden(true);
791       url_id = history_db_->AddURL(url_row);
792       if (url_id == 0) {
793         LOG(ERROR) << "Can not add url for the new bookmark";
794         return false;
795       }
796       if (!history_db_->AddAndroidURLRow(i->url.spec(), url_id))
797         return false;
798       if (!history_db_->AddBookmarkCacheRow(base::Time::UnixEpoch(),
799                                             base::Time::UnixEpoch(), url_id))
800         return false;
801     }
802     url_ids.push_back(url_id);
803   }
804
805   return history_db_->MarkURLsAsBookmarked(url_ids);
806 }
807
808 bool AndroidProviderBackend::UpdateFavicon() {
809   ThumbnailDatabase::IconMappingEnumerator enumerator;
810
811   // We want the AndroidProviderBackend run without thumbnail_db_
812   if (!thumbnail_db_)
813     return true;
814
815   if (!thumbnail_db_->InitIconMappingEnumerator(favicon_base::FAVICON,
816                                                 &enumerator))
817     return false;
818
819   IconMapping icon_mapping;
820   while (enumerator.GetNextIconMapping(&icon_mapping)) {
821     URLID url_id = history_db_->GetRowForURL(icon_mapping.page_url, NULL);
822     if (url_id == 0) {
823       LOG(ERROR) << "Can not find favicon's page url";
824       continue;
825     }
826     history_db_->SetFaviconID(url_id, icon_mapping.icon_id);
827   }
828   return true;
829 }
830
831 bool AndroidProviderBackend::UpdateSearchTermTable() {
832   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
833                                                    kSearchTermUpdateClause));
834   while (statement.Step()) {
835     base::string16 term = statement.ColumnString16(0);
836     base::Time last_visit_time =
837         base::Time::FromInternalValue(statement.ColumnInt64(1));
838     SearchTermRow search_term_row;
839     if (history_db_->GetSearchTerm(term, &search_term_row)) {
840       if (search_term_row.last_visit_time != last_visit_time) {
841         search_term_row.last_visit_time = last_visit_time;
842         if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
843           return false;
844       }
845     } else {
846       if (!history_db_->AddSearchTerm(term, last_visit_time))
847         return false;
848     }
849   }
850   if (!history_db_->DeleteUnusedSearchTerms())
851     return false;
852
853   return true;
854 }
855
856 int AndroidProviderBackend::AppendBookmarkResultColumn(
857     const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
858     std::string* result_column) {
859   int replaced_index = -1;
860   // Attach the projections
861   bool first = true;
862   int index = 0;
863   for (std::vector<HistoryAndBookmarkRow::ColumnID>::const_iterator i =
864            projections.begin(); i != projections.end(); ++i) {
865     if (first)
866       first = false;
867     else
868       result_column->append(", ");
869
870     if (*i == HistoryAndBookmarkRow::FAVICON)
871       replaced_index = index;
872
873     result_column->append(HistoryAndBookmarkRow::GetAndroidName(*i));
874     index++;
875   }
876   return replaced_index;
877 }
878
879 bool AndroidProviderBackend::GetSelectedURLs(
880     const std::string& selection,
881     const std::vector<base::string16>& selection_args,
882     TableIDRows* rows) {
883   std::string sql("SELECT url_id, urls_url, bookmark FROM (");
884   sql.append(kVirtualHistoryAndBookmarkTable);
885   sql.append(" )");
886
887   if (!selection.empty()) {
888     sql.append(" WHERE ");
889     sql.append(selection);
890   }
891
892   sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
893   int count = 0;
894   BindStatement(selection_args, &statement, &count);
895   if (!statement.is_valid()) {
896     LOG(ERROR) << db_->GetErrorMessage();
897     return false;
898   }
899   while (statement.Step()) {
900     TableIDRow row;
901     row.url_id = statement.ColumnInt64(0);
902     row.url = GURL(statement.ColumnString(1));
903     row.bookmarked = statement.ColumnBool(2);
904     rows->push_back(row);
905   }
906   return true;
907 }
908
909 bool AndroidProviderBackend::GetSelectedSearchTerms(
910     const std::string& selection,
911     const std::vector<base::string16>& selection_args,
912     SearchTerms* rows) {
913   std::string sql("SELECT search "
914                   "FROM android_cache_db.search_terms ");
915   if (!selection.empty()) {
916     sql.append(" WHERE ");
917     sql.append(selection);
918   }
919   sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
920   int count = 0;
921   BindStatement(selection_args, &statement, &count);
922   if (!statement.is_valid()) {
923     LOG(ERROR) << db_->GetErrorMessage();
924     return false;
925   }
926   while (statement.Step()) {
927     rows->push_back(statement.ColumnString16(0));
928   }
929   return true;
930 }
931
932 void AndroidProviderBackend::AppendSearchResultColumn(
933     const std::vector<SearchRow::ColumnID>& projections,
934     std::string* result_column) {
935   bool first = true;
936   int index = 0;
937   for (std::vector<SearchRow::ColumnID>::const_iterator i =
938            projections.begin(); i != projections.end(); ++i) {
939     if (first)
940       first = false;
941     else
942       result_column->append(", ");
943
944     result_column->append(SearchRow::GetAndroidName(*i));
945     index++;
946   }
947 }
948
949 bool AndroidProviderBackend::SimulateUpdateURL(
950     const HistoryAndBookmarkRow& row,
951     const TableIDRows& ids,
952     HistoryNotifications* notifications) {
953   DCHECK(ids.size() == 1);
954   // URL can not be updated, we simulate the update by deleting the old URL
955   // and inserting the new one; We do update the android_urls table as the id
956   // need to keep same.
957
958   // Find all columns value of the current URL.
959   std::vector<HistoryAndBookmarkRow::ColumnID> projections;
960   projections.push_back(HistoryAndBookmarkRow::LAST_VISIT_TIME);
961   projections.push_back(HistoryAndBookmarkRow::CREATED);
962   projections.push_back(HistoryAndBookmarkRow::VISIT_COUNT);
963   projections.push_back(HistoryAndBookmarkRow::TITLE);
964   projections.push_back(HistoryAndBookmarkRow::FAVICON);
965   projections.push_back(HistoryAndBookmarkRow::BOOKMARK);
966
967   std::ostringstream oss;
968   oss << "url_id = " << ids[0].url_id;
969
970   scoped_ptr<AndroidStatement> statement;
971   statement.reset(QueryHistoryAndBookmarksInternal(projections, oss.str(),
972       std::vector<base::string16>(), std::string()));
973   if (!statement.get() || !statement->statement()->Step())
974     return false;
975
976   HistoryAndBookmarkRow new_row;
977   new_row.set_last_visit_time(FromDatabaseTime(
978       statement->statement()->ColumnInt64(0)));
979   new_row.set_created(FromDatabaseTime(
980       statement->statement()->ColumnInt64(1)));
981   new_row.set_visit_count(statement->statement()->ColumnInt(2));
982   new_row.set_title(statement->statement()->ColumnString16(3));
983
984   std::set<GURL> favicons;
985   scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
986   scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
987   URLRow old_url_row;
988   if (!history_db_->GetURLRow(ids[0].url_id, &old_url_row))
989     return false;
990   deleted_details->rows.push_back(old_url_row);
991
992   favicon_base::FaviconID favicon_id = statement->statement()->ColumnInt64(4);
993   if (favicon_id) {
994     std::vector<FaviconBitmap> favicon_bitmaps;
995     if (!thumbnail_db_ ||
996         !thumbnail_db_->GetFaviconBitmaps(favicon_id, &favicon_bitmaps))
997       return false;
998    scoped_refptr<base::RefCountedMemory> bitmap_data =
999        favicon_bitmaps[0].bitmap_data;
1000    if (bitmap_data.get() && bitmap_data->size())
1001       new_row.set_favicon(bitmap_data);
1002    favicons.insert(old_url_row.url());
1003    favicons.insert(row.url());
1004   }
1005   new_row.set_is_bookmark(statement->statement()->ColumnBool(5));
1006
1007   // The SQLHandler vector is not used here because the row in android_url
1008   // shouldn't be deleted, we need keep the AndroidUIID unchanged, so it
1009   // appears update to the client.
1010   if (!urls_handler_->Delete(ids))
1011     return false;
1012
1013   if (!visit_handler_->Delete(ids))
1014     return false;
1015
1016   if (favicon_handler_ && !favicon_handler_->Delete(ids))
1017     return false;
1018
1019   if (!bookmark_model_handler_->Delete(ids))
1020     return false;
1021
1022   new_row.set_url(row.url());
1023   new_row.set_raw_url(row.raw_url());
1024   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME))
1025     new_row.set_last_visit_time(row.last_visit_time());
1026   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED))
1027     new_row.set_created(row.created());
1028   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT))
1029     new_row.set_visit_count(row.visit_count());
1030   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE))
1031     new_row.set_title(row.title());
1032   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON)) {
1033     new_row.set_favicon(row.favicon());
1034     favicons.insert(new_row.url());
1035   }
1036   if (row.is_value_set_explicitly(HistoryAndBookmarkRow::BOOKMARK))
1037     new_row.set_is_bookmark(row.is_bookmark());
1038
1039   if (!urls_handler_->Insert(&new_row))
1040     return false;
1041
1042   if (!visit_handler_->Insert(&new_row))
1043     return false;
1044
1045   // Update the current row instead of inserting a new row in android urls
1046   // table. We need keep the AndroidUIID unchanged, so it appears update
1047   // to the client.
1048   if (!android_urls_handler_->Update(new_row, ids))
1049     return false;
1050
1051   if (favicon_handler_ && !favicon_handler_->Insert(&new_row))
1052     return false;
1053
1054   if (!bookmark_model_handler_->Insert(&new_row))
1055     return false;
1056
1057   URLRow new_url_row;
1058   if (!history_db_->GetURLRow(new_row.url_id(), &new_url_row))
1059     return false;
1060
1061   modified->changed_urls.push_back(new_url_row);
1062
1063   scoped_ptr<HistoryDetails> details = deleted_details.Pass();
1064   notifications->push_back(
1065       base::Bind(&HistoryBackend::Delegate::BroadcastNotifications,
1066                  base::Unretained(delegate_),
1067                  chrome::NOTIFICATION_HISTORY_URLS_DELETED,
1068                  base::Passed(&details)));
1069   if (!favicons.empty()) {
1070     notifications->push_back(
1071         base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
1072                    base::Unretained(delegate_),
1073                    favicons));
1074   }
1075   scoped_ptr<HistoryDetails> other_details = modified.Pass();
1076   notifications->push_back(
1077       base::Bind(&HistoryBackend::Delegate::BroadcastNotifications,
1078                  base::Unretained(delegate_),
1079                  chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
1080                  base::Passed(&other_details)));
1081
1082   return true;
1083 }
1084
1085 AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarksInternal(
1086     const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
1087     const std::string& selection,
1088     const std::vector<base::string16>& selection_args,
1089     const std::string& sort_order) {
1090   std::string sql;
1091   sql.append("SELECT ");
1092   int replaced_index = AppendBookmarkResultColumn(projections, &sql);
1093   sql.append(" FROM (");
1094   sql.append(kVirtualHistoryAndBookmarkTable);
1095   sql.append(")");
1096
1097   if (!selection.empty()) {
1098     sql.append(" WHERE ");
1099     sql.append(selection);
1100   }
1101
1102   if (!sort_order.empty()) {
1103     sql.append(" ORDER BY ");
1104     sql.append(sort_order);
1105   }
1106
1107   scoped_ptr<sql::Statement> statement(new sql::Statement(
1108       db_->GetUniqueStatement(sql.c_str())));
1109   int count = 0;
1110   BindStatement(selection_args, statement.get(), &count);
1111   if (!statement->is_valid()) {
1112     LOG(ERROR) << db_->GetErrorMessage();
1113     return NULL;
1114   }
1115   sql::Statement* result = statement.release();
1116   return new AndroidStatement(result, replaced_index);
1117 }
1118
1119 bool AndroidProviderBackend::DeleteHistoryInternal(
1120     const TableIDRows& urls,
1121     bool delete_bookmarks,
1122     HistoryNotifications* notifications) {
1123   std::set<GURL> favicon;
1124   scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
1125   for (TableIDRows::const_iterator i = urls.begin(); i != urls.end(); ++i) {
1126     URLRow url_row;
1127     if (!history_db_->GetURLRow(i->url_id, &url_row))
1128       return false;
1129     deleted_details->rows.push_back(url_row);
1130     if (thumbnail_db_ &&
1131         thumbnail_db_->GetIconMappingsForPageURL(url_row.url(), NULL))
1132       favicon.insert(url_row.url());
1133   }
1134
1135   // Only invoke Delete on the BookmarkModelHandler if we need
1136   // to delete bookmarks.
1137   for (std::vector<SQLHandler*>::iterator i =
1138        sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
1139     if ((*i) != bookmark_model_handler_.get() || delete_bookmarks)
1140       if (!(*i)->Delete(urls))
1141         return false;
1142   }
1143
1144   scoped_ptr<HistoryDetails> details = deleted_details.Pass();
1145   notifications->push_back(
1146       base::Bind(&HistoryBackend::Delegate::BroadcastNotifications,
1147                  base::Unretained(delegate_),
1148                  chrome::NOTIFICATION_HISTORY_URLS_DELETED,
1149                  base::Passed(&details)));
1150   if (!favicon.empty()) {
1151     notifications->push_back(
1152         base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
1153                    base::Unretained(delegate_),
1154                    favicon));
1155   }
1156   return true;
1157 }
1158
1159 void AndroidProviderBackend::BroadcastNotifications(
1160     HistoryNotifications* notifications) {
1161   while (!notifications->empty()) {
1162     notifications->back().Run();
1163     notifications->pop_back();
1164   }
1165 }
1166
1167 bool AndroidProviderBackend::AddSearchTerm(const SearchRow& values) {
1168   DCHECK(values.is_value_set_explicitly(SearchRow::SEARCH_TERM));
1169   DCHECK(values.is_value_set_explicitly(SearchRow::KEYWORD_ID));
1170   DCHECK(values.is_value_set_explicitly(SearchRow::URL));
1171
1172   URLRow url_row;
1173   HistoryAndBookmarkRow bookmark_row;
1174   // Android CTS test BrowserTest.testAccessSearches allows insert the same
1175   // seach term multiple times, and just search time need updated.
1176   if (history_db_->GetRowForURL(values.url(), &url_row)) {
1177     // Already exist, Add a visit.
1178     if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
1179       bookmark_row.set_last_visit_time(values.search_time());
1180     else
1181       bookmark_row.set_visit_count(url_row.visit_count() + 1);
1182     TableIDRows table_id_rows;
1183     TableIDRow table_id_row;
1184     table_id_row.url = values.url();
1185     table_id_row.url_id = url_row.id();
1186     table_id_rows.push_back(table_id_row);
1187     if (!urls_handler_->Update(bookmark_row, table_id_rows))
1188       return false;
1189     if (!visit_handler_->Update(bookmark_row, table_id_rows))
1190       return false;
1191
1192     if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL))
1193       if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(),
1194                values.keyword_id(), values.search_term()))
1195         return false;
1196   } else {
1197     bookmark_row.set_raw_url(values.url().spec());
1198     bookmark_row.set_url(values.url());
1199     if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
1200       bookmark_row.set_last_visit_time(values.search_time());
1201
1202     if (!urls_handler_->Insert(&bookmark_row))
1203       return false;
1204
1205     if (!visit_handler_->Insert(&bookmark_row))
1206       return false;
1207
1208     if (!android_urls_handler_->Insert(&bookmark_row))
1209       return false;
1210
1211     if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(),
1212                           values.keyword_id(), values.search_term()))
1213       return false;
1214   }
1215   return true;
1216 }
1217
1218 }  // namespace history