Add chrome.fileSystem.GetVolumeList().

This CL adds a method to list available volumes. It's available only to kiosk
apps running in kiosk session and to some whitelited component apps/extensions.

[email protected]  # chrome_extensions.js reviewed at cr/90486340.
TEST=browser_tests: *KioskTest*GetVolumeList*,
     *FileSystemApiTestForRequestFileSystem*GetVolumeList*
BUG=440674

Review URL: https://codereview.chromium.org/1029803004

Cr-Commit-Position: refs/heads/master@{#324040}
diff --git a/chrome/browser/chromeos/login/kiosk_browsertest.cc b/chrome/browser/chromeos/login/kiosk_browsertest.cc
index 890a1b7..b4f1aa887 100644
--- a/chrome/browser/chromeos/login/kiosk_browsertest.cc
+++ b/chrome/browser/chromeos/login/kiosk_browsertest.cc
@@ -111,6 +111,13 @@
 //       detail/bmbpicmpniaclbbpdkfglgipkkebnbjf
 const char kTestLocalFsKioskApp[] = "bmbpicmpniaclbbpdkfglgipkkebnbjf";
 
+// An app to test local access to file systems via the
+// chrome.fileSystem.requestFileSystem API.
+// Webstore data json is in
+//   chrome/test/data/chromeos/app_mode/webstore/inlineinstall/
+//       detail/aaedpojejpghjkedenggihopfhfijcko
+const char kTestGetVolumeListKioskApp[] = "aaedpojejpghjkedenggihopfhfijcko";
+
 // Fake usb stick mount path.
 const char kFakeUsbMountPathUpdatePass[] =
     "chromeos/app_mode/external_update/update_pass";
@@ -1252,6 +1259,17 @@
   OobeScreenWaiter(OobeDisplay::SCREEN_ERROR_MESSAGE).Wait();
 }
 
+// Verifies available volumes for kiosk apps in kiosk session.
+IN_PROC_BROWSER_TEST_F(KioskTest, GetVolumeList) {
+  set_test_app_id(kTestGetVolumeListKioskApp);
+  set_test_app_version("0.1");
+  set_test_crx_file(test_app_id() + ".crx");
+
+  extensions::ResultCatcher catcher;
+  StartAppLaunchFromLoginScreen(SimulateNetworkOnlineClosure());
+  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
 // Verifies that an enterprise device does not auto-launch kiosk mode when cros
 // settings are untrusted.
 IN_PROC_BROWSER_TEST_F(KioskTest, NoEnterpriseAutoLaunchWhenUntrusted) {
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc
index 68c4cd70..38da52a 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_api.cc
@@ -5,12 +5,14 @@
 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
 
 #include <set>
+#include <vector>
 
 #include "apps/saved_files_service.h"
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/memory/linked_ptr.h"
 #include "base/path_service.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -350,6 +352,70 @@
       break;
   }
 }
+
+ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile,
+                                                 content::RenderViewHost* host)
+    : profile_(profile), host_(host) {
+  DCHECK(profile_);
+  DCHECK(host_);
+}
+
+ConsentProviderDelegate::~ConsentProviderDelegate() {
+}
+
+// static
+void ConsentProviderDelegate::SetAutoDialogButtonForTest(
+    ui::DialogButton button) {
+  g_auto_dialog_button_for_test = button;
+}
+
+void ConsentProviderDelegate::ShowDialog(
+    const extensions::Extension& extension,
+    base::WeakPtr<file_manager::Volume> volume,
+    bool writable,
+    const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
+  content::WebContents* const foreground_contents =
+      GetWebContentsForRenderViewHost(profile_, host_);
+  // If there is no web contents handle, then the method is most probably
+  // executed from a background page. Find an app window to host the dialog.
+  content::WebContents* const web_contents =
+      foreground_contents ? foreground_contents
+                          : GetWebContentsForAppId(profile_, extension.id());
+  if (!web_contents) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
+    return;
+  }
+
+  // Short circuit the user consent dialog for tests. This is far from a pretty
+  // code design.
+  if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::Bind(callback, g_auto_dialog_button_for_test /* result */));
+    return;
+  }
+
+  RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
+                                          writable, base::Bind(callback));
+}
+
+bool ConsentProviderDelegate::IsAutoLaunched(
+    const extensions::Extension& extension) {
+  chromeos::KioskAppManager::App app_info;
+  return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
+         app_info.was_auto_launched_with_zero_delay;
+}
+
+bool ConsentProviderDelegate::IsWhitelistedComponent(
+    const extensions::Extension& extension) {
+  for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
+    if (extension.id().compare(whitelisted_id) == 0)
+      return true;
+  }
+  return false;
+}
+
 #endif
 
 }  // namespace file_system_api
