event_router.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/private_api_util.h"
20#include "chrome/browser/chromeos/file_manager/app_id.h"
21#include "chrome/browser/chromeos/file_manager/desktop_notifications.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/login_display_host_impl.h"
26#include "chrome/browser/chromeos/login/screen_locker.h"
27#include "chrome/browser/drive/drive_service_interface.h"
28#include "chrome/browser/extensions/event_names.h"
29#include "chrome/browser/extensions/extension_service.h"
30#include "chrome/browser/extensions/extension_system.h"
31#include "chrome/browser/profiles/profile.h"
32#include "chrome/common/extensions/api/file_browser_private.h"
33#include "chrome/common/pref_names.h"
34#include "chromeos/login/login_state.h"
35#include "chromeos/network/network_handler.h"
36#include "chromeos/network/network_state_handler.h"
37#include "content/public/browser/browser_thread.h"
38#include "content/public/browser/notification_source.h"
39#include "extensions/browser/event_router.h"
40#include "webkit/common/fileapi/file_system_types.h"
41#include "webkit/common/fileapi/file_system_util.h"
42
43using chromeos::disks::DiskMountManager;
44using chromeos::NetworkHandler;
45using content::BrowserThread;
46using drive::DriveIntegrationService;
47using drive::DriveIntegrationServiceFactory;
48
49namespace file_browser_private = extensions::api::file_browser_private;
50
51namespace file_manager {
52namespace {
53
54const char kPathChanged[] = "changed";
55const char kPathWatchError[] = "error";
56
57void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path,
58                                   const base::Closure& success_callback,
59                                   const base::Closure& failure_callback) {
60  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
61
62  if (base::DirectoryExists(directory_path))
63    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback);
64  else
65    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback);
66};
67
68void DirectoryExistsOnUIThread(const base::FilePath& directory_path,
69                               const base::Closure& success_callback,
70                               const base::Closure& failure_callback) {
71  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
72
73  content::BrowserThread::PostBlockingPoolTask(
74      FROM_HERE,
75      base::Bind(&DirectoryExistsOnBlockingPool,
76                 directory_path,
77                 success_callback,
78                 failure_callback));
79};
80
81// Constants for the "transferState" field of onFileTransferUpdated event.
82const char kFileTransferStateStarted[] = "started";
83const char kFileTransferStateInProgress[] = "in_progress";
84const char kFileTransferStateCompleted[] = "completed";
85const char kFileTransferStateFailed[] = "failed";
86
87// Frequency of sending onFileTransferUpdated.
88const int64 kFileTransferEventFrequencyInMilliseconds = 1000;
89
90// Utility function to check if |job_info| is a file uploading job.
91bool IsUploadJob(drive::JobType type) {
92  return (type == drive::TYPE_UPLOAD_NEW_FILE ||
93          type == drive::TYPE_UPLOAD_EXISTING_FILE);
94}
95
96// Converts the job info to its JSON (Value) form.
97scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue(
98    const std::string& extension_id,
99    const std::string& job_status,
100    const drive::JobInfo& job_info) {
101  DCHECK(IsActiveFileTransferJobInfo(job_info));
102
103  scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
104  GURL url = util::ConvertRelativeFilePathToFileSystemUrl(
105      job_info.file_path, extension_id);
106  result->SetString("fileUrl", url.spec());
107  result->SetString("transferState", job_status);
108  result->SetString("transferType",
109                    IsUploadJob(job_info.job_type) ? "upload" : "download");
110  // JavaScript does not have 64-bit integers. Instead we use double, which
111  // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice
112  // in C++. Larger values are rounded.
113  result->SetDouble("processed",
114                    static_cast<double>(job_info.num_completed_bytes));
115  result->SetDouble("total", static_cast<double>(job_info.num_total_bytes));
116  return result.Pass();
117}
118
119// Checks for availability of the Google+ Photos app.
120bool IsGooglePhotosInstalled(Profile *profile) {
121  ExtensionService* service =
122      extensions::ExtensionSystem::Get(profile)->extension_service();
123  if (!service)
124    return false;
125
126  // Google+ Photos uses several ids for different channels. Therefore, all of
127  // them should be checked.
128  const std::string kGooglePlusPhotosIds[] = {
129    "ebpbnabdhheoknfklmpddcdijjkmklkp",  // G+ Photos staging
130    "efjnaogkjbogokcnohkmnjdojkikgobo",  // G+ Photos prod
131    "ejegoaikibpmikoejfephaneibodccma"   // G+ Photos dev
132  };
133
134  for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) {
135    if (service->GetExtensionById(kGooglePlusPhotosIds[i],
136                                  false /* include_disable */) != NULL)
137      return true;
138  }
139
140  return false;
141}
142
143// Sends an event named |event_name| with arguments |event_args| to extensions.
144void BroadcastEvent(Profile* profile,
145                    const std::string& event_name,
146                    scoped_ptr<base::ListValue> event_args) {
147  extensions::ExtensionSystem::Get(profile)->event_router()->
148      BroadcastEvent(make_scoped_ptr(
149          new extensions::Event(event_name, event_args.Pass())));
150}
151
152file_browser_private::MountCompletedEvent::Status
153MountErrorToMountCompletedStatus(chromeos::MountError error) {
154  using file_browser_private::MountCompletedEvent;
155
156  switch (error) {
157    case chromeos::MOUNT_ERROR_NONE:
158      return MountCompletedEvent::STATUS_SUCCESS;
159    case chromeos::MOUNT_ERROR_UNKNOWN:
160      return MountCompletedEvent::STATUS_ERROR_UNKNOWN;
161    case chromeos::MOUNT_ERROR_INTERNAL:
162      return MountCompletedEvent::STATUS_ERROR_INTERNAL;
163    case chromeos::MOUNT_ERROR_INVALID_ARGUMENT:
164      return MountCompletedEvent::STATUS_ERROR_INVALID_ARGUMENT;
165    case chromeos::MOUNT_ERROR_INVALID_PATH:
166      return MountCompletedEvent::STATUS_ERROR_INVALID_PATH;
167    case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED:
168      return MountCompletedEvent::STATUS_ERROR_PATH_ALREADY_MOUNTED;
169    case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED:
170      return MountCompletedEvent::STATUS_ERROR_PATH_NOT_MOUNTED;
171    case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED:
172      return MountCompletedEvent::STATUS_ERROR_DIRECTORY_CREATION_FAILED;
173    case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS:
174      return MountCompletedEvent::STATUS_ERROR_INVALID_MOUNT_OPTIONS;
175    case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS:
176      return MountCompletedEvent::STATUS_ERROR_INVALID_UNMOUNT_OPTIONS;
177    case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS:
178      return MountCompletedEvent::STATUS_ERROR_INSUFFICIENT_PERMISSIONS;
179    case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND:
180      return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_NOT_FOUND;
181    case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED:
182      return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_FAILED;
183    case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH:
184      return MountCompletedEvent::STATUS_ERROR_INVALID_DEVICE_PATH;
185    case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM:
186      return MountCompletedEvent::STATUS_ERROR_UNKNOWN_FILESYSTEM;
187    case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM:
188      return MountCompletedEvent::STATUS_ERROR_UNSUPORTED_FILESYSTEM;
189    case chromeos::MOUNT_ERROR_INVALID_ARCHIVE:
190      return MountCompletedEvent::STATUS_ERROR_INVALID_ARCHIVE;
191    case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED:
192      return MountCompletedEvent::STATUS_ERROR_AUTHENTICATION;
193    case chromeos::MOUNT_ERROR_PATH_UNMOUNTED:
194      return MountCompletedEvent::STATUS_ERROR_PATH_UNMOUNTED;
195  }
196  NOTREACHED();
197  return MountCompletedEvent::STATUS_NONE;
198}
199
200void BroadcastMountCompletedEvent(
201    Profile* profile,
202    file_browser_private::MountCompletedEvent::EventType event_type,
203    chromeos::MountError error,
204    const VolumeInfo& volume_info) {
205  file_browser_private::MountCompletedEvent event;
206  event.event_type = event_type;
207  event.status = MountErrorToMountCompletedStatus(error);
208  util::VolumeInfoToVolumeMetadata(
209      profile, volume_info, &event.volume_metadata);
210
211  if (!volume_info.mount_path.empty() &&
212      event.volume_metadata.mount_path.empty()) {
213    event.status =
214        file_browser_private::MountCompletedEvent::STATUS_ERROR_PATH_UNMOUNTED;
215  }
216
217  BroadcastEvent(
218      profile,
219      extensions::event_names::kOnFileBrowserMountCompleted,
220      file_browser_private::OnMountCompleted::Create(event));
221}
222
223file_browser_private::CopyProgressStatus::Type
224CopyProgressTypeToCopyProgressStatusType(
225    fileapi::FileSystemOperation::CopyProgressType type) {
226  using file_browser_private::CopyProgressStatus;
227
228  switch (type) {
229    case fileapi::FileSystemOperation::BEGIN_COPY_ENTRY:
230      return CopyProgressStatus::TYPE_BEGIN_COPY_ENTRY;
231    case fileapi::FileSystemOperation::END_COPY_ENTRY:
232      return CopyProgressStatus::TYPE_END_COPY_ENTRY;
233    case fileapi::FileSystemOperation::PROGRESS:
234      return CopyProgressStatus::TYPE_PROGRESS;
235  }
236  NOTREACHED();
237  return CopyProgressStatus::TYPE_NONE;
238}
239
240}  // namespace
241
242// Pass dummy value to JobInfo's constructor for make it default constructible.
243EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus()
244    : job_info(drive::TYPE_DOWNLOAD_FILE) {
245}
246
247EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus(
248    const drive::JobInfo& info, const std::string& status)
249    : job_info(info), status(status) {
250}
251
252EventRouter::EventRouter(Profile* profile)
253    : notifications_(new DesktopNotifications(profile)),
254      pref_change_registrar_(new PrefChangeRegistrar),
255      profile_(profile),
256      weak_factory_(this) {
257  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
258}
259
260EventRouter::~EventRouter() {
261}
262
263void EventRouter::Shutdown() {
264  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
265
266  DLOG_IF(WARNING, !file_watchers_.empty())
267      << "Not all file watchers are "
268      << "removed. This can happen when Files.app is open during shutdown.";
269  STLDeleteValues(&file_watchers_);
270  if (!profile_) {
271    NOTREACHED();
272    return;
273  }
274
275  pref_change_registrar_->RemoveAll();
276
277  if (NetworkHandler::IsInitialized()) {
278    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
279                                                                   FROM_HERE);
280  }
281
282  DriveIntegrationService* integration_service =
283      DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates(
284          profile_);
285  if (integration_service) {
286    integration_service->file_system()->RemoveObserver(this);
287    integration_service->drive_service()->RemoveObserver(this);
288    integration_service->job_list()->RemoveObserver(this);
289  }
290
291  VolumeManager* volume_manager = VolumeManager::Get(profile_);
292  if (volume_manager)
293    volume_manager->RemoveObserver(this);
294
295  profile_ = NULL;
296}
297
298void EventRouter::ObserveFileSystemEvents() {
299  if (!profile_) {
300    NOTREACHED();
301    return;
302  }
303  if (!chromeos::LoginState::IsInitialized() ||
304      !chromeos::LoginState::Get()->IsUserLoggedIn()) {
305    return;
306  }
307
308  // VolumeManager's construction triggers DriveIntegrationService's
309  // construction, so it is necessary to call VolumeManager's Get before
310  // accessing DriveIntegrationService.
311  VolumeManager* volume_manager = VolumeManager::Get(profile_);
312  if (volume_manager)
313    volume_manager->AddObserver(this);
314
315  DriveIntegrationService* integration_service =
316      DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates(
317          profile_);
318  if (integration_service) {
319    integration_service->drive_service()->AddObserver(this);
320    integration_service->file_system()->AddObserver(this);
321    integration_service->job_list()->AddObserver(this);
322  }
323
324  if (NetworkHandler::IsInitialized()) {
325    NetworkHandler::Get()->network_state_handler()->AddObserver(this,
326                                                                FROM_HERE);
327  }
328
329  pref_change_registrar_->Init(profile_->GetPrefs());
330  base::Closure callback =
331      base::Bind(&EventRouter::OnFileManagerPrefsChanged,
332                 weak_factory_.GetWeakPtr());
333  pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback);
334  pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback);
335  pref_change_registrar_->Add(prefs::kDisableDrive, callback);
336  pref_change_registrar_->Add(prefs::kUse24HourClock, callback);
337}
338
339// File watch setup routines.
340void EventRouter::AddFileWatch(const base::FilePath& local_path,
341                               const base::FilePath& virtual_path,
342                               const std::string& extension_id,
343                               const BoolCallback& callback) {
344  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345  DCHECK(!callback.is_null());
346
347  base::FilePath watch_path = local_path;
348  bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path);
349  // Tweak watch path for remote sources - we need to drop leading /special
350  // directory from there in order to be able to pair these events with
351  // their change notifications.
352  if (is_on_drive)
353    watch_path = drive::util::ExtractDrivePath(watch_path);
354
355  WatcherMap::iterator iter = file_watchers_.find(watch_path);
356  if (iter == file_watchers_.end()) {
357    scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path));
358    watcher->AddExtension(extension_id);
359
360    if (is_on_drive) {
361      // For Drive, file watching is done via OnDirectoryChanged().
362      base::MessageLoopProxy::current()->PostTask(FROM_HERE,
363                                                  base::Bind(callback, true));
364    } else {
365      // For local files, start watching using FileWatcher.
366      watcher->WatchLocalFile(
367          watch_path,
368          base::Bind(&EventRouter::HandleFileWatchNotification,
369                     weak_factory_.GetWeakPtr()),
370          callback);
371    }
372
373    file_watchers_[watch_path] = watcher.release();
374  } else {
375    iter->second->AddExtension(extension_id);
376    base::MessageLoopProxy::current()->PostTask(FROM_HERE,
377                                                base::Bind(callback, true));
378  }
379}
380
381void EventRouter::RemoveFileWatch(const base::FilePath& local_path,
382                                  const std::string& extension_id) {
383  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
384
385  base::FilePath watch_path = local_path;
386  // Tweak watch path for remote sources - we need to drop leading /special
387  // directory from there in order to be able to pair these events with
388  // their change notifications.
389  if (drive::util::IsUnderDriveMountPoint(watch_path)) {
390    watch_path = drive::util::ExtractDrivePath(watch_path);
391  }
392  WatcherMap::iterator iter = file_watchers_.find(watch_path);
393  if (iter == file_watchers_.end())
394    return;
395  // Remove the watcher if |watch_path| is no longer watched by any extensions.
396  iter->second->RemoveExtension(extension_id);
397  if (iter->second->GetExtensionIds().empty()) {
398    delete iter->second;
399    file_watchers_.erase(iter);
400  }
401}
402
403void EventRouter::OnCopyCompleted(int copy_id,
404                                  const GURL& source_url,
405                                  const GURL& destination_url,
406                                  base::PlatformFileError error) {
407  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
408
409  file_browser_private::CopyProgressStatus status;
410  if (error == base::PLATFORM_FILE_OK) {
411    // Send success event.
412    status.type = file_browser_private::CopyProgressStatus::TYPE_SUCCESS;
413    status.source_url.reset(new std::string(source_url.spec()));
414    status.destination_url.reset(new std::string(destination_url.spec()));
415  } else {
416    // Send error event.
417    status.type = file_browser_private::CopyProgressStatus::TYPE_ERROR;
418    status.error.reset(
419        new int(fileapi::PlatformFileErrorToWebFileError(error)));
420  }
421
422  BroadcastEvent(
423      profile_,
424      extensions::event_names::kOnFileBrowserCopyProgress,
425      file_browser_private::OnCopyProgress::Create(copy_id, status));
426}
427
428void EventRouter::OnCopyProgress(
429    int copy_id,
430    fileapi::FileSystemOperation::CopyProgressType type,
431    const GURL& source_url,
432    const GURL& destination_url,
433    int64 size) {
434  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
435
436  file_browser_private::CopyProgressStatus status;
437  status.type = CopyProgressTypeToCopyProgressStatusType(type);
438  status.source_url.reset(new std::string(source_url.spec()));
439  if (type == fileapi::FileSystemOperation::END_COPY_ENTRY)
440    status.destination_url.reset(new std::string(destination_url.spec()));
441  if (type == fileapi::FileSystemOperation::PROGRESS)
442    status.size.reset(new double(size));
443
444  BroadcastEvent(
445      profile_,
446      extensions::event_names::kOnFileBrowserCopyProgress,
447      file_browser_private::OnCopyProgress::Create(copy_id, status));
448}
449
450void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) {
451  if (!profile_ ||
452      !extensions::ExtensionSystem::Get(profile_)->event_router()) {
453    NOTREACHED();
454    return;
455  }
456
457  BroadcastEvent(
458      profile_,
459      extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
460      make_scoped_ptr(new ListValue));
461}
462
463void EventRouter::OnFileManagerPrefsChanged() {
464  if (!profile_ ||
465      !extensions::ExtensionSystem::Get(profile_)->event_router()) {
466    NOTREACHED();
467    return;
468  }
469
470  BroadcastEvent(
471      profile_,
472      extensions::event_names::kOnFileBrowserPreferencesChanged,
473      make_scoped_ptr(new ListValue));
474}
475
476void EventRouter::OnJobAdded(const drive::JobInfo& job_info) {
477  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478  OnJobUpdated(job_info);
479}
480
481void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) {
482  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
483  if (!drive::IsActiveFileTransferJobInfo(job_info))
484    return;
485
486  bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end());
487
488  // Replace with the latest job info.
489  drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
490      job_info,
491      is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress);
492
493  // Fire event if needed.
494  bool always = is_new_job;
495  SendDriveFileTransferEvent(always);
496}
497
498void EventRouter::OnJobDone(const drive::JobInfo& job_info,
499                            drive::FileError error) {
500  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501  if (!drive::IsActiveFileTransferJobInfo(job_info))
502    return;
503
504  // Replace with the latest job info.
505  drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
506      job_info,
507      error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted
508      : kFileTransferStateFailed);
509
510  // Fire event if needed.
511  bool always = true;
512  SendDriveFileTransferEvent(always);
513
514  // Forget about the job.
515  drive_jobs_.erase(job_info.job_id);
516}
517
518void EventRouter::SendDriveFileTransferEvent(bool always) {
519  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
520
521  const base::Time now = base::Time::Now();
522
523  // When |always| flag is not set, we don't send the event until certain
524  // amount of time passes after the previous one. This is to avoid
525  // flooding the IPC between extensions by many onFileTransferUpdated events.
526  if (!always) {
527    const int64 delta = (now - last_file_transfer_event_).InMilliseconds();
528    // delta < 0 may rarely happen if system clock is synced and rewinded.
529    // To be conservative, we don't skip in that case.
530    if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds)
531      return;
532  }
533
534  // Convert the current |drive_jobs_| to a JSON value.
535  scoped_ptr<base::ListValue> event_list(new base::ListValue);
536  for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator
537           iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) {
538
539    scoped_ptr<base::DictionaryValue> job_info_dict(
540        JobInfoToDictionaryValue(kFileManagerAppId,
541                                 iter->second.status,
542                                 iter->second.job_info));
543    event_list->Append(job_info_dict.release());
544  }
545
546  scoped_ptr<ListValue> args(new ListValue());
547  args->Append(event_list.release());
548  scoped_ptr<extensions::Event> event(new extensions::Event(
549      extensions::event_names::kOnFileTransfersUpdated, args.Pass()));
550  extensions::ExtensionSystem::Get(profile_)->event_router()->
551      DispatchEventToExtension(kFileManagerAppId, event.Pass());
552
553  last_file_transfer_event_ = now;
554}
555
556void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) {
557  HandleFileWatchNotification(directory_path, false);
558}
559
560void EventRouter::OnRefreshTokenInvalid() {
561  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
562
563  // Raise a DriveConnectionStatusChanged event to notify the status offline.
564  BroadcastEvent(
565      profile_,
566      extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
567      make_scoped_ptr(new ListValue));
568}
569
570void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path,
571                                              bool got_error) {
572  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
573
574  WatcherMap::const_iterator iter = file_watchers_.find(local_path);
575  if (iter == file_watchers_.end()) {
576    return;
577  }
578  DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error,
579                               iter->second->GetExtensionIds());
580}
581
582void EventRouter::DispatchDirectoryChangeEvent(
583    const base::FilePath& virtual_path,
584    bool got_error,
585    const std::vector<std::string>& extension_ids) {
586  if (!profile_) {
587    NOTREACHED();
588    return;
589  }
590
591  for (size_t i = 0; i < extension_ids.size(); ++i) {
592    const std::string& extension_id = extension_ids[i];
593
594    GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId(
595        extension_id));
596    GURL base_url = fileapi::GetFileSystemRootURI(
597        target_origin_url,
598        fileapi::kFileSystemTypeExternal);
599    GURL target_directory_url = GURL(base_url.spec() + virtual_path.value());
600    scoped_ptr<ListValue> args(new ListValue());
601    DictionaryValue* watch_info = new DictionaryValue();
602    args->Append(watch_info);
603    watch_info->SetString("directoryUrl", target_directory_url.spec());
604    watch_info->SetString("eventType",
605                          got_error ? kPathWatchError : kPathChanged);
606
607    scoped_ptr<extensions::Event> event(new extensions::Event(
608        extensions::event_names::kOnDirectoryChanged, args.Pass()));
609    extensions::ExtensionSystem::Get(profile_)->event_router()->
610        DispatchEventToExtension(extension_id, event.Pass());
611  }
612}
613
614void EventRouter::ShowRemovableDeviceInFileManager(
615    const base::FilePath& mount_path) {
616  // Do not attempt to open File Manager while the login is in progress or
617  // the screen is locked.
618  if (chromeos::LoginDisplayHostImpl::default_host() ||
619      chromeos::ScreenLocker::default_screen_locker())
620    return;
621
622  // According to DCF (Design rule of Camera File system) by JEITA / CP-3461
623  // cameras should have pictures located in the DCIM root directory.
624  const base::FilePath dcim_path = mount_path.Append(
625      FILE_PATH_LITERAL("DCIM"));
626
627  // If there is no DCIM folder or an external photo importer is not available,
628  // then launch Files.app.
629  DirectoryExistsOnUIThread(
630      dcim_path,
631      IsGooglePhotosInstalled(profile_) ?
632      base::Bind(&base::DoNothing) :
633      base::Bind(&util::OpenRemovableDrive, mount_path),
634      base::Bind(&util::OpenRemovableDrive, mount_path));
635}
636
637void EventRouter::OnDiskAdded(
638    const DiskMountManager::Disk& disk, bool mounting) {
639  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
640
641  if (!mounting) {
642    // If the disk is not being mounted, we don't want the Scanning
643    // notification to persist.
644    notifications_->HideNotification(DesktopNotifications::DEVICE,
645                                     disk.system_path_prefix());
646  }
647}
648
649void EventRouter::OnDiskRemoved(const DiskMountManager::Disk& disk) {
650  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
651  // Do nothing.
652}
653
654void EventRouter::OnDeviceAdded(const std::string& device_path) {
655  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
656
657  // If the policy is set instead of showing the new device notification,
658  // we show a notification that the operation is not permitted.
659  if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
660    notifications_->ShowNotification(
661        DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED,
662        device_path);
663    return;
664  }
665
666  notifications_->RegisterDevice(device_path);
667  notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE,
668                                          device_path,
669                                          base::TimeDelta::FromSeconds(5));
670}
671
672void EventRouter::OnDeviceRemoved(const std::string& device_path) {
673  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
674
675  notifications_->HideNotification(DesktopNotifications::DEVICE,
676                                   device_path);
677  notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL,
678                                   device_path);
679  notifications_->UnregisterDevice(device_path);
680}
681
682void EventRouter::OnVolumeMounted(chromeos::MountError error_code,
683                                  const VolumeInfo& volume_info,
684                                  bool is_remounting) {
685  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
686  // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can
687  // happen at shutdown. This should be removed after removing Drive mounting
688  // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is
689  // the only path to come here after Shutdown is called).
690  if (!profile_)
691    return;
692
693  BroadcastMountCompletedEvent(
694      profile_,
695      file_browser_private::MountCompletedEvent::EVENT_TYPE_MOUNT,
696      error_code, volume_info);
697
698  if (volume_info.type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION &&
699      !is_remounting) {
700    notifications_->ManageNotificationsOnMountCompleted(
701        volume_info.system_path_prefix.AsUTF8Unsafe(),
702        volume_info.drive_label,
703        volume_info.is_parent,
704        error_code == chromeos::MOUNT_ERROR_NONE,
705        error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM);
706
707    // If a new device was mounted, a new File manager window may need to be
708    // opened.
709    if (error_code == chromeos::MOUNT_ERROR_NONE)
710      ShowRemovableDeviceInFileManager(volume_info.mount_path);
711  }
712}
713
714void EventRouter::OnVolumeUnmounted(chromeos::MountError error_code,
715                                    const VolumeInfo& volume_info) {
716  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
717  BroadcastMountCompletedEvent(
718      profile_,
719      file_browser_private::MountCompletedEvent::EVENT_TYPE_UNMOUNT,
720      error_code, volume_info);
721}
722
723void EventRouter::OnFormatStarted(const std::string& device_path,
724                                  bool success) {
725  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
726
727  if (success) {
728    notifications_->ShowNotification(DesktopNotifications::FORMAT_START,
729                                     device_path);
730  } else {
731    notifications_->ShowNotification(
732        DesktopNotifications::FORMAT_START_FAIL, device_path);
733  }
734}
735
736void EventRouter::OnFormatCompleted(const std::string& device_path,
737                                    bool success) {
738  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
739
740  if (success) {
741    notifications_->HideNotification(DesktopNotifications::FORMAT_START,
742                                     device_path);
743    notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS,
744                                     device_path);
745    // Hide it after a couple of seconds.
746    notifications_->HideNotificationDelayed(
747        DesktopNotifications::FORMAT_SUCCESS,
748        device_path,
749        base::TimeDelta::FromSeconds(4));
750  } else {
751    notifications_->HideNotification(DesktopNotifications::FORMAT_START,
752                                     device_path);
753    notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL,
754                                     device_path);
755  }
756}
757
758}  // namespace file_manager
759