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.
5 #include "chrome/browser/history/android/android_provider_backend.h"
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"
26 // Helpers --------------------------------------------------------------------
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
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))";
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) ";
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";
62 void BindStatement(const std::vector<base::string16>& selection_args,
63 sql::Statement* statement,
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);
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));
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
83 // d. The difference between created and last visit time is less than
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())
91 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
92 row.created() > base::Time::Now())
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())
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))
108 if (row.last_visit_time().ToInternalValue() -
109 row.created().ToInternalValue() < row.visit_count())
118 // AndroidProviderBackend::ScopedTransaction ----------------------------------
120 AndroidProviderBackend::ScopedTransaction::ScopedTransaction(
121 HistoryDatabase* history_db,
122 ThumbnailDatabase* thumbnail_db)
123 : history_db_(history_db),
124 thumbnail_db_(thumbnail_db),
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_;
137 history_db_->CommitTransaction();
138 history_db_->BeginTransaction();
141 count = thumbnail_transaction_nesting_;
143 thumbnail_db_->CommitTransaction();
144 thumbnail_db_->BeginTransaction();
148 AndroidProviderBackend::ScopedTransaction::~ScopedTransaction() {
150 history_db_->RollbackTransaction();
152 thumbnail_db_->RollbackTransaction();
154 // There is no transaction now.
155 DCHECK_EQ(0, history_db_->transaction_nesting());
156 DCHECK(!thumbnail_db_ || 0 == thumbnail_db_->transaction_nesting());
158 int count = history_transaction_nesting_;
160 history_db_->BeginTransaction();
163 count = thumbnail_transaction_nesting_;
165 thumbnail_db_->BeginTransaction();
169 void AndroidProviderBackend::ScopedTransaction::Commit() {
171 history_db_->CommitTransaction();
173 thumbnail_db_->CommitTransaction();
178 // AndroidProviderBackend -----------------------------------------------------
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),
192 delegate_(delegate) {
196 AndroidProviderBackend::~AndroidProviderBackend() {
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())
207 ScopedTransaction transaction(history_db_, thumbnail_db_);
209 if (!EnsureInitializedAndUpdated())
212 transaction.Commit();
214 return QueryHistoryAndBookmarksInternal(projections, selection,
215 selection_args, sort_order);
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;
225 ScopedTransaction transaction(history_db_, thumbnail_db_);
227 if (!UpdateHistoryAndBookmarks(row, selection, selection_args, updated_count,
231 transaction.Commit();
232 BroadcastNotifications(¬ifications);
236 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
237 const HistoryAndBookmarkRow& values) {
238 HistoryNotifications notifications;
240 ScopedTransaction transaction(history_db_, thumbnail_db_);
242 AndroidURLID id = InsertHistoryAndBookmark(values, true, ¬ifications);
246 transaction.Commit();
247 BroadcastNotifications(¬ifications);
251 bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
252 const std::string& selection,
253 const std::vector<base::string16>& selection_args,
254 int* deleted_count) {
255 HistoryNotifications notifications;
257 ScopedTransaction transaction(history_db_, thumbnail_db_);
259 if (!DeleteHistoryAndBookmarks(selection, selection_args, deleted_count,
263 transaction.Commit();
264 BroadcastNotifications(¬ifications);
268 bool AndroidProviderBackend::DeleteHistory(
269 const std::string& selection,
270 const std::vector<base::string16>& selection_args,
271 int* deleted_count) {
272 HistoryNotifications notifications;
274 ScopedTransaction transaction(history_db_, thumbnail_db_);
276 if (!DeleteHistory(selection, selection_args, deleted_count, ¬ifications))
279 transaction.Commit();
280 BroadcastNotifications(¬ifications);
284 bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
285 const HistoryAndBookmarkRow& row,
286 const std::string& selection,
287 const std::vector<base::string16>& selection_args,
289 HistoryNotifications* notifications) {
290 if (!IsHistoryAndBookmarkRowValid(row))
293 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::ID))
296 if (!EnsureInitializedAndUpdated())
300 if (!GetSelectedURLs(selection, selection_args, &ids_set))
303 if (ids_set.empty()) {
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)
315 HistoryAndBookmarkRow new_row = row;
316 if (!SimulateUpdateURL(new_row, ids_set, notifications))
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))
329 *updated_count = ids_set.size();
331 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
332 std::set<GURL> favicon;
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)) {
339 if (!history_db_->GetURLRow(id.url_id, &url_row))
341 modified->changed_urls.push_back(url_row);
344 row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON))
345 favicon.insert(id.url);
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)));
357 if (!favicon.empty()) {
358 notifications->push_back(
359 base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
360 base::Unretained(delegate_),
367 AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
368 const HistoryAndBookmarkRow& values,
369 bool ensure_initialized_and_updated,
370 HistoryNotifications* notifications) {
371 if (!IsHistoryAndBookmarkRowValid(values))
374 if (ensure_initialized_and_updated && !EnsureInitializedAndUpdated())
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))
387 if (!history_db_->GetURLRow(row.url_id(), &url_row))
390 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
391 modified->changed_urls.push_back(url_row);
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());
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)));
407 if (!favicon.empty()) {
408 notifications->push_back(
409 base::Bind(&HistoryBackend::Delegate::NotifyFaviconChanged,
410 base::Unretained(delegate_),
417 bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
418 const std::string& selection,
419 const std::vector<base::string16>& selection_args,
421 HistoryNotifications* notifications) {
422 if (!EnsureInitializedAndUpdated())
426 if (!GetSelectedURLs(selection, selection_args, &ids_set))
429 if (ids_set.empty()) {
434 if (!DeleteHistoryInternal(ids_set, true, notifications))
437 *deleted_count = ids_set.size();
442 bool AndroidProviderBackend::DeleteHistory(
443 const std::string& selection,
444 const std::vector<base::string16>& selection_args,
446 HistoryNotifications* notifications) {
447 if (!EnsureInitializedAndUpdated())
451 if (!GetSelectedURLs(selection, selection_args, &ids_set))
454 if (ids_set.empty()) {
459 *deleted_count = ids_set.size();
461 // Get the bookmarked rows.
462 std::vector<HistoryAndBookmarkRow> bookmarks;
463 for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
466 AndroidURLRow android_url_row;
467 if (!history_db_->GetAndroidURLRow(i->url_id, &android_url_row))
469 HistoryAndBookmarkRow row;
470 row.set_raw_url(android_url_row.raw_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
478 bookmarks.push_back(row);
482 // Don't delete the bookmark from bookmark model when deleting the history.
483 if (!DeleteHistoryInternal(ids_set, false, notifications))
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))
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())
506 if (!EnsureInitializedAndUpdated())
510 sql.append("SELECT ");
511 AppendSearchResultColumn(projections, &sql);
512 sql.append(" FROM android_cache_db.search_terms ");
514 if (!selection.empty()) {
515 sql.append(" WHERE ");
516 sql.append(selection);
519 if (!sort_order.empty()) {
520 sql.append(" ORDER BY ");
521 sql.append(sort_order);
524 scoped_ptr<sql::Statement> statement(new sql::Statement(
525 db_->GetUniqueStatement(sql.c_str())));
527 BindStatement(selection_args, statement.get(), &count);
528 if (!statement->is_valid()) {
529 LOG(ERROR) << db_->GetErrorMessage();
532 sql::Statement* result = statement.release();
533 return new AndroidStatement(result, -1);
536 bool AndroidProviderBackend::UpdateSearchTerms(
537 const SearchRow& row,
538 const std::string& selection,
539 const std::vector<base::string16>& selection_args,
541 if (!EnsureInitializedAndUpdated())
544 SearchTerms search_terms;
545 if (!GetSelectedSearchTerms(selection, selection_args, &search_terms))
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)
553 *update_count = search_terms.size();
555 if (search_terms.empty())
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))
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);
568 search_term_row.last_visit_time = search_row.search_time();
570 // Delete the original search term.
571 if (!history_db_->DeleteKeywordSearchTerm(search_terms[0]))
575 if (!AddSearchTerm(search_row))
578 // Update the cache table so the id will not be changed.
579 if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
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))
591 // Check whether the given search time less than the existing one.
592 if (search_term_row.last_visit_time > row.search_time())
595 std::vector<KeywordSearchTermRow> search_term_rows;
596 if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) ||
597 search_term_rows.empty())
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.
603 if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row))
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))
615 if (!visit_handler_->Update(bookmark_row, table_id_rows))
621 SearchTermID AndroidProviderBackend::InsertSearchTerm(
622 const SearchRow& values) {
623 if (!EnsureInitializedAndUpdated())
626 if (!AddSearchTerm(values))
629 SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL);
631 // Note the passed in Time() will be changed in UpdateSearchTermTable().
632 id = history_db_->AddSearchTerm(values.search_term(), base::Time());
636 bool AndroidProviderBackend::DeleteSearchTerms(
637 const std::string& selection,
638 const std::vector<base::string16>& selection_args,
639 int * deleted_count) {
640 if (!EnsureInitializedAndUpdated())
644 if (!GetSelectedSearchTerms(selection, selection_args, &rows))
647 *deleted_count = rows.size();
651 for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i)
652 if (!history_db_->DeleteKeywordSearchTerm(*i))
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.
660 bool AndroidProviderBackend::EnsureInitializedAndUpdated() {
665 return UpdateTables();
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_));
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());
684 if (!history_db_->CreateAndroidURLsTable())
686 if (sql::INIT_OK != history_db_->InitAndroidCacheDatabase(
687 android_cache_db_filename_))
693 bool AndroidProviderBackend::UpdateTables() {
694 if (!UpdateVisitedURLs()) {
695 LOG(ERROR) << "Update of the visisted URLS failed";
699 if (!UpdateRemovedURLs()) {
700 LOG(ERROR) << "Update of the removed URLS failed";
704 if (!UpdateBookmarks()) {
705 LOG(ERROR) << "Update of the bookmarks failed";
709 if (!UpdateFavicon()) {
710 LOG(ERROR) << "Update of the icons failed";
714 if (!UpdateSearchTermTable()) {
715 LOG(ERROR) << "Update of the search_terms failed";
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,
726 if (!urls_statement.is_valid()) {
727 LOG(ERROR) << db_->GetErrorMessage();
731 while (urls_statement.Step()) {
732 if (history_db_->GetAndroidURLRow(urls_statement.ColumnInt64(0), NULL))
734 if (!history_db_->AddAndroidURLRow(urls_statement.ColumnString(3),
735 urls_statement.ColumnInt64(0)))
739 if (!history_db_->ClearAllBookmarkCache())
742 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
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;
754 if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL)
755 created_time = base::Time::FromInternalValue(statement.ColumnInt64(2));
757 if (!history_db_->AddBookmarkCacheRow(created_time, last_visit_time,
758 statement.ColumnInt64(0)))
764 bool AndroidProviderBackend::UpdateRemovedURLs() {
765 return history_db_->DeleteUnusedAndroidURLs();
768 bool AndroidProviderBackend::UpdateBookmarks() {
769 if (history_client_ == NULL) {
770 LOG(ERROR) << "HistoryClient is not available";
774 std::vector<URLAndTitle> bookmarks;
775 history_client_->GetBookmarks(&bookmarks);
777 if (bookmarks.empty())
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);
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);
793 LOG(ERROR) << "Can not add url for the new bookmark";
796 if (!history_db_->AddAndroidURLRow(i->url.spec(), url_id))
798 if (!history_db_->AddBookmarkCacheRow(base::Time::UnixEpoch(),
799 base::Time::UnixEpoch(), url_id))
802 url_ids.push_back(url_id);
805 return history_db_->MarkURLsAsBookmarked(url_ids);
808 bool AndroidProviderBackend::UpdateFavicon() {
809 ThumbnailDatabase::IconMappingEnumerator enumerator;
811 // We want the AndroidProviderBackend run without thumbnail_db_
815 if (!thumbnail_db_->InitIconMappingEnumerator(favicon_base::FAVICON,
819 IconMapping icon_mapping;
820 while (enumerator.GetNextIconMapping(&icon_mapping)) {
821 URLID url_id = history_db_->GetRowForURL(icon_mapping.page_url, NULL);
823 LOG(ERROR) << "Can not find favicon's page url";
826 history_db_->SetFaviconID(url_id, icon_mapping.icon_id);
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))
846 if (!history_db_->AddSearchTerm(term, last_visit_time))
850 if (!history_db_->DeleteUnusedSearchTerms())
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
863 for (std::vector<HistoryAndBookmarkRow::ColumnID>::const_iterator i =
864 projections.begin(); i != projections.end(); ++i) {
868 result_column->append(", ");
870 if (*i == HistoryAndBookmarkRow::FAVICON)
871 replaced_index = index;
873 result_column->append(HistoryAndBookmarkRow::GetAndroidName(*i));
876 return replaced_index;
879 bool AndroidProviderBackend::GetSelectedURLs(
880 const std::string& selection,
881 const std::vector<base::string16>& selection_args,
883 std::string sql("SELECT url_id, urls_url, bookmark FROM (");
884 sql.append(kVirtualHistoryAndBookmarkTable);
887 if (!selection.empty()) {
888 sql.append(" WHERE ");
889 sql.append(selection);
892 sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
894 BindStatement(selection_args, &statement, &count);
895 if (!statement.is_valid()) {
896 LOG(ERROR) << db_->GetErrorMessage();
899 while (statement.Step()) {
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);
909 bool AndroidProviderBackend::GetSelectedSearchTerms(
910 const std::string& selection,
911 const std::vector<base::string16>& selection_args,
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);
919 sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
921 BindStatement(selection_args, &statement, &count);
922 if (!statement.is_valid()) {
923 LOG(ERROR) << db_->GetErrorMessage();
926 while (statement.Step()) {
927 rows->push_back(statement.ColumnString16(0));
932 void AndroidProviderBackend::AppendSearchResultColumn(
933 const std::vector<SearchRow::ColumnID>& projections,
934 std::string* result_column) {
937 for (std::vector<SearchRow::ColumnID>::const_iterator i =
938 projections.begin(); i != projections.end(); ++i) {
942 result_column->append(", ");
944 result_column->append(SearchRow::GetAndroidName(*i));
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.
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);
967 std::ostringstream oss;
968 oss << "url_id = " << ids[0].url_id;
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())
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));
984 std::set<GURL> favicons;
985 scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
986 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
988 if (!history_db_->GetURLRow(ids[0].url_id, &old_url_row))
990 deleted_details->rows.push_back(old_url_row);
992 favicon_base::FaviconID favicon_id = statement->statement()->ColumnInt64(4);
994 std::vector<FaviconBitmap> favicon_bitmaps;
995 if (!thumbnail_db_ ||
996 !thumbnail_db_->GetFaviconBitmaps(favicon_id, &favicon_bitmaps))
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());
1005 new_row.set_is_bookmark(statement->statement()->ColumnBool(5));
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))
1013 if (!visit_handler_->Delete(ids))
1016 if (favicon_handler_ && !favicon_handler_->Delete(ids))
1019 if (!bookmark_model_handler_->Delete(ids))
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());
1036 if (row.is_value_set_explicitly(HistoryAndBookmarkRow::BOOKMARK))
1037 new_row.set_is_bookmark(row.is_bookmark());
1039 if (!urls_handler_->Insert(&new_row))
1042 if (!visit_handler_->Insert(&new_row))
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
1048 if (!android_urls_handler_->Update(new_row, ids))
1051 if (favicon_handler_ && !favicon_handler_->Insert(&new_row))
1054 if (!bookmark_model_handler_->Insert(&new_row))
1058 if (!history_db_->GetURLRow(new_row.url_id(), &new_url_row))
1061 modified->changed_urls.push_back(new_url_row);
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_),
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)));
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) {
1091 sql.append("SELECT ");
1092 int replaced_index = AppendBookmarkResultColumn(projections, &sql);
1093 sql.append(" FROM (");
1094 sql.append(kVirtualHistoryAndBookmarkTable);
1097 if (!selection.empty()) {
1098 sql.append(" WHERE ");
1099 sql.append(selection);
1102 if (!sort_order.empty()) {
1103 sql.append(" ORDER BY ");
1104 sql.append(sort_order);
1107 scoped_ptr<sql::Statement> statement(new sql::Statement(
1108 db_->GetUniqueStatement(sql.c_str())));
1110 BindStatement(selection_args, statement.get(), &count);
1111 if (!statement->is_valid()) {
1112 LOG(ERROR) << db_->GetErrorMessage();
1115 sql::Statement* result = statement.release();
1116 return new AndroidStatement(result, replaced_index);
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) {
1127 if (!history_db_->GetURLRow(i->url_id, &url_row))
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());
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))
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_),
1159 void AndroidProviderBackend::BroadcastNotifications(
1160 HistoryNotifications* notifications) {
1161 while (!notifications->empty()) {
1162 notifications->back().Run();
1163 notifications->pop_back();
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));
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());
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))
1189 if (!visit_handler_->Update(bookmark_row, table_id_rows))
1192 if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL))
1193 if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(),
1194 values.keyword_id(), values.search_term()))
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());
1202 if (!urls_handler_->Insert(&bookmark_row))
1205 if (!visit_handler_->Insert(&bookmark_row))
1208 if (!android_urls_handler_->Insert(&bookmark_row))
1211 if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(),
1212 values.keyword_id(), values.search_term()))
1218 } // namespace history