@@ -1135,9 +1201,14 @@
   return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
 }
 
+ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
+  NOTIMPLEMENTED();
+  return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
+}
 #else
+
 FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
-    : chrome_details_(this), consent_provider_(this) {
+    : chrome_details_(this) {
 }
 
 FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {
@@ -1149,8 +1220,12 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   // Only kiosk apps in kiosk sessions can use this API.
-  // Additionally whitelisted component extensions and apps.
-  if (!consent_provider_.IsGrantable(*extension()))
+  // Additionally it is enabled for whitelisted component extensions and apps.
+  file_system_api::ConsentProviderDelegate consent_provider_delegate(
+      chrome_details_.GetProfile(), render_view_host());
+  file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
+
+  if (!consent_provider.IsGrantable(*extension()))
     return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
 
   using file_manager::VolumeManager;
@@ -1187,68 +1262,13 @@
   if (writable && (volume->is_read_only()))
     return RespondNow(Error(kSecurityError));
 
-  consent_provider_.RequestConsent(
+  consent_provider.RequestConsent(
       *extension(), volume, writable,
       base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
                  volume, writable));
   return RespondLater();
 }
 
-// static
-void FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(
-    ui::DialogButton button) {
-  g_auto_dialog_button_for_test = button;
-}
-
-void FileSystemRequestFileSystemFunction::ShowDialog(
-    const extensions::Extension& extension,
-    base::WeakPtr<file_manager::Volume> volume,
-    bool writable,
-    const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
-  content::WebContents* const foreground_contents =
-      GetWebContentsForRenderViewHost(chrome_details_.GetProfile(),
-                                      render_view_host());
-  // If there is no web contents handle, then the method is most probably
-  // executed from a background page. Find an app window to host the dialog.
-  content::WebContents* const web_contents =
-      foreground_contents ? foreground_contents
-                          : GetWebContentsForAppId(chrome_details_.GetProfile(),
-                                                   extension_id());
-  if (!web_contents) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
-    return;
-  }
-
-  // Short circuit the user consent dialog for tests. This is far from a pretty
-  // code design.
-  if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::Bind(callback, g_auto_dialog_button_for_test /* result */));
-    return;
-  }
-
-  RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
-                                          writable, base::Bind(callback));
-}
-
-bool FileSystemRequestFileSystemFunction::IsAutoLaunched(
-    const extensions::Extension& extension) {
-  chromeos::KioskAppManager::App app_info;
-  return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
-         app_info.was_auto_launched_with_zero_delay;
-}
-
-bool FileSystemRequestFileSystemFunction::IsWhitelistedComponent(
-    const extensions::Extension& extension) {
-  for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
-    if (extension.id().compare(whitelisted_id) == 0)
-      return true;
-  }
-  return false;
-}
-
 void FileSystemRequestFileSystemFunction::OnConsentReceived(
     base::WeakPtr<file_manager::Volume> volume,
     bool writable,
@@ -1351,6 +1371,44 @@
   SetResult(dict);
   SendResponse(true);
 }
