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/chromeos/extensions/file_manager/event_router.h" 6 7 #include "base/bind.h" 8 #include "base/file_util.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/prefs/pref_change_registrar.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/stl_util.h" 13 #include "base/threading/sequenced_worker_pool.h" 14 #include "base/values.h" 15 #include "chrome/browser/app_mode/app_mode_utils.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/chromeos/drive/drive_integration_service.h" 18 #include "chrome/browser/chromeos/drive/file_system_interface.h" 19 #include "chrome/browser/chromeos/drive/file_system_util.h" 20 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h" 21 #include "chrome/browser/chromeos/file_manager/app_id.h" 22 #include "chrome/browser/chromeos/file_manager/fileapi_util.h" 23 #include "chrome/browser/chromeos/file_manager/open_util.h" 24 #include "chrome/browser/chromeos/file_manager/volume_manager.h" 25 #include "chrome/browser/chromeos/login/lock/screen_locker.h" 26 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 27 #include "chrome/browser/drive/drive_service_interface.h" 28 #include "chrome/browser/extensions/extension_service.h" 29 #include "chrome/browser/profiles/profile.h" 30 #include "chrome/browser/profiles/profile_manager.h" 31 #include "chrome/common/pref_names.h" 32 #include "chromeos/login/login_state.h" 33 #include "chromeos/network/network_handler.h" 34 #include "chromeos/network/network_state_handler.h" 35 #include "content/public/browser/browser_thread.h" 36 #include "content/public/browser/notification_source.h" 37 #include "content/public/browser/render_process_host.h" 38 #include "extensions/browser/event_router.h" 39 #include "extensions/browser/extension_host.h" 40 #include "extensions/browser/extension_prefs.h" 41 #include "extensions/browser/extension_system.h" 42 #include "webkit/common/fileapi/file_system_types.h" 43 #include "webkit/common/fileapi/file_system_util.h" 44 45 using chromeos::disks::DiskMountManager; 46 using chromeos::NetworkHandler; 47 using content::BrowserThread; 48 using drive::DriveIntegrationService; 49 using drive::DriveIntegrationServiceFactory; 50 using file_manager::util::EntryDefinition; 51 using file_manager::util::FileDefinition; 52 53 namespace file_browser_private = extensions::api::file_browser_private; 54 55 namespace file_manager { 56 namespace { 57 58 void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path, 59 const base::Closure& success_callback, 60 const base::Closure& failure_callback) { 61 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 62 63 if (base::DirectoryExists(directory_path)) 64 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback); 65 else 66 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback); 67 } 68 69 void DirectoryExistsOnUIThread(const base::FilePath& directory_path, 70 const base::Closure& success_callback, 71 const base::Closure& failure_callback) { 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 73 74 content::BrowserThread::PostBlockingPoolTask( 75 FROM_HERE, 76 base::Bind(&DirectoryExistsOnBlockingPool, 77 directory_path, 78 success_callback, 79 failure_callback)); 80 } 81 82 // Constants for the "transferState" field of onFileTransferUpdated event. 83 const char kFileTransferStateStarted[] = "started"; 84 const char kFileTransferStateInProgress[] = "in_progress"; 85 const char kFileTransferStateCompleted[] = "completed"; 86 const char kFileTransferStateFailed[] = "failed"; 87 88 // Frequency of sending onFileTransferUpdated. 89 const int64 kProgressEventFrequencyInMilliseconds = 1000; 90 91 // Utility function to check if |job_info| is a file uploading job. 92 bool IsUploadJob(drive::JobType type) { 93 return (type == drive::TYPE_UPLOAD_NEW_FILE || 94 type == drive::TYPE_UPLOAD_EXISTING_FILE); 95 } 96 97 // Converts the job info to a IDL generated type. 98 void JobInfoToTransferStatus( 99 Profile* profile, 100 const std::string& extension_id, 101 const std::string& job_status, 102 const drive::JobInfo& job_info, 103 file_browser_private::FileTransferStatus* status) { 104 DCHECK(IsActiveFileTransferJobInfo(job_info)); 105 106 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); 107 GURL url = util::ConvertDrivePathToFileSystemUrl( 108 profile, job_info.file_path, extension_id); 109 status->file_url = url.spec(); 110 status->transfer_state = file_browser_private::ParseTransferState(job_status); 111 status->transfer_type = 112 IsUploadJob(job_info.job_type) ? 113 file_browser_private::TRANSFER_TYPE_UPLOAD : 114 file_browser_private::TRANSFER_TYPE_DOWNLOAD; 115 // JavaScript does not have 64-bit integers. Instead we use double, which 116 // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice 117 // in C++. Larger values are rounded. 118 status->processed.reset( 119 new double(static_cast<double>(job_info.num_completed_bytes))); 120 status->total.reset( 121 new double(static_cast<double>(job_info.num_total_bytes))); 122 } 123 124 // Checks for availability of the Google+ Photos app. 125 // TODO(mtomasz): Replace with crbug.com/341902 solution. 126 bool IsGooglePhotosInstalled(Profile *profile) { 127 ExtensionService* service = 128 extensions::ExtensionSystem::Get(profile)->extension_service(); 129 if (!service) 130 return false; 131 132 // Google+ Photos uses several ids for different channels. Therefore, all of 133 // them should be checked. 134 const std::string kGooglePlusPhotosIds[] = { 135 "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging 136 "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod 137 "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev 138 }; 139 140 for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) { 141 if (service->GetExtensionById(kGooglePlusPhotosIds[i], 142 false /* include_disable */) != NULL) 143 return true; 144 } 145 146 return false; 147 } 148 149 // Checks if the Recovery Tool is running. This is a temporary solution. 150 // TODO(mtomasz): Replace with crbug.com/341902 solution. 151 bool IsRecoveryToolRunning(Profile* profile) { 152 extensions::ExtensionPrefs* extension_prefs = 153 extensions::ExtensionPrefs::Get(profile); 154 if (!extension_prefs) 155 return false; 156 157 const std::string kRecoveryToolIds[] = { 158 "kkebgepbbgbcmghedmmdfcbdcodlkngh", // Recovery tool staging 159 "jndclpdbaamdhonoechobihbbiimdgai" // Recovery tool prod 160 }; 161 162 for (size_t i = 0; i < arraysize(kRecoveryToolIds); ++i) { 163 const std::string extension_id = kRecoveryToolIds[i]; 164 if (extension_prefs->IsExtensionRunning(extension_id)) 165 return true; 166 } 167 168 return false; 169 } 170 171 // Sends an event named |event_name| with arguments |event_args| to extensions. 172 void BroadcastEvent(Profile* profile, 173 const std::string& event_name, 174 scoped_ptr<base::ListValue> event_args) { 175 extensions::EventRouter::Get(profile)->BroadcastEvent( 176 make_scoped_ptr(new extensions::Event(event_name, event_args.Pass()))); 177 } 178 179 file_browser_private::MountCompletedStatus 180 MountErrorToMountCompletedStatus(chromeos::MountError error) { 181 switch (error) { 182 case chromeos::MOUNT_ERROR_NONE: 183 return file_browser_private::MOUNT_COMPLETED_STATUS_SUCCESS; 184 case chromeos::MOUNT_ERROR_UNKNOWN: 185 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN; 186 case chromeos::MOUNT_ERROR_INTERNAL: 187 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_INTERNAL; 188 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: 189 return file_browser_private:: 190 MOUNT_COMPLETED_STATUS_ERROR_INVALID_ARGUMENT; 191 case chromeos::MOUNT_ERROR_INVALID_PATH: 192 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_INVALID_PATH; 193 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: 194 return file_browser_private:: 195 MOUNT_COMPLETED_STATUS_ERROR_PATH_ALREADY_MOUNTED; 196 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: 197 return file_browser_private:: 198 MOUNT_COMPLETED_STATUS_ERROR_PATH_NOT_MOUNTED; 199 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: 200 return file_browser_private 201 ::MOUNT_COMPLETED_STATUS_ERROR_DIRECTORY_CREATION_FAILED; 202 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: 203 return file_browser_private 204 ::MOUNT_COMPLETED_STATUS_ERROR_INVALID_MOUNT_OPTIONS; 205 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: 206 return file_browser_private:: 207 MOUNT_COMPLETED_STATUS_ERROR_INVALID_UNMOUNT_OPTIONS; 208 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: 209 return file_browser_private:: 210 MOUNT_COMPLETED_STATUS_ERROR_INSUFFICIENT_PERMISSIONS; 211 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: 212 return file_browser_private:: 213 MOUNT_COMPLETED_STATUS_ERROR_MOUNT_PROGRAM_NOT_FOUND; 214 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: 215 return file_browser_private:: 216 MOUNT_COMPLETED_STATUS_ERROR_MOUNT_PROGRAM_FAILED; 217 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: 218 return file_browser_private:: 219 MOUNT_COMPLETED_STATUS_ERROR_INVALID_DEVICE_PATH; 220 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: 221 return file_browser_private:: 222 MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN_FILESYSTEM; 223 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: 224 return file_browser_private:: 225 MOUNT_COMPLETED_STATUS_ERROR_UNSUPORTED_FILESYSTEM; 226 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: 227 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_INVALID_ARCHIVE; 228 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: 229 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_AUTHENTICATION; 230 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: 231 return file_browser_private::MOUNT_COMPLETED_STATUS_ERROR_PATH_UNMOUNTED; 232 } 233 NOTREACHED(); 234 return file_browser_private::MOUNT_COMPLETED_STATUS_NONE; 235 } 236 237 void BroadcastMountCompletedEvent( 238 Profile* profile, 239 file_browser_private::MountCompletedEventType event_type, 240 chromeos::MountError error, 241 const VolumeInfo& volume_info, 242 bool is_remounting) { 243 file_browser_private::MountCompletedEvent event; 244 event.event_type = event_type; 245 event.status = MountErrorToMountCompletedStatus(error); 246 util::VolumeInfoToVolumeMetadata( 247 profile, volume_info, &event.volume_metadata); 248 event.is_remounting = is_remounting; 249 250 BroadcastEvent( 251 profile, 252 file_browser_private::OnMountCompleted::kEventName, 253 file_browser_private::OnMountCompleted::Create(event)); 254 } 255 256 file_browser_private::CopyProgressStatusType 257 CopyProgressTypeToCopyProgressStatusType( 258 fileapi::FileSystemOperation::CopyProgressType type) { 259 switch (type) { 260 case fileapi::FileSystemOperation::BEGIN_COPY_ENTRY: 261 return file_browser_private::COPY_PROGRESS_STATUS_TYPE_BEGIN_COPY_ENTRY; 262 case fileapi::FileSystemOperation::END_COPY_ENTRY: 263 return file_browser_private::COPY_PROGRESS_STATUS_TYPE_END_COPY_ENTRY; 264 case fileapi::FileSystemOperation::PROGRESS: 265 return file_browser_private::COPY_PROGRESS_STATUS_TYPE_PROGRESS; 266 } 267 NOTREACHED(); 268 return file_browser_private::COPY_PROGRESS_STATUS_TYPE_NONE; 269 } 270 271 std::string FileErrorToErrorName(base::File::Error error_code) { 272 namespace js = extensions::api::file_browser_private; 273 switch (error_code) { 274 case base::File::FILE_ERROR_NOT_FOUND: 275 return "NotFoundError"; 276 case base::File::FILE_ERROR_INVALID_OPERATION: 277 case base::File::FILE_ERROR_EXISTS: 278 case base::File::FILE_ERROR_NOT_EMPTY: 279 return "InvalidModificationError"; 280 case base::File::FILE_ERROR_NOT_A_DIRECTORY: 281 case base::File::FILE_ERROR_NOT_A_FILE: 282 return "TypeMismatchError"; 283 case base::File::FILE_ERROR_ACCESS_DENIED: 284 return "NoModificationAllowedError"; 285 case base::File::FILE_ERROR_FAILED: 286 return "InvalidStateError"; 287 case base::File::FILE_ERROR_ABORT: 288 return "AbortError"; 289 case base::File::FILE_ERROR_SECURITY: 290 return "SecurityError"; 291 case base::File::FILE_ERROR_NO_SPACE: 292 return "QuotaExceededError"; 293 case base::File::FILE_ERROR_INVALID_URL: 294 return "EncodingError"; 295 default: 296 return "InvalidModificationError"; 297 } 298 } 299 300 void GrantAccessForAddedProfileToRunningInstance(Profile* added_profile, 301 Profile* running_profile) { 302 extensions::ProcessManager* const process_manager = 303 extensions::ExtensionSystem::Get(running_profile)->process_manager(); 304 if (!process_manager) 305 return; 306 307 extensions::ExtensionHost* const extension_host = 308 process_manager->GetBackgroundHostForExtension(kFileManagerAppId); 309 if (!extension_host || !extension_host->render_process_host()) 310 return; 311 312 const int id = extension_host->render_process_host()->GetID(); 313 file_manager::util::SetupProfileFileAccessPermissions(id, added_profile); 314 } 315 316 // Checks if we should send a progress event or not according to the 317 // |last_time| of sending an event. If |always| is true, the function always 318 // returns true. If the function returns true, the function also updates 319 // |last_time|. 320 bool ShouldSendProgressEvent(bool always, base::Time* last_time) { 321 const base::Time now = base::Time::Now(); 322 const int64 delta = (now - *last_time).InMilliseconds(); 323 // delta < 0 may rarely happen if system clock is synced and rewinded. 324 // To be conservative, we don't skip in that case. 325 if (!always && 0 <= delta && delta < kProgressEventFrequencyInMilliseconds) { 326 return false; 327 } else { 328 *last_time = now; 329 return true; 330 } 331 } 332 333 } // namespace 334 335 // Pass dummy value to JobInfo's constructor for make it default constructible. 336 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() 337 : job_info(drive::TYPE_DOWNLOAD_FILE) { 338 } 339 340 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( 341 const drive::JobInfo& info, const std::string& status) 342 : job_info(info), status(status) { 343 } 344 345 EventRouter::EventRouter(Profile* profile) 346 : pref_change_registrar_(new PrefChangeRegistrar), 347 profile_(profile), 348 multi_user_window_manager_observer_registered_(false), 349 weak_factory_(this) { 350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 351 } 352 353 EventRouter::~EventRouter() { 354 } 355 356 void EventRouter::Shutdown() { 357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 358 359 DLOG_IF(WARNING, !file_watchers_.empty()) 360 << "Not all file watchers are " 361 << "removed. This can happen when Files.app is open during shutdown."; 362 STLDeleteValues(&file_watchers_); 363 if (!profile_) { 364 NOTREACHED(); 365 return; 366 } 367 368 pref_change_registrar_->RemoveAll(); 369 370 if (NetworkHandler::IsInitialized()) { 371 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, 372 FROM_HERE); 373 } 374 375 DriveIntegrationService* integration_service = 376 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( 377 profile_); 378 if (integration_service) { 379 integration_service->file_system()->RemoveObserver(this); 380 integration_service->drive_service()->RemoveObserver(this); 381 integration_service->job_list()->RemoveObserver(this); 382 } 383 384 VolumeManager* volume_manager = VolumeManager::Get(profile_); 385 if (volume_manager) 386 volume_manager->RemoveObserver(this); 387 388 chrome::MultiUserWindowManager* const multi_user_window_manager = 389 chrome::MultiUserWindowManager::GetInstance(); 390 if (multi_user_window_manager && 391 multi_user_window_manager_observer_registered_) { 392 multi_user_window_manager_observer_registered_ = false; 393 multi_user_window_manager->RemoveObserver(this); 394 } 395 396 profile_ = NULL; 397 } 398 399 void EventRouter::ObserveEvents() { 400 if (!profile_) { 401 NOTREACHED(); 402 return; 403 } 404 if (!chromeos::LoginState::IsInitialized() || 405 !chromeos::LoginState::Get()->IsUserLoggedIn()) { 406 return; 407 } 408 409 // VolumeManager's construction triggers DriveIntegrationService's 410 // construction, so it is necessary to call VolumeManager's Get before 411 // accessing DriveIntegrationService. 412 VolumeManager* volume_manager = VolumeManager::Get(profile_); 413 if (volume_manager) 414 volume_manager->AddObserver(this); 415 416 DriveIntegrationService* integration_service = 417 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( 418 profile_); 419 if (integration_service) { 420 integration_service->drive_service()->AddObserver(this); 421 integration_service->file_system()->AddObserver(this); 422 integration_service->job_list()->AddObserver(this); 423 } 424 425 if (NetworkHandler::IsInitialized()) { 426 NetworkHandler::Get()->network_state_handler()->AddObserver(this, 427 FROM_HERE); 428 } 429 430 pref_change_registrar_->Init(profile_->GetPrefs()); 431 base::Closure callback = 432 base::Bind(&EventRouter::OnFileManagerPrefsChanged, 433 weak_factory_.GetWeakPtr()); 434 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); 435 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); 436 pref_change_registrar_->Add(prefs::kDisableDrive, callback); 437 pref_change_registrar_->Add(prefs::kUse24HourClock, callback); 438 439 notification_registrar_.Add(this, 440 chrome::NOTIFICATION_PROFILE_ADDED, 441 content::NotificationService::AllSources()); 442 } 443 444 // File watch setup routines. 445 void EventRouter::AddFileWatch(const base::FilePath& local_path, 446 const base::FilePath& virtual_path, 447 const std::string& extension_id, 448 const BoolCallback& callback) { 449 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 450 DCHECK(!callback.is_null()); 451 452 base::FilePath watch_path = local_path; 453 bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path); 454 // Tweak watch path for remote sources - we need to drop leading /special 455 // directory from there in order to be able to pair these events with 456 // their change notifications. 457 if (is_on_drive) 458 watch_path = drive::util::ExtractDrivePath(watch_path); 459 460 WatcherMap::iterator iter = file_watchers_.find(watch_path); 461 if (iter == file_watchers_.end()) { 462 scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path)); 463 watcher->AddExtension(extension_id); 464 465 if (is_on_drive) { 466 // For Drive, file watching is done via OnDirectoryChanged(). 467 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 468 base::Bind(callback, true)); 469 } else { 470 // For local files, start watching using FileWatcher. 471 watcher->WatchLocalFile( 472 watch_path, 473 base::Bind(&EventRouter::HandleFileWatchNotification, 474 weak_factory_.GetWeakPtr()), 475 callback); 476 } 477 478 file_watchers_[watch_path] = watcher.release(); 479 } else { 480 iter->second->AddExtension(extension_id); 481 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 482 base::Bind(callback, true)); 483 } 484 } 485 486 void EventRouter::RemoveFileWatch(const base::FilePath& local_path, 487 const std::string& extension_id) { 488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 489 490 base::FilePath watch_path = local_path; 491 // Tweak watch path for remote sources - we need to drop leading /special 492 // directory from there in order to be able to pair these events with 493 // their change notifications. 494 if (drive::util::IsUnderDriveMountPoint(watch_path)) { 495 watch_path = drive::util::ExtractDrivePath(watch_path); 496 } 497 WatcherMap::iterator iter = file_watchers_.find(watch_path); 498 if (iter == file_watchers_.end()) 499 return; 500 // Remove the watcher if |watch_path| is no longer watched by any extensions. 501 iter->second->RemoveExtension(extension_id); 502 if (iter->second->GetExtensionIds().empty()) { 503 delete iter->second; 504 file_watchers_.erase(iter); 505 } 506 } 507 508 void EventRouter::OnCopyCompleted(int copy_id, 509 const GURL& source_url, 510 const GURL& destination_url, 511 base::File::Error error) { 512 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 513 514 file_browser_private::CopyProgressStatus status; 515 if (error == base::File::FILE_OK) { 516 // Send success event. 517 status.type = file_browser_private::COPY_PROGRESS_STATUS_TYPE_SUCCESS; 518 status.source_url.reset(new std::string(source_url.spec())); 519 status.destination_url.reset(new std::string(destination_url.spec())); 520 } else { 521 // Send error event. 522 status.type = file_browser_private::COPY_PROGRESS_STATUS_TYPE_ERROR; 523 status.error.reset(new std::string(FileErrorToErrorName(error))); 524 } 525 526 BroadcastEvent( 527 profile_, 528 file_browser_private::OnCopyProgress::kEventName, 529 file_browser_private::OnCopyProgress::Create(copy_id, status)); 530 } 531 532 void EventRouter::OnCopyProgress( 533 int copy_id, 534 fileapi::FileSystemOperation::CopyProgressType type, 535 const GURL& source_url, 536 const GURL& destination_url, 537 int64 size) { 538 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 539 540 file_browser_private::CopyProgressStatus status; 541 status.type = CopyProgressTypeToCopyProgressStatusType(type); 542 status.source_url.reset(new std::string(source_url.spec())); 543 if (type == fileapi::FileSystemOperation::END_COPY_ENTRY) 544 status.destination_url.reset(new std::string(destination_url.spec())); 545 if (type == fileapi::FileSystemOperation::PROGRESS) 546 status.size.reset(new double(size)); 547 548 // Should not skip events other than TYPE_PROGRESS. 549 const bool always = 550 status.type != file_browser_private::COPY_PROGRESS_STATUS_TYPE_PROGRESS; 551 if (!ShouldSendProgressEvent(always, &last_copy_progress_event_)) 552 return; 553 554 BroadcastEvent( 555 profile_, 556 file_browser_private::OnCopyProgress::kEventName, 557 file_browser_private::OnCopyProgress::Create(copy_id, status)); 558 } 559 560 void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) { 561 if (!profile_ || !extensions::EventRouter::Get(profile_)) { 562 NOTREACHED(); 563 return; 564 } 565 566 BroadcastEvent( 567 profile_, 568 file_browser_private::OnDriveConnectionStatusChanged::kEventName, 569 file_browser_private::OnDriveConnectionStatusChanged::Create()); 570 } 571 572 void EventRouter::OnFileManagerPrefsChanged() { 573 if (!profile_ || !extensions::EventRouter::Get(profile_)) { 574 NOTREACHED(); 575 return; 576 } 577 578 BroadcastEvent( 579 profile_, 580 file_browser_private::OnPreferencesChanged::kEventName, 581 file_browser_private::OnPreferencesChanged::Create()); 582 } 583 584 void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { 585 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 586 OnJobUpdated(job_info); 587 } 588 589 void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) { 590 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 591 if (!drive::IsActiveFileTransferJobInfo(job_info)) 592 return; 593 594 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); 595 596 // Replace with the latest job info. 597 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 598 job_info, 599 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress); 600 601 // Fire event if needed. 602 bool always = is_new_job; 603 SendDriveFileTransferEvent(always); 604 } 605 606 void EventRouter::OnJobDone(const drive::JobInfo& job_info, 607 drive::FileError error) { 608 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 609 if (!drive::IsActiveFileTransferJobInfo(job_info)) 610 return; 611 612 // Replace with the latest job info. 613 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 614 job_info, 615 error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted 616 : kFileTransferStateFailed); 617 618 // Fire event if needed. 619 bool always = true; 620 SendDriveFileTransferEvent(always); 621 622 // Forget about the job. 623 drive_jobs_.erase(job_info.job_id); 624 } 625 626 void EventRouter::SendDriveFileTransferEvent(bool always) { 627 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 628 629 // When |always| flag is not set, we don't send the event until certain 630 // amount of time passes after the previous one. This is to avoid 631 // flooding the IPC between extensions by many onFileTransferUpdated events. 632 if (!ShouldSendProgressEvent(always, &last_file_transfer_event_)) 633 return; 634 635 // Convert the current |drive_jobs_| to IDL type. 636 std::vector<linked_ptr<file_browser_private::FileTransferStatus> > 637 status_list; 638 for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator 639 iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) { 640 linked_ptr<file_browser_private::FileTransferStatus> status( 641 new file_browser_private::FileTransferStatus()); 642 JobInfoToTransferStatus(profile_, 643 kFileManagerAppId, 644 iter->second.status, 645 iter->second.job_info, 646 status.get()); 647 status_list.push_back(status); 648 } 649 BroadcastEvent( 650 profile_, 651 file_browser_private::OnFileTransfersUpdated::kEventName, 652 file_browser_private::OnFileTransfersUpdated::Create(status_list)); 653 } 654 655 void EventRouter::OnDirectoryChanged(const base::FilePath& drive_path) { 656 HandleFileWatchNotification(drive_path, false); 657 } 658 659 void EventRouter::OnDriveSyncError(drive::file_system::DriveSyncErrorType type, 660 const base::FilePath& drive_path) { 661 file_browser_private::DriveSyncErrorEvent event; 662 switch (type) { 663 case drive::file_system::DRIVE_SYNC_ERROR_DELETE_WITHOUT_PERMISSION: 664 event.type = 665 file_browser_private::DRIVE_SYNC_ERROR_TYPE_DELETE_WITHOUT_PERMISSION; 666 break; 667 case drive::file_system::DRIVE_SYNC_ERROR_SERVICE_UNAVAILABLE: 668 event.type = 669 file_browser_private::DRIVE_SYNC_ERROR_TYPE_SERVICE_UNAVAILABLE; 670 break; 671 case drive::file_system::DRIVE_SYNC_ERROR_MISC: 672 event.type = 673 file_browser_private::DRIVE_SYNC_ERROR_TYPE_MISC; 674 break; 675 } 676 event.file_url = util::ConvertDrivePathToFileSystemUrl( 677 profile_, drive_path, kFileManagerAppId).spec(); 678 BroadcastEvent( 679 profile_, 680 file_browser_private::OnDriveSyncError::kEventName, 681 file_browser_private::OnDriveSyncError::Create(event)); 682 } 683 684 void EventRouter::OnRefreshTokenInvalid() { 685 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 686 687 // Raise a DriveConnectionStatusChanged event to notify the status offline. 688 BroadcastEvent( 689 profile_, 690 file_browser_private::OnDriveConnectionStatusChanged::kEventName, 691 file_browser_private::OnDriveConnectionStatusChanged::Create()); 692 } 693 694 void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path, 695 bool got_error) { 696 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 697 698 WatcherMap::const_iterator iter = file_watchers_.find(local_path); 699 if (iter == file_watchers_.end()) { 700 return; 701 } 702 DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error, 703 iter->second->GetExtensionIds()); 704 } 705 706 void EventRouter::DispatchDirectoryChangeEvent( 707 const base::FilePath& virtual_path, 708 bool got_error, 709 const std::vector<std::string>& extension_ids) { 710 if (!profile_) { 711 NOTREACHED(); 712 return; 713 } 714 715 for (size_t i = 0; i < extension_ids.size(); ++i) { 716 const std::string& extension_id = extension_ids[i]; 717 718 FileDefinition file_definition; 719 file_definition.virtual_path = virtual_path; 720 file_definition.is_directory = true; 721 722 file_manager::util::ConvertFileDefinitionToEntryDefinition( 723 profile_, 724 extension_id, 725 file_definition, 726 base::Bind( 727 &EventRouter::DispatchDirectoryChangeEventWithEntryDefinition, 728 weak_factory_.GetWeakPtr(), 729 got_error)); 730 } 731 } 732 733 void EventRouter::DispatchDirectoryChangeEventWithEntryDefinition( 734 bool watcher_error, 735 const EntryDefinition& entry_definition) { 736 if (entry_definition.error != base::File::FILE_OK || 737 !entry_definition.is_directory) { 738 DVLOG(1) << "Unable to dispatch event because resolving the directory " 739 << "entry definition failed."; 740 return; 741 } 742 743 file_browser_private::FileWatchEvent event; 744 event.event_type = watcher_error 745 ? file_browser_private::FILE_WATCH_EVENT_TYPE_ERROR 746 : file_browser_private::FILE_WATCH_EVENT_TYPE_CHANGED; 747 748 event.entry.additional_properties.SetString( 749 "fileSystemName", entry_definition.file_system_name); 750 event.entry.additional_properties.SetString( 751 "fileSystemRoot", entry_definition.file_system_root_url); 752 event.entry.additional_properties.SetString( 753 "fileFullPath", "/" + entry_definition.full_path.value()); 754 event.entry.additional_properties.SetBoolean("fileIsDirectory", 755 entry_definition.is_directory); 756 757 BroadcastEvent(profile_, 758 file_browser_private::OnDirectoryChanged::kEventName, 759 file_browser_private::OnDirectoryChanged::Create(event)); 760 } 761 762 void EventRouter::ShowRemovableDeviceInFileManager( 763 VolumeType type, 764 const base::FilePath& mount_path) { 765 // Do not attempt to open File Manager while the login is in progress or 766 // the screen is locked or running in kiosk app mode and make sure the file 767 // manager is opened only for the active user. 768 if (chromeos::LoginDisplayHostImpl::default_host() || 769 chromeos::ScreenLocker::default_screen_locker() || 770 chrome::IsRunningInForcedAppMode() || 771 profile_ != ProfileManager::GetActiveUserProfile()) 772 return; 773 774 // Do not pop-up the File Manager, if the recovery tool is running. 775 if (IsRecoveryToolRunning(profile_)) 776 return; 777 778 // Do not pop-up the File Manager, if Google+ Photos may be launched. 779 if (IsGooglePhotosInstalled(profile_)) { 780 // MTP device is handled by Photos app. 781 if (type == VOLUME_TYPE_MTP) 782 return; 783 // According to DCF (Design rule of Camera File system) by JEITA / CP-3461 784 // cameras should have pictures located in the DCIM root directory. 785 // Such removable disks are handled by Photos app. 786 if (type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) { 787 DirectoryExistsOnUIThread( 788 mount_path.AppendASCII("DCIM"), 789 base::Bind(&base::DoNothing), 790 base::Bind(&util::OpenRemovableDrive, profile_, mount_path)); 791 return; 792 } 793 } 794 795 util::OpenRemovableDrive(profile_, mount_path); 796 } 797 798 void EventRouter::DispatchDeviceEvent( 799 file_browser_private::DeviceEventType type, 800 const std::string& device_path) { 801 file_browser_private::DeviceEvent event; 802 event.type = type; 803 event.device_path = device_path; 804 BroadcastEvent(profile_, 805 file_browser_private::OnDeviceChanged::kEventName, 806 file_browser_private::OnDeviceChanged::Create(event)); 807 } 808 809 void EventRouter::OnDiskAdded( 810 const DiskMountManager::Disk& disk, bool mounting) { 811 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 812 813 if (!mounting) { 814 // If the disk is not being mounted, we don't want the Scanning 815 // notification to persist. 816 DispatchDeviceEvent( 817 file_browser_private::DEVICE_EVENT_TYPE_SCAN_CANCELED, 818 disk.system_path_prefix()); 819 } 820 } 821 822 void EventRouter::OnDiskRemoved(const DiskMountManager::Disk& disk) { 823 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 824 // Do nothing. 825 } 826 827 void EventRouter::OnDeviceAdded(const std::string& device_path) { 828 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 829 830 // If the policy is set instead of showing the new device notification, 831 // we show a notification that the operation is not permitted. 832 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { 833 DispatchDeviceEvent( 834 file_browser_private::DEVICE_EVENT_TYPE_DISABLED, 835 device_path); 836 return; 837 } 838 839 DispatchDeviceEvent( 840 file_browser_private::DEVICE_EVENT_TYPE_ADDED, 841 device_path); 842 } 843 844 void EventRouter::OnDeviceRemoved(const std::string& device_path, 845 bool hard_unplugged) { 846 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 847 848 DispatchDeviceEvent( 849 file_browser_private::DEVICE_EVENT_TYPE_REMOVED, 850 device_path); 851 852 if (hard_unplugged) { 853 DispatchDeviceEvent(file_browser_private::DEVICE_EVENT_TYPE_HARD_UNPLUGGED, 854 device_path); 855 } 856 } 857 858 void EventRouter::OnVolumeMounted(chromeos::MountError error_code, 859 const VolumeInfo& volume_info, 860 bool is_remounting) { 861 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 862 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can 863 // happen at shutdown. This should be removed after removing Drive mounting 864 // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is 865 // the only path to come here after Shutdown is called). 866 if (!profile_) 867 return; 868 869 BroadcastMountCompletedEvent( 870 profile_, 871 file_browser_private::MOUNT_COMPLETED_EVENT_TYPE_MOUNT, 872 error_code, 873 volume_info, 874 is_remounting); 875 876 if ((volume_info.type == VOLUME_TYPE_MTP || 877 volume_info.type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) && 878 !is_remounting) { 879 // If a new device was mounted, a new File manager window may need to be 880 // opened. 881 if (error_code == chromeos::MOUNT_ERROR_NONE) 882 ShowRemovableDeviceInFileManager(volume_info.type, 883 volume_info.mount_path); 884 } 885 } 886 887 void EventRouter::OnVolumeUnmounted(chromeos::MountError error_code, 888 const VolumeInfo& volume_info) { 889 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 890 BroadcastMountCompletedEvent( 891 profile_, 892 file_browser_private::MOUNT_COMPLETED_EVENT_TYPE_UNMOUNT, 893 error_code, 894 volume_info, 895 false); 896 } 897 898 void EventRouter::OnFormatStarted(const std::string& device_path, 899 bool success) { 900 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 901 902 if (success) { 903 DispatchDeviceEvent(file_browser_private::DEVICE_EVENT_TYPE_FORMAT_START, 904 device_path); 905 } else { 906 DispatchDeviceEvent(file_browser_private::DEVICE_EVENT_TYPE_FORMAT_FAIL, 907 device_path); 908 } 909 } 910 911 void EventRouter::OnFormatCompleted(const std::string& device_path, 912 bool success) { 913 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 914 DispatchDeviceEvent(success ? 915 file_browser_private::DEVICE_EVENT_TYPE_FORMAT_SUCCESS : 916 file_browser_private::DEVICE_EVENT_TYPE_FORMAT_FAIL, 917 device_path); 918 } 919 920 void EventRouter::Observe(int type, 921 const content::NotificationSource& source, 922 const content::NotificationDetails& details) { 923 if (type == chrome::NOTIFICATION_PROFILE_ADDED) { 924 Profile* const added_profile = content::Source<Profile>(source).ptr(); 925 if (!added_profile->IsOffTheRecord()) 926 GrantAccessForAddedProfileToRunningInstance(added_profile, profile_); 927 928 BroadcastEvent(profile_, 929 file_browser_private::OnProfileAdded::kEventName, 930 file_browser_private::OnProfileAdded::Create()); 931 } 932 } 933 934 void EventRouter::RegisterMultiUserWindowManagerObserver() { 935 if (multi_user_window_manager_observer_registered_) 936 return; 937 chrome::MultiUserWindowManager* const multi_user_window_manager = 938 chrome::MultiUserWindowManager::GetInstance(); 939 if (multi_user_window_manager) { 940 multi_user_window_manager->AddObserver(this); 941 multi_user_window_manager_observer_registered_ = true; 942 } 943 } 944 945 void EventRouter::OnOwnerEntryChanged(aura::Window* window) { 946 BroadcastEvent(profile_, 947 file_browser_private::OnDesktopChanged::kEventName, 948 file_browser_private::OnDesktopChanged::Create()); 949 } 950 951 } // namespace file_manager 952