Home | History | Annotate | Download | only in android
      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 
     23 using base::Time;
     24 using base::TimeDelta;
     25 
     26 namespace history {
     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<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 
     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() > 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 
    118 AndroidProviderBackend::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 
    134 AndroidProviderBackend::~AndroidProviderBackend() {
    135 }
    136 
    137 AndroidStatement* 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 
    156 bool 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 
    174 AndroidURLID 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 
    189 bool 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 
    206 bool 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 
    223 AndroidProviderBackend::HistoryNotification::HistoryNotification(
    224     int type,
    225     HistoryDetails* detail)
    226     : type(type),
    227       detail(detail) {
    228 }
    229 
    230 AndroidProviderBackend::HistoryNotification::~HistoryNotification() {
    231 }
    232 
    233 AndroidProviderBackend::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 
    261 AndroidProviderBackend::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 
    282 void AndroidProviderBackend::ScopedTransaction::Commit() {
    283   DCHECK(!committed_);
    284   history_db_->CommitTransaction();
    285   if (thumbnail_db_)
    286     thumbnail_db_->CommitTransaction();
    287   committed_ = true;
    288 }
    289 
    290 bool 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 
    366 AndroidURLID 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 
    413 bool 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 
    438 bool 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 
    494 AndroidStatement* 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 
    532 bool 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 
    617 SearchTermID 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 
    632 bool 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 
    656 bool AndroidProviderBackend::EnsureInitializedAndUpdated() {
    657   if (!initialized_) {
    658     if (!Init())
    659       return false;
    660   }
    661   return UpdateTables();
    662 }
    663 
    664 bool 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 
    689 bool 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 
    717 bool 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 
    759 bool AndroidProviderBackend::UpdateRemovedURLs() {
    760   return history_db_->DeleteUnusedAndroidURLs();
    761 }
    762 
    763 bool 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 
    804 bool 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 
    826 bool 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 
    850 int 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 
    873 bool 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 
    903 bool 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 
    926 void 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 
    943 bool 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 
   1069 AndroidStatement* 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 
   1103 bool 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 
   1137 void 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 
   1145 bool 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