+
+FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
+    : chrome_details_(this) {
+}
+
+FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {
+}
+
+ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
+  // Only kiosk apps in kiosk sessions can use this API.
+  // Additionally it is enabled for whitelisted component extensions and apps.
+  file_system_api::ConsentProviderDelegate consent_provider_delegate(
+      chrome_details_.GetProfile(), render_view_host());
+  file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
+
+  if (!consent_provider.IsGrantable(*extension()))
+    return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
+
+  using file_manager::VolumeManager;
+  VolumeManager* const volume_manager =
+      VolumeManager::Get(chrome_details_.GetProfile());
+  DCHECK(volume_manager);
+
+  using extensions::api::file_system::Volume;
+  const auto& volume_list = volume_manager->GetVolumeList();
+  std::vector<linked_ptr<Volume>> result_volume_list;
+  // Convert volume_list to result_volume_list.
+  for (const auto& volume : volume_list) {
+    const linked_ptr<Volume> result_volume(new Volume);
+    result_volume->volume_id = volume->volume_id();
+    result_volume->writable = !volume->is_read_only();
+    result_volume_list.push_back(result_volume);
+  }
+
+  return RespondNow(
+      ArgumentList(extensions::api::file_system::GetVolumeList::Results::Create(
+                       result_volume_list).Pass()));
+}
 #endif
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.h b/chrome/browser/extensions/api/file_system/file_system_api.h
index 2445c19..7ae7c386 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.h
+++ b/chrome/browser/extensions/api/file_system/file_system_api.h
@@ -48,8 +48,8 @@
 #if defined(OS_CHROMEOS)
 // Requests consent for the chrome.fileSystem.requestFileSystem() method.
 // Interaction with UI and environmental checks (kiosk mode, whitelist) are
-// provided by a delegate: FileSystemRequestFileSystemFunction. For testing,
-// it is TestingConsentProviderDelegate.
+// provided by a delegate: ConsentProviderDelegate. For testing, it is
+// TestingConsentProviderDelegate.
 class ConsentProvider {
  public:
   enum Consent { CONSENT_GRANTED, CONSENT_REJECTED, CONSENT_IMPOSSIBLE };
@@ -97,6 +97,35 @@
 
   DISALLOW_COPY_AND_ASSIGN(ConsentProvider);
 };
+
+// Handles interaction with user as well as environment checks (whitelists,
+// context of running extensions) for ConsentProvider.
+class ConsentProviderDelegate : public ConsentProvider::DelegateInterface {
+ public:
+  ConsentProviderDelegate(Profile* profile, content::RenderViewHost* host);
+  ~ConsentProviderDelegate();
+
+ private:
+  friend ScopedSkipRequestFileSystemDialog;
+
+  // Sets a fake result for the user consent dialog. If ui::DIALOG_BUTTON_NONE
+  // then disabled.
+  static void SetAutoDialogButtonForTest(ui::DialogButton button);
+
+  // ConsentProvider::DelegateInterface overrides:
+  void ShowDialog(const extensions::Extension& extension,
+                  base::WeakPtr<file_manager::Volume> volume,
+                  bool writable,
+                  const file_system_api::ConsentProvider::ShowDialogCallback&
+                      callback) override;
+  bool IsAutoLaunched(const extensions::Extension& extension) override;
+  bool IsWhitelistedComponent(const extensions::Extension& extension) override;
+
+  Profile* const profile_;
+  content::RenderViewHost* const host_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConsentProviderDelegate);
+};
 #endif
 
 }  // namespace file_system_api
@@ -315,15 +344,26 @@
  protected:
   ~FileSystemRequestFileSystemFunction() override {}
 
-  // AsyncExtensionFunction overrides.
+  // UIThreadExtensionFunction overrides.
+  ExtensionFunction::ResponseAction Run() override;
+};
+
+// Stub for non Chrome OS operating systems.
+class FileSystemGetVolumeListFunction : public UIThreadExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
+                             FILESYSTEM_GETVOLUMELIST);
+
+ protected:
+  ~FileSystemGetVolumeListFunction() override {}
+
+  // UIThreadExtensionFunction overrides.
   ExtensionFunction::ResponseAction Run() override;
 };
 
 #else
 // Requests a file system for the specified volume id.
