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