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<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() > 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<base::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<base::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 ¬ifications)) 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, ¬ifications, 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<base::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 ¬ifications)) 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<base::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 ¬ifications)) 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<base::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<base::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<base::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<base::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<base::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<base::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 base::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<base::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<base::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<base::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<base::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