-class FileSystemRequestFileSystemFunction
-    : public UIThreadExtensionFunction,
-      public file_system_api::ConsentProvider::DelegateInterface {
+class FileSystemRequestFileSystemFunction : public UIThreadExtensionFunction {
  public:
   DECLARE_EXTENSION_FUNCTION("fileSystem.requestFileSystem",
                              FILESYSTEM_REQUESTFILESYSTEM)
@@ -332,25 +372,10 @@
  protected:
   ~FileSystemRequestFileSystemFunction() override;
 
-  // AsyncExtensionFunction overrides.
+  // UIThreadExtensionFunction overrides.
   ExtensionFunction::ResponseAction Run() override;
 
  private:
-  friend ScopedSkipRequestFileSystemDialog;
-
-  // Sets a fake result for the user consent dialog. If ui::DIALOG_BUTTON_NONE
-  // then disabled.
-  static void SetAutoDialogButtonForTest(ui::DialogButton button);
-
-  // ConsentProvider::DelegateInterface overrides:
-  void ShowDialog(const extensions::Extension& extension,
-                  base::WeakPtr<file_manager::Volume> volume,
-                  bool writable,
-                  const file_system_api::ConsentProvider::ShowDialogCallback&
-                      callback) override;
-  bool IsAutoLaunched(const extensions::Extension& extension) override;
-  bool IsWhitelistedComponent(const extensions::Extension& extension) override;
-
   // Called when a user grants or rejects permissions for the file system
   // access.
   void OnConsentReceived(base::WeakPtr<file_manager::Volume> volume,
@@ -358,7 +383,23 @@
                          file_system_api::ConsentProvider::Consent result);
 
   ChromeExtensionFunctionDetails chrome_details_;
-  file_system_api::ConsentProvider consent_provider_;
+};
+
+// Requests a list of available volumes.
+class FileSystemGetVolumeListFunction : public UIThreadExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
+                             FILESYSTEM_GETVOLUMELIST);
+  FileSystemGetVolumeListFunction();
+
+ protected:
+  ~FileSystemGetVolumeListFunction() override;
+
+  // UIThreadExtensionFunction overrides.
+  ExtensionFunction::ResponseAction Run() override;
+
+ private:
+  ChromeExtensionFunctionDetails chrome_details_;
 };
 #endif
 
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index f221c56..40c1991 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -44,10 +44,11 @@
 class ScopedSkipRequestFileSystemDialog {
  public:
   explicit ScopedSkipRequestFileSystemDialog(ui::DialogButton button) {
-    FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(button);
+    file_system_api::ConsentProviderDelegate::SetAutoDialogButtonForTest(
+        button);
   }
   ~ScopedSkipRequestFileSystemDialog() {
-    FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(
+    file_system_api::ConsentProviderDelegate::SetAutoDialogButtonForTest(
         ui::DIALOG_BUTTON_NONE);
   }
 
@@ -413,4 +414,17 @@
       << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem, GetVolumeList) {
+  EnterKioskSession();
+  ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/get_volume_list"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem,
+                       GetVolumeList_NotKioskSession) {
+  ASSERT_TRUE(RunPlatformAppTest(
+      "api_test/file_system/get_volume_list_not_kiosk_session"))
+      << message_;
+}
+
 }  // namespace extensions
diff --git a/chrome/common/extensions/api/file_system.idl b/chrome/common/extensions/api/file_system.idl
index a39dd01..ed4aa1a 100644
--- a/chrome/common/extensions/api/file_system.idl
+++ b/chrome/common/extensions/api/file_system.idl
@@ -83,7 +83,7 @@
     boolean? acceptsMultiple;
   };
 
