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