event_router.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
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/chrome_notification_types.h"
16#include "chrome/browser/chromeos/drive/drive_integration_service.h"
17#include "chrome/browser/chromeos/drive/file_system_interface.h"
18#include "chrome/browser/chromeos/drive/file_system_util.h"
19#include "chrome/browser/chromeos/extensions/file_manager/desktop_notifications.h"
20#include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
21#include "chrome/browser/chromeos/extensions/file_manager/mounted_disk_monitor.h"
22#include "chrome/browser/chromeos/login/login_display_host_impl.h"
23#include "chrome/browser/chromeos/login/screen_locker.h"
24#include "chrome/browser/chromeos/net/connectivity_state_helper.h"
25#include "chrome/browser/drive/drive_service_interface.h"
26#include "chrome/browser/extensions/event_names.h"
27#include "chrome/browser/extensions/event_router.h"
28#include "chrome/browser/extensions/extension_service.h"
29#include "chrome/browser/extensions/extension_system.h"
30#include "chrome/browser/profiles/profile.h"
31#include "chrome/common/pref_names.h"
32#include "chromeos/login/login_state.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/notification_source.h"
35#include "webkit/common/fileapi/file_system_types.h"
36#include "webkit/common/fileapi/file_system_util.h"
37
38using chromeos::disks::DiskMountManager;
39using content::BrowserThread;
40using drive::DriveIntegrationService;
41using drive::DriveIntegrationServiceFactory;
42
43namespace file_manager {
44namespace {
45
46const char kPathChanged[] = "changed";
47const char kPathWatchError[] = "error";
48
49// Used as a callback for FileSystem::MarkCacheFileAsUnmounted().
50void OnMarkAsUnmounted(drive::FileError error) {
51  // Do nothing.
52}
53
54const char* MountErrorToString(chromeos::MountError error) {
55  switch (error) {
56    case chromeos::MOUNT_ERROR_NONE:
57      return "success";
58    case chromeos::MOUNT_ERROR_UNKNOWN:
59      return "error_unknown";
60    case chromeos::MOUNT_ERROR_INTERNAL:
61      return "error_internal";
62    case chromeos::MOUNT_ERROR_INVALID_ARGUMENT:
63      return "error_invalid_argument";
64    case chromeos::MOUNT_ERROR_INVALID_PATH:
65      return "error_invalid_path";
66    case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED:
67      return "error_path_already_mounted";
68    case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED:
69      return "error_path_not_mounted";
70    case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED:
71      return "error_directory_creation_failed";
72    case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS:
73      return "error_invalid_mount_options";
74    case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS:
75      return "error_invalid_unmount_options";
76    case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS:
77      return "error_insufficient_permissions";
78    case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND:
79      return "error_mount_program_not_found";
80    case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED:
81      return "error_mount_program_failed";
82    case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH:
83      return "error_invalid_device_path";
84    case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM:
85      return "error_unknown_filesystem";
86    case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM:
87      return "error_unsuported_filesystem";
88    case chromeos::MOUNT_ERROR_INVALID_ARCHIVE:
89      return "error_invalid_archive";
90    case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED:
91      return "error_authentication";
92    case chromeos::MOUNT_ERROR_PATH_UNMOUNTED:
93      return "error_path_unmounted";
94  }
95  NOTREACHED();
96  return "";
97}
98
99void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path,
100                                   const base::Closure& success_callback,
101                                   const base::Closure& failure_callback) {
102  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
103
104  if (base::DirectoryExists(directory_path))
105    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback);
106  else
107    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback);
108};
109
110void DirectoryExistsOnUIThread(const base::FilePath& directory_path,
111                               const base::Closure& success_callback,
112                               const base::Closure& failure_callback) {
113  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
114
115  content::BrowserThread::PostBlockingPoolTask(
116      FROM_HERE,
117      base::Bind(&DirectoryExistsOnBlockingPool,
118                 directory_path,
119                 success_callback,
120                 failure_callback));
121};
122
123// Creates a base::FilePathWatcher and starts watching at |watch_path| with
124// |callback|. Returns NULL on failure.
125base::FilePathWatcher* CreateAndStartFilePathWatcher(
126    const base::FilePath& watch_path,
127    const base::FilePathWatcher::Callback& callback) {
128  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
129  DCHECK(!callback.is_null());
130
131  base::FilePathWatcher* watcher(new base::FilePathWatcher);
132  if (!watcher->Watch(watch_path, false /* recursive */, callback)) {
133    delete watcher;
134    return NULL;
135  }
136
137  return watcher;
138}
139
140// Constants for the "transferState" field of onFileTransferUpdated event.
141const char kFileTransferStateStarted[] = "started";
142const char kFileTransferStateInProgress[] = "in_progress";
143const char kFileTransferStateCompleted[] = "completed";
144const char kFileTransferStateFailed[] = "failed";
145
146// Frequency of sending onFileTransferUpdated.
147const int64 kFileTransferEventFrequencyInMilliseconds = 1000;
148
149// Utility function to check if |job_info| is a file uploading job.
150bool IsUploadJob(drive::JobType type) {
151  return (type == drive::TYPE_UPLOAD_NEW_FILE ||
152          type == drive::TYPE_UPLOAD_EXISTING_FILE);
153}
154
155// Utility function to check if |job_info| is a file downloading job.
156bool IsDownloadJob(drive::JobType type) {
157  return type == drive::TYPE_DOWNLOAD_FILE;
158}
159
160// Converts the job info to its JSON (Value) form.
161scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue(
162    const std::string& extension_id,
163    const std::string& job_status,
164    const drive::JobInfo& job_info) {
165  DCHECK(IsActiveFileTransferJobInfo(job_info));
166
167  scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
168  GURL url = util::ConvertRelativePathToFileSystemUrl(
169      job_info.file_path, extension_id);
170  result->SetString("fileUrl", url.spec());
171  result->SetString("transferState", job_status);
172  result->SetString("transferType",
173                    IsUploadJob(job_info.job_type) ? "upload" : "download");
174  // JavaScript does not have 64-bit integers. Instead we use double, which
175  // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice
176  // in C++. Larger values are rounded.
177  result->SetDouble("processed",
178                    static_cast<double>(job_info.num_completed_bytes));
179  result->SetDouble("total", static_cast<double>(job_info.num_total_bytes));
180  return result.Pass();
181}
182
183// Checks for availability of the Google+ Photos app.
184bool IsGooglePhotosInstalled(Profile *profile) {
185  ExtensionService* service =
186      extensions::ExtensionSystem::Get(profile)->extension_service();
187  if (!service)
188    return false;
189
190  // Google+ Photos uses several ids for different channels. Therefore, all of
191  // them should be checked.
192  const std::string kGooglePlusPhotosIds[] = {
193    "ebpbnabdhheoknfklmpddcdijjkmklkp",  // G+ Photos staging
194    "efjnaogkjbogokcnohkmnjdojkikgobo",  // G+ Photos prod
195    "ejegoaikibpmikoejfephaneibodccma"   // G+ Photos dev
196  };
197
198  for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) {
199    if (service->GetExtensionById(kGooglePlusPhotosIds[i],
200                                  false /* include_disable */) != NULL)
201      return true;
202  }
203
204  return false;
205}
206
207}  // namespace
208
209// Pass dummy value to JobInfo's constructor for make it default constructible.
210EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus()
211    : job_info(drive::TYPE_DOWNLOAD_FILE) {
212}
213
214EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus(
215    const drive::JobInfo& info, const std::string& status)
216    : job_info(info), status(status) {
217}
218
219EventRouter::EventRouter(
220    Profile* profile)
221    : notifications_(new DesktopNotifications(profile)),
222      pref_change_registrar_(new PrefChangeRegistrar),
223      profile_(profile),
224      weak_factory_(this) {
225  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226}
227
228EventRouter::~EventRouter() {
229}
230
231void EventRouter::Shutdown() {
232  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233
234  DLOG_IF(WARNING, !file_watchers_.empty())
235      << "Not all file watchers are "
236      << "removed. This can happen when Files.app is open during shutdown.";
237  STLDeleteValues(&file_watchers_);
238  if (!profile_) {
239    NOTREACHED();
240    return;
241  }
242
243  DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
244  if (disk_mount_manager)
245    disk_mount_manager->RemoveObserver(this);
246
247  DriveIntegrationService* integration_service =
248      DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates(
249          profile_);
250  if (integration_service) {
251    integration_service->RemoveObserver(this);
252    integration_service->file_system()->RemoveObserver(this);
253    integration_service->drive_service()->RemoveObserver(this);
254    integration_service->job_list()->RemoveObserver(this);
255  }
256
257  if (chromeos::ConnectivityStateHelper::IsInitialized()) {
258    chromeos::ConnectivityStateHelper::Get()->
259        RemoveNetworkManagerObserver(this);
260  }
261  profile_ = NULL;
262}
263
264void EventRouter::ObserveFileSystemEvents() {
265  if (!profile_) {
266    NOTREACHED();
267    return;
268  }
269  if (!chromeos::LoginState::IsInitialized() ||
270      !chromeos::LoginState::Get()->IsUserLoggedIn()) {
271    return;
272  }
273
274  DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
275  if (disk_mount_manager) {
276    disk_mount_manager->RemoveObserver(this);
277    disk_mount_manager->AddObserver(this);
278    disk_mount_manager->RequestMountInfoRefresh();
279  }
280
281  DriveIntegrationService* integration_service =
282      DriveIntegrationServiceFactory::GetForProfileRegardlessOfStates(
283          profile_);
284  if (integration_service) {
285    integration_service->AddObserver(this);
286    integration_service->drive_service()->AddObserver(this);
287    integration_service->file_system()->AddObserver(this);
288    integration_service->job_list()->AddObserver(this);
289  }
290
291  if (chromeos::ConnectivityStateHelper::IsInitialized()) {
292    chromeos::ConnectivityStateHelper::Get()->
293        AddNetworkManagerObserver(this);
294  }
295
296  mounted_disk_monitor_.reset(new MountedDiskMonitor());
297
298  pref_change_registrar_->Init(profile_->GetPrefs());
299
300  pref_change_registrar_->Add(
301      prefs::kExternalStorageDisabled,
302      base::Bind(&EventRouter::OnExternalStorageDisabledChanged,
303                 weak_factory_.GetWeakPtr()));
304
305  base::Closure callback =
306      base::Bind(&EventRouter::OnFileManagerPrefsChanged,
307                 weak_factory_.GetWeakPtr());
308  pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback);
309  pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback);
310  pref_change_registrar_->Add(prefs::kDisableDrive, callback);
311  pref_change_registrar_->Add(prefs::kUse24HourClock, callback);
312}
313
314// File watch setup routines.
315void EventRouter::AddFileWatch(const base::FilePath& local_path,
316                               const base::FilePath& virtual_path,
317                               const std::string& extension_id,
318                               const BoolCallback& callback) {
319  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320  DCHECK(!callback.is_null());
321
322  base::FilePath watch_path = local_path;
323  bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path);
324  // Tweak watch path for remote sources - we need to drop leading /special
325  // directory from there in order to be able to pair these events with
326  // their change notifications.
327  if (is_on_drive)
328    watch_path = drive::util::ExtractDrivePath(watch_path);
329
330  WatcherMap::iterator iter = file_watchers_.find(watch_path);
331  if (iter == file_watchers_.end()) {
332    scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path));
333    watcher->AddExtension(extension_id);
334
335    if (is_on_drive) {
336      // For Drive, file watching is done via OnDirectoryChanged().
337      base::MessageLoopProxy::current()->PostTask(FROM_HERE,
338                                                  base::Bind(callback, true));
339    } else {
340      // For local files, start watching using FileWatcher.
341      watcher->WatchLocalFile(
342          watch_path,
343          base::Bind(&EventRouter::HandleFileWatchNotification,
344                     weak_factory_.GetWeakPtr()),
345          callback);
346    }
347
348    file_watchers_[watch_path] = watcher.release();
349  } else {
350    iter->second->AddExtension(extension_id);
351    base::MessageLoopProxy::current()->PostTask(FROM_HERE,
352                                                base::Bind(callback, true));
353  }
354}
355
356void EventRouter::RemoveFileWatch(const base::FilePath& local_path,
357                                  const std::string& extension_id) {
358  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
359
360  base::FilePath watch_path = local_path;
361  // Tweak watch path for remote sources - we need to drop leading /special
362  // directory from there in order to be able to pair these events with
363  // their change notifications.
364  if (drive::util::IsUnderDriveMountPoint(watch_path)) {
365    watch_path = drive::util::ExtractDrivePath(watch_path);
366  }
367  WatcherMap::iterator iter = file_watchers_.find(watch_path);
368  if (iter == file_watchers_.end())
369    return;
370  // Remove the watcher if |watch_path| is no longer watched by any extensions.
371  iter->second->RemoveExtension(extension_id);
372  if (iter->second->GetExtensionIds().empty()) {
373    delete iter->second;
374    file_watchers_.erase(iter);
375  }
376}
377
378void EventRouter::OnDiskEvent(DiskMountManager::DiskEvent event,
379                              const DiskMountManager::Disk* disk) {
380  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
381
382  // Disregard hidden devices.
383  if (disk->is_hidden())
384    return;
385  if (event == DiskMountManager::DISK_ADDED) {
386    OnDiskAdded(disk);
387  } else if (event == DiskMountManager::DISK_REMOVED) {
388    OnDiskRemoved(disk);
389  }
390}
391
392void EventRouter::OnDeviceEvent(DiskMountManager::DeviceEvent event,
393                                const std::string& device_path) {
394  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395
396  if (event == DiskMountManager::DEVICE_ADDED) {
397    OnDeviceAdded(device_path);
398  } else if (event == DiskMountManager::DEVICE_REMOVED) {
399    OnDeviceRemoved(device_path);
400  } else if (event == DiskMountManager::DEVICE_SCANNED) {
401    OnDeviceScanned(device_path);
402  }
403}
404
405void EventRouter::OnMountEvent(
406    DiskMountManager::MountEvent event,
407    chromeos::MountError error_code,
408    const DiskMountManager::MountPointInfo& mount_info) {
409  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
410  // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can
411  // happen at shutdown.
412  if (!profile_)
413    return;
414
415  DCHECK(mount_info.mount_type != chromeos::MOUNT_TYPE_INVALID);
416
417  DispatchMountEvent(event, error_code, mount_info);
418
419  if (mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE &&
420      event == DiskMountManager::MOUNTING) {
421    DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
422    const DiskMountManager::Disk* disk =
423        disk_mount_manager->FindDiskBySourcePath(mount_info.source_path);
424    if (!disk || mounted_disk_monitor_->DiskIsRemounting(*disk))
425      return;
426
427    notifications_->ManageNotificationsOnMountCompleted(
428        disk->system_path_prefix(), disk->drive_label(), disk->is_parent(),
429        error_code == chromeos::MOUNT_ERROR_NONE,
430        error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM);
431
432    // If a new device was mounted, a new File manager window may need to be
433    // opened.
434    if (error_code == chromeos::MOUNT_ERROR_NONE)
435      ShowRemovableDeviceInFileManager(
436          *disk,
437          base::FilePath::FromUTF8Unsafe(mount_info.mount_path));
438  } else if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
439    // Clear the "mounted" state for archive files in drive cache
440    // when mounting failed or unmounting succeeded.
441    if ((event == DiskMountManager::MOUNTING) !=
442        (error_code == chromeos::MOUNT_ERROR_NONE)) {
443      DriveIntegrationService* integration_service =
444          DriveIntegrationServiceFactory::GetForProfile(profile_);
445      drive::FileSystemInterface* file_system =
446          integration_service ? integration_service->file_system() : NULL;
447      if (file_system) {
448        file_system->MarkCacheFileAsUnmounted(
449            base::FilePath(mount_info.source_path),
450            base::Bind(&OnMarkAsUnmounted));
451      }
452    }
453  }
454}
455
456void EventRouter::OnFormatEvent(DiskMountManager::FormatEvent event,
457                                chromeos::FormatError error_code,
458                                const std::string& device_path) {
459  if (event == DiskMountManager::FORMAT_STARTED) {
460    OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE);
461  } else if (event == DiskMountManager::FORMAT_COMPLETED) {
462    OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE);
463  }
464}
465
466void EventRouter::NetworkManagerChanged() {
467  if (!profile_ ||
468      !extensions::ExtensionSystem::Get(profile_)->event_router()) {
469    NOTREACHED();
470    return;
471  }
472  scoped_ptr<extensions::Event> event(new extensions::Event(
473      extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
474      scoped_ptr<ListValue>(new ListValue())));
475  extensions::ExtensionSystem::Get(profile_)->event_router()->
476      BroadcastEvent(event.Pass());
477}
478
479void EventRouter::DefaultNetworkChanged() {
480  NetworkManagerChanged();
481}
482
483void EventRouter::OnExternalStorageDisabledChanged() {
484  // If the policy just got disabled we have to unmount every device currently
485  // mounted. The opposite is fine - we can let the user re-plug her device to
486  // make it available.
487  if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
488    DiskMountManager* manager = DiskMountManager::GetInstance();
489    DiskMountManager::MountPointMap mounts(manager->mount_points());
490    for (DiskMountManager::MountPointMap::const_iterator it = mounts.begin();
491         it != mounts.end(); ++it) {
492      LOG(INFO) << "Unmounting " << it->second.mount_path
493                << " because of policy.";
494      manager->UnmountPath(it->second.mount_path,
495                           chromeos::UNMOUNT_OPTIONS_NONE,
496                           DiskMountManager::UnmountPathCallback());
497    }
498  }
499}
500
501void EventRouter::OnFileManagerPrefsChanged() {
502  if (!profile_ ||
503      !extensions::ExtensionSystem::Get(profile_)->event_router()) {
504    NOTREACHED();
505    return;
506  }
507
508  scoped_ptr<extensions::Event> event(new extensions::Event(
509      extensions::event_names::kOnFileBrowserPreferencesChanged,
510      scoped_ptr<ListValue>(new ListValue())));
511  extensions::ExtensionSystem::Get(profile_)->event_router()->
512      BroadcastEvent(event.Pass());
513}
514
515void EventRouter::OnJobAdded(const drive::JobInfo& job_info) {
516  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
517  OnJobUpdated(job_info);
518}
519
520void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) {
521  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
522  if (!drive::IsActiveFileTransferJobInfo(job_info))
523    return;
524
525  bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end());
526
527  // Replace with the latest job info.
528  drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
529      job_info,
530      is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress);
531
532  // Fire event if needed.
533  bool always = is_new_job;
534  SendDriveFileTransferEvent(always);
535}
536
537void EventRouter::OnJobDone(const drive::JobInfo& job_info,
538                            drive::FileError error) {
539  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
540  if (!drive::IsActiveFileTransferJobInfo(job_info))
541    return;
542
543  // Replace with the latest job info.
544  drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
545      job_info,
546      error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted
547      : kFileTransferStateFailed);
548
549  // Fire event if needed.
550  bool always = true;
551  SendDriveFileTransferEvent(always);
552
553  // Forget about the job.
554  drive_jobs_.erase(job_info.job_id);
555}
556
557void EventRouter::SendDriveFileTransferEvent(bool always) {
558  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559
560  const base::Time now = base::Time::Now();
561
562  // When |always| flag is not set, we don't send the event until certain
563  // amount of time passes after the previous one. This is to avoid
564  // flooding the IPC between extensions by many onFileTransferUpdated events.
565  if (!always) {
566    const int64 delta = (now - last_file_transfer_event_).InMilliseconds();
567    // delta < 0 may rarely happen if system clock is synced and rewinded.
568    // To be conservative, we don't skip in that case.
569    if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds)
570      return;
571  }
572
573  // Convert the current |drive_jobs_| to a JSON value.
574  scoped_ptr<base::ListValue> event_list(new base::ListValue);
575  for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator
576           iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) {
577
578    scoped_ptr<base::DictionaryValue> job_info_dict(
579        JobInfoToDictionaryValue(kFileBrowserDomain,
580                                 iter->second.status,
581                                 iter->second.job_info));
582    event_list->Append(job_info_dict.release());
583  }
584
585  scoped_ptr<ListValue> args(new ListValue());
586  args->Append(event_list.release());
587  scoped_ptr<extensions::Event> event(new extensions::Event(
588      extensions::event_names::kOnFileTransfersUpdated, args.Pass()));
589  extensions::ExtensionSystem::Get(profile_)->event_router()->
590      DispatchEventToExtension(kFileBrowserDomain, event.Pass());
591
592  last_file_transfer_event_ = now;
593}
594
595void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) {
596  HandleFileWatchNotification(directory_path, false);
597}
598
599void EventRouter::OnFileSystemMounted() {
600  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
601
602  const std::string& drive_path = drive::util::GetDriveMountPointPathAsString();
603  DiskMountManager::MountPointInfo mount_info(
604      drive_path,
605      drive_path,
606      chromeos::MOUNT_TYPE_GOOGLE_DRIVE,
607      chromeos::disks::MOUNT_CONDITION_NONE);
608
609  // Raise mount event.
610  // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed
611  // or network is unreachable. These two errors will be handled later.
612  OnMountEvent(DiskMountManager::MOUNTING, chromeos::MOUNT_ERROR_NONE,
613               mount_info);
614}
615
616void EventRouter::OnFileSystemBeingUnmounted() {
617  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
618
619  // Raise a mount event to notify the File Manager.
620  const std::string& drive_path = drive::util::GetDriveMountPointPathAsString();
621  DiskMountManager::MountPointInfo mount_info(
622      drive_path,
623      drive_path,
624      chromeos::MOUNT_TYPE_GOOGLE_DRIVE,
625      chromeos::disks::MOUNT_CONDITION_NONE);
626  OnMountEvent(DiskMountManager::UNMOUNTING, chromeos::MOUNT_ERROR_NONE,
627               mount_info);
628}
629
630void EventRouter::OnRefreshTokenInvalid() {
631  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
632
633  // Raise a DriveConnectionStatusChanged event to notify the status offline.
634  scoped_ptr<extensions::Event> event(new extensions::Event(
635      extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
636      scoped_ptr<ListValue>(new ListValue())));
637  extensions::ExtensionSystem::Get(profile_)->event_router()->
638      BroadcastEvent(event.Pass());
639}
640
641void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path,
642                                              bool got_error) {
643  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
644
645  WatcherMap::const_iterator iter = file_watchers_.find(local_path);
646  if (iter == file_watchers_.end()) {
647    return;
648  }
649  DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error,
650                               iter->second->GetExtensionIds());
651}
652
653void EventRouter::DispatchDirectoryChangeEvent(
654    const base::FilePath& virtual_path,
655    bool got_error,
656    const std::vector<std::string>& extension_ids) {
657  if (!profile_) {
658    NOTREACHED();
659    return;
660  }
661
662  for (size_t i = 0; i < extension_ids.size(); ++i) {
663    const std::string& extension_id = extension_ids[i];
664
665    GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId(
666        extension_id));
667    GURL base_url = fileapi::GetFileSystemRootURI(
668        target_origin_url,
669        fileapi::kFileSystemTypeExternal);
670    GURL target_directory_url = GURL(base_url.spec() + virtual_path.value());
671    scoped_ptr<ListValue> args(new ListValue());
672    DictionaryValue* watch_info = new DictionaryValue();
673    args->Append(watch_info);
674    watch_info->SetString("directoryUrl", target_directory_url.spec());
675    watch_info->SetString("eventType",
676                          got_error ? kPathWatchError : kPathChanged);
677
678    // TODO(mtomasz): Pass set of entries. http://crbug.com/157834
679    ListValue* watch_info_entries = new ListValue();
680    watch_info->Set("changedEntries", watch_info_entries);
681
682    scoped_ptr<extensions::Event> event(new extensions::Event(
683        extensions::event_names::kOnDirectoryChanged, args.Pass()));
684    extensions::ExtensionSystem::Get(profile_)->event_router()->
685        DispatchEventToExtension(extension_id, event.Pass());
686  }
687}
688
689void EventRouter::DispatchMountEvent(
690    DiskMountManager::MountEvent event,
691    chromeos::MountError error_code,
692    const DiskMountManager::MountPointInfo& mount_info) {
693  scoped_ptr<ListValue> args(new ListValue());
694  DictionaryValue* mount_info_value = new DictionaryValue();
695  args->Append(mount_info_value);
696  mount_info_value->SetString(
697      "eventType",
698      event == DiskMountManager::MOUNTING ? "mount" : "unmount");
699  mount_info_value->SetString("status", MountErrorToString(error_code));
700  mount_info_value->SetString(
701      "mountType",
702      DiskMountManager::MountTypeToString(mount_info.mount_type));
703
704  // Add sourcePath to the event.
705  mount_info_value->SetString("sourcePath", mount_info.source_path);
706
707  base::FilePath relative_mount_path;
708
709  // If there were no error or some special conditions occurred, add mountPath
710  // to the event.
711  if (event == DiskMountManager::UNMOUNTING ||
712      error_code == chromeos::MOUNT_ERROR_NONE ||
713      mount_info.mount_condition) {
714    // Convert mount point path to relative path with the external file system
715    // exposed within File API.
716    if (util::ConvertFileToRelativeFileSystemPath(
717            profile_,
718            kFileBrowserDomain,
719            base::FilePath(mount_info.mount_path),
720            &relative_mount_path)) {
721      mount_info_value->SetString("mountPath",
722                                  "/" + relative_mount_path.value());
723    } else {
724      mount_info_value->SetString(
725          "status",
726          MountErrorToString(chromeos::MOUNT_ERROR_PATH_UNMOUNTED));
727    }
728  }
729
730  scoped_ptr<extensions::Event> extension_event(new extensions::Event(
731      extensions::event_names::kOnFileBrowserMountCompleted, args.Pass()));
732  extensions::ExtensionSystem::Get(profile_)->event_router()->
733      BroadcastEvent(extension_event.Pass());
734}
735
736void EventRouter::ShowRemovableDeviceInFileManager(
737    const DiskMountManager::Disk& disk,
738    const base::FilePath& mount_path) {
739  // Do not attempt to open File Manager while the login is in progress or
740  // the screen is locked.
741  if (chromeos::LoginDisplayHostImpl::default_host() ||
742      chromeos::ScreenLocker::default_screen_locker())
743    return;
744
745  // According to DCF (Design rule of Camera File system) by JEITA / CP-3461
746  // cameras should have pictures located in the DCIM root directory.
747  const base::FilePath dcim_path = mount_path.Append(
748      FILE_PATH_LITERAL("DCIM"));
749
750  // If there is no DCIM folder or an external photo importer is not available,
751  // then launch Files.app.
752  DirectoryExistsOnUIThread(
753      dcim_path,
754      IsGooglePhotosInstalled(profile_) ?
755      base::Bind(&base::DoNothing) :
756      base::Bind(&util::ViewRemovableDrive, mount_path),
757      base::Bind(&util::ViewRemovableDrive, mount_path));
758}
759
760void EventRouter::OnDiskAdded(const DiskMountManager::Disk* disk) {
761  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
762
763  VLOG(1) << "Disk added: " << disk->device_path();
764  if (disk->device_path().empty()) {
765    VLOG(1) << "Empty system path for " << disk->device_path();
766    return;
767  }
768
769  // If disk is not mounted yet and it has media and there is no policy
770  // forbidding external storage, give it a try.
771  if (disk->mount_path().empty() && disk->has_media() &&
772      !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
773    // Initiate disk mount operation. MountPath auto-detects the filesystem
774    // format if the second argument is empty. The third argument (mount label)
775    // is not used in a disk mount operation.
776    DiskMountManager::GetInstance()->MountPath(
777        disk->device_path(), std::string(), std::string(),
778        chromeos::MOUNT_TYPE_DEVICE);
779  } else {
780    // Either the disk was mounted or it has no media. In both cases we don't
781    // want the Scanning notification to persist.
782    notifications_->HideNotification(DesktopNotifications::DEVICE,
783                                     disk->system_path_prefix());
784  }
785}
786
787void EventRouter::OnDiskRemoved(const DiskMountManager::Disk* disk) {
788  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
789
790  VLOG(1) << "Disk removed: " << disk->device_path();
791
792  if (!disk->mount_path().empty()) {
793    DiskMountManager::GetInstance()->UnmountPath(
794        disk->mount_path(),
795        chromeos::UNMOUNT_OPTIONS_LAZY,
796        DiskMountManager::UnmountPathCallback());
797  }
798}
799
800void EventRouter::OnDeviceAdded(const std::string& device_path) {
801  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
802
803  VLOG(1) << "Device added : " << device_path;
804
805  // If the policy is set instead of showing the new device notification we show
806  // a notification that the operation is not permitted.
807  if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
808    notifications_->ShowNotification(
809        DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED,
810        device_path);
811    return;
812  }
813
814  notifications_->RegisterDevice(device_path);
815  notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE,
816                                          device_path,
817                                          base::TimeDelta::FromSeconds(5));
818}
819
820void EventRouter::OnDeviceRemoved(const std::string& device_path) {
821  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
822
823  VLOG(1) << "Device removed : " << device_path;
824  notifications_->HideNotification(DesktopNotifications::DEVICE,
825                                   device_path);
826  notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL,
827                                   device_path);
828  notifications_->UnregisterDevice(device_path);
829}
830
831void EventRouter::OnDeviceScanned(const std::string& device_path) {
832  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
833  VLOG(1) << "Device scanned : " << device_path;
834}
835
836void EventRouter::OnFormatStarted(const std::string& device_path,
837                                  bool success) {
838  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
839
840  if (success) {
841    notifications_->ShowNotification(DesktopNotifications::FORMAT_START,
842                                     device_path);
843  } else {
844    notifications_->ShowNotification(
845        DesktopNotifications::FORMAT_START_FAIL, device_path);
846  }
847}
848
849void EventRouter::OnFormatCompleted(const std::string& device_path,
850                                    bool success) {
851  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
852
853  if (success) {
854    notifications_->HideNotification(DesktopNotifications::FORMAT_START,
855                                     device_path);
856    notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS,
857                                     device_path);
858    // Hide it after a couple of seconds.
859    notifications_->HideNotificationDelayed(
860        DesktopNotifications::FORMAT_SUCCESS,
861        device_path,
862        base::TimeDelta::FromSeconds(4));
863    // MountPath auto-detects filesystem format if second argument is empty.
864    // The third argument (mount label) is not used in a disk mount operation.
865    DiskMountManager::GetInstance()->MountPath(device_path, std::string(),
866                                               std::string(),
867                                               chromeos::MOUNT_TYPE_DEVICE);
868  } else {
869    notifications_->HideNotification(DesktopNotifications::FORMAT_START,
870                                     device_path);
871    notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL,
872                                     device_path);
873  }
874}
875
876}  // namespace file_manager
877