-  dictionary RequestFileSystemOptions {
+  [nodoc] dictionary RequestFileSystemOptions {
     // The ID of the requested volume.
     DOMString volumeId;
 
@@ -92,7 +92,14 @@
     boolean? writable;
   };
 
-// Change to an entry within a tracked directory.
+  // Represents a mounted volume, which can be accessed via <code>chrome.
+  // fileSystem.requestFileSystem</code>.
+  [nodoc] dictionary Volume {
+    DOMString volumeId;
+    boolean writable;
+  };
+
+  // Change to an entry within a tracked directory.
   [nodoc] dictionary ChildChange {
     [instanceOf=Entry] object entry;
     ChildChangeType type;
@@ -124,7 +131,8 @@
   [nodoc] callback GetObservedEntriesCallback = void (
       [instanceOf=Entry] object[] entries);
   [nodoc] callback RequestFileSystemCallback = void(
-      [instanceOf=FileSystem] optional object fileSystem);
+      [instanceOf=FileSystem] object fileSystem);
+  [nodoc] callback GetVolumeListCallback = void(Volume[] volumes);
 
   interface Functions {
     // Get the display path of an Entry object. The display path is based on
@@ -175,6 +183,11 @@
     [nodoc] static void requestFileSystem(RequestFileSystemOptions options,
                                           RequestFileSystemCallback callback);
 
+    // Returns a list of volumes available for <code>requestFileSystem()</code>.
+    // The <code>"fileSystem": {"requestFileSystem"}</code> manifest permission
+    // is required. Available to kiosk apps running in the kiosk session only.
+    [nodoc] static void getVolumeList(GetVolumeListCallback callback);
+
     // Observes a directory entry. Emits an event if the tracked directory is
     // changed (including the list of files on it), or removed. If <code>
     // recursive</code> is set to true, then also all accessible subdirectories
diff --git a/chrome/renderer/resources/extensions/file_system_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
index 5553ae93..62a9ad68 100644
--- a/chrome/renderer/resources/extensions/file_system_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
@@ -94,6 +94,16 @@
         [fileSystem]);
   });
 
