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