+  apiFunctions.setCustomCallback('getVolumeList',
+      function(name, request, callback, response) {
+    var volumeList = response || null;
+    sendRequest.safeCallbackApply(
+        'fileSystem.getVolumeList',
+        request,
+        callback,
+        [volumeList]);
+  });
+
   // TODO(benwells): Remove these deprecated versions of the functions.
   fileSystem.getWritableFileEntry = function() {
     console.log("chrome.fileSystem.getWritableFileEntry is deprecated");
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem b/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem
new file mode 100644
index 0000000..2fb7246
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxDEYtIfEohWkM
+MFDQsQTYRfVNI7byeeceWdtShDMPJumwgsnLO7uGytFLotki2sbZFys+kvkppTYI
+RuyZw3Bk5lC5L85Wn5EeMTxFbdcBbauYcqHCNiD2WAbeBVp/PZjTvwIT7/cDxuUo
+QBL2n70IfmxdP5WDvX7Bb+YoFtiEZM5T2si10rXVfwChcMx6WDJfCakltAca+E29
+ZfzRZEvgiVKycwiWe/mVc4REQ7GR9B7B7CYAMYfKN6AYWRJ31UuGoPxSyEAbS5BO
+j054mnpVuoRt6DEElU24X99Mz2imx221yRykNeB2o1YVq/sbB/jtiINon4mx09IC
+hyH/TV+ZAgMBAAECggEAfaQkOOsZZJQoVAoFj9PPiFs9FRz/O1ve8974kbpXMa1/
+sU9fPOaK0cEkffR2+xEeg/i5K5LJVxBzI7SROx1CqZf4OTL/zuE17qMqDmtAZTca
+yviualBXW/pkBTLvYdSom7u1Ecj9FqUTAllWG8aIM3rkE9iHlhHn3gY24sQzqt9g
+Acs1eVk+mbHl0DbrRGIrd8+ruBbawG9z3rxEpNc+s9Y1EDQnB6WSA0X92aB1Ilny
+G048dhylfTksH6M5Di3fXRtmSDKi1jV4SV+zRJMDP986UpDV3UkkaeJ/B4SZdrkg
+g90skXmnwvBr4bXvdB7IenTMJbZrRxuMZ3h3dW5m8QKBgQDiUgV6C3XLTuAdu6T3
+UQh0oL6pZzqv7ZMr0i9JLWmT2/JrqTI6OpBFOXE9yllkG7bwfzV7WTcs5ybbXWHv
+TvS/VcDaOOeokerz8JZNXwqIaQO9HrvevMZHZQHQtJzNw2u4Q25vi58q4vB0smPj
+anFy3GfAvPPPSRvfc6rBawex3wKBgQDIRBa/ey+gPnGe34+pPWffmH5H5b1LKNjm
+ZJMXazJAASCvNTzkIrzGkDwhtXA+QRI+XuTy+oHPap0rBSsu9BWlVzy4mCp3eWQN
+yWWuIrWHvUYwnDIcFU42MpEntKWIOIfQgEKWHfLkAlCT6vHdYCUbbaOverYX0mNW
+Chx6CxHNhwKBgQDUVoAs8XOjPG2pd9Re9fgo9GfuKJw3U38xLhKPZbwYrdPUjvpB
+B5E0YaCNiLw14IrTOYbEJABQcM9UIVkxXbLjkWFPXPR8g+sc1C0wimsncN/BIITD
+hfnCIlKBrfMwWplGWH3UyfqcEi/oTTbKt6OZUJFHlABsCvvLuooKzpB5oQKBgDxH
+MkmkPGuRIAXf6I/aKb/FWI0ve1B6FP8T2qo7274kGMBj19YbFpL1qwPCZux2DZW0
+Xlk8SYIy5ueiAKN7WGCR53bwZifb49+6dN57GASpVc0f1n1ZdFcf1U0MNJ7R1R9O
+27vve8JhZ/t9xhsJ62FcGN6ioth8vOWS2YtqdYtVAoGBANFPCmGbukdqO67btQ+h
+mqfX+KrIo1RGttHwx7FBH2o8n4Za0o7LX86CMMDWQhp4DDNOIDpv4ypVUN0/gAJH
+EQ2Hsy2wjqcxfc/1DlDPShX9UB+0dLce10FjOsMchLAzTHB7COC4NRA7lY3iqhFx
+4AeBbnvdJAaigqw7ehkKuHht
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js b/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js
new file mode 100644
index 0000000..c9447e8c
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js
@@ -0,0 +1,15 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+  function getVolumeList() {
+    chrome.fileSystem.getVolumeList(
+        chrome.test.callbackPass(function(volumeList) {
+          // Drive is not exposed in kiosk session.
+          chrome.test.assertEq(1, volumeList.length);
+          chrome.test.assertEq('downloads:Downloads', volumeList[0].volumeId);
+          chrome.test.assertTrue(volumeList[0].writable);
+        }));
+  }
+]);
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json b/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json
new file mode 100644
index 0000000..18879f8fb
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session",
+  "version": "0.1",
+  "description": "Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.",
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "kiosk_only": true,
+  "kiosk_enabled": true,
+  "permissions": [
+    {
+      "fileSystem": ["requestFileSystem"]
+    }
+  ]
+}
diff --git a/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx
new file mode 100644
index 0000000..b6180d6
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx
Binary files differ
diff --git a/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers
new file mode 100644
index 0000000..707bb1f
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-chrome-extension
diff --git a/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko b/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko
new file mode 100644
index 0000000..3e5878d
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko
@@ -0,0 +1,11 @@
+{
+  "id": "aaedpojejpghjkedenggihopfhfijcko",
+  "users": "1234",
+  "average_rating": 1.0,
+  "rating_count": 999,
+  "verified_site": "chrome.google.com",
+  "localized_name": "chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session",
+  "localized_description": "Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.",
+  "icon_url": "webstore/inlineinstall/detail/app_1_green16x16.png",
+  "manifest": "{ \"name\": \"chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session\", \"version\": \"0.1\", \"description\": \"Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.\", \"app\": { \"background\": { \"scripts\": [\"background.js\"] } }, \"kiosk_only\": true, \"kiosk_enabled\": true, \"permissions\": [ { \"fileSystem\": [\"requestFileSystem\"] } ] }"
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js b/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js
new file mode 100644
index 0000000..44c89ab0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js
@@ -0,0 +1,26 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+  function getVolumeList() {
+    chrome.fileSystem.getVolumeList(
+        chrome.test.callbackPass(function(volumeList) {
+          // Drive is not available in a real kiosk session. Hhowever, this test
+          // runs in a normal session (with a user marked as kiosk user) since
+          // executing a real real kiosk session is tests is very complicated.
+          // Whether Drive is available in the real kiosk session is tested
+          // separetely in: chrome/browser/chromeos/login/kiosk_browsertest.cc.
+          chrome.test.assertEq(4, volumeList.length);
+          chrome.test.assertEq('downloads:Downloads', volumeList[0].volumeId);
+          chrome.test.assertTrue(volumeList[0].writable);
+          chrome.test.assertEq('drive:drive-user', volumeList[1].volumeId);
+          chrome.test.assertTrue(volumeList[1].writable);
+
+          chrome.test.assertEq('testing:read-only', volumeList[2].volumeId);
+          chrome.test.assertFalse(volumeList[2].writable);
+          chrome.test.assertEq('testing:writable', volumeList[3].volumeId);
+          chrome.test.assertTrue(volumeList[3].writable);
+        }));
+  }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json b/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json
new file mode 100644
index 0000000..af3d483
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "chrome.fileSystem.getVolumeList test",
+  "version": "0.1",
+  "description": "Test for getting a list of available volumes via chrome.fileSystem.getVolumeList.",
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "kiosk_only": true,
+  "kiosk_enabled": true,
+  "permissions": [
+    {
+      "fileSystem": ["requestFileSystem"]
+    }
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js
new file mode 100644
index 0000000..dcca20f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+  function getVolumeList() {
+    chrome.fileSystem.getVolumeList(
+        chrome.test.callbackFail('Operation only supported for kiosk apps ' +
+            'running in a kiosk session.',
+            function(volumeList) {
+              chrome.test.assertFalse(!!volumeList);
+            }));
+  }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json
new file mode 100644
index 0000000..3a37053
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "chrome.fileSystem.getVolumeList not in kiosk session test",
+  "version": "0.1",
+  "description": "Test for getting a list of available volumes via chrome.fileSystem.getVolumeList when not running in the kiosk session.",
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "permissions": [
+    {
+      "fileSystem": ["requestFileSystem"]
+    }
+  ]
+}
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 09707ce..3eb1b99 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1056,6 +1056,7 @@
   LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS,
   DATAREDUCTIONPROXY_CLEARDATASAVINGS,
   BLUETOOTHPRIVATE_SETDISCOVERYFILTER,
+  FILESYSTEM_GETVOLUMELIST,
   // Last entry: Add new entries above and ensure to update
   // tools/metrics/histograms/histograms.xml.
   ENUM_BOUNDARY
diff --git a/third_party/closure_compiler/externs/chrome_extensions.js b/third_party/closure_compiler/externs/chrome_extensions.js
index 713f7d1..40a89bf5 100644
--- a/third_party/closure_compiler/externs/chrome_extensions.js
+++ b/third_party/closure_compiler/externs/chrome_extensions.js
@@ -6994,6 +6994,21 @@
 
 
 /**
+ * @see http://developer.chrome.com/apps/fileSystem.html#method-getVolumeList
+ * @constructor
+ */
+chrome.fileSystem.Volume = function() {};
+
+
+/** @type {string} */
+chrome.fileSystem.Volume.prototype.volumeId;
+
+
+/** @type {boolean} */
+chrome.fileSystem.Volume.prototype.writable;
+
+
+/**
  * @param {!chrome.fileSystem.ChooseEntryOptions|
  *     function(Entry=, !Array.<!FileEntry>=)} optionsOrCallback The
  *     options for the file prompt or the callback.
@@ -7032,13 +7047,21 @@
 /**
  * @param {!chrome.fileSystem.RequestFileSystemOptions} options Options for the
  *     request.
- * @param {function(!FileSystem=)} callback A completion callback.
+ * @param {function(FileSystem)} callback A completion callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-requestFileSystem
  */
 chrome.fileSystem.requestFileSystem = function(options, callback) {};
 
 
 /**
+ * @param {function(Array<!chrome.fileSystem.Volume>)} callback A completion
+ *     callback.
+ * @see http://developer.chrome.com/apps/fileSystem.html#method-getVolumeList
+ */
+chrome.fileSystem.getVolumeList = function(callback) {};
+
+
+/**
  * @const
  * @see https://developer.chrome.com/apps/syncFileSystem
  */
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index bd419fd..5d22fb2 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -50228,6 +50228,7 @@
   <int value="995" label="LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS"/>
   <int value="996" label="DATAREDUCTIONPROXY_CLEARDATASAVINGS"/>
   <int value="997" label="BLUETOOTHPRIVATE_SETDISCOVERYFILTER"/>
+  <int value="998" label="FILESYSTEM_GETVOLUMELIST"/>
 </enum>
 
 <enum name="ExtensionInstallCause" type="int">