Make DataOffer::SetDropData handle filesystem:// URLs.

Below are the steps for converting Chrome-specific filesystem:// URLs into ARC URLs.
1) Extract filesystem URL(s) from pickle.
  -> "filesystem:chrome-extension://hhao.../external/drive-.../root/...."
2) Crack URL with FileSystemContext.
  -> "/special/drive-.../root/...."
3) Convert to Arc URL
  -> "content://org.chromium.arc.chromecontentprovider/externalfile%3Adrive-.../root/...."

Bug: chromium:767982
Test: out/Default/exo_unittests --gtest_filter="DataOffer.*"
Change-Id: I9fc803f69acd0d3573c25668ca5786d7e6002501
Reviewed-on: https://chromium-review.googlesource.com/867807
Commit-Queue: Satoshi Niwa <[email protected]>
Reviewed-by: David Reveman <[email protected]>
Reviewed-by: Ryo Hashimoto <[email protected]>
Cr-Commit-Position: refs/heads/master@{#532756}
diff --git a/chrome/browser/exo_parts.cc b/chrome/browser/exo_parts.cc
index 64528ee..6383a0c 100644
--- a/chrome/browser/exo_parts.cc
+++ b/chrome/browser/exo_parts.cc
@@ -14,20 +14,44 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
+#include "chrome/browser/chromeos/file_manager/app_id.h"
+#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/exo/display.h"
 #include "components/exo/file_helper.h"
 #include "components/exo/wayland/server.h"
 #include "components/exo/wm_helper.h"
+#include "components/user_manager/user_manager.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/common/drop_data.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
 #include "ui/arc/notification/arc_notification_surface_manager_impl.h"
 
 namespace {
 
 constexpr char kMimeTypeArcUriList[] = "application/x-arc-uri-list";
 
+storage::FileSystemContext* GetFileSystemContext() {
+  // Obtains the primary profile.
+  if (!user_manager::UserManager::IsInitialized())
+    return nullptr;
+  const user_manager::User* primary_user =
+      user_manager::UserManager::Get()->GetPrimaryUser();
+  if (!primary_user)
+    return nullptr;
+  Profile* primary_profile =
+      chromeos::ProfileHelper::Get()->GetProfileByUser(primary_user);
+  if (!primary_profile)
+    return nullptr;
+
+  return file_manager::util::GetFileSystemContextForExtensionId(
+      primary_profile, file_manager::kFileManagerAppId);
+}
+
 class ChromeFileHelper : public exo::FileHelper {
  public:
   ChromeFileHelper() {}
@@ -42,10 +66,30 @@
                       GURL* out) override {
     return file_manager::util::ConvertPathToArcUrl(path, out);
   }
-  bool GetUrlFromFileSystemUrl(const std::string& app_id,
-                               const GURL& url,
-                               GURL* out) override {
-    return false;
+  bool GetUrlsFromPickle(const std::string& app_id,
+                         const base::Pickle& pickle,
+                         std::vector<GURL>* out_urls) override {
+    storage::FileSystemContext* file_system_context = GetFileSystemContext();
+    if (!file_system_context)
+      return false;
+
+    std::vector<content::DropData::FileSystemFileInfo> file_system_files;
+    if (!content::DropData::FileSystemFileInfo::ReadFileSystemFilesFromPickle(
+            pickle, &file_system_files))
+      return false;
+
+    for (const auto& file_system_file : file_system_files) {
+      const storage::FileSystemURL file_system_url =
+          file_system_context->CrackURL(file_system_file.url);
+      GURL out_url;
+      // TODO(niwa): Check that app_id is an Arc app once the caller
+      //             (exo::DataOffer) starts filling app_id.
+      if (file_manager::util::ConvertPathToArcUrl(file_system_url.path(),
+                                                  &out_url)) {
+        out_urls->push_back(out_url);
+      }
+    }
+    return !out_urls->empty();
   }
 };
 
diff --git a/components/exo/data_device_unittest.cc b/components/exo/data_device_unittest.cc
index e721cfd..dd77d1c 100644
--- a/components/exo/data_device_unittest.cc
+++ b/components/exo/data_device_unittest.cc
@@ -114,9 +114,9 @@
                       GURL* out) override {
     return true;
   }
-  bool GetUrlFromFileSystemUrl(const std::string& app_id,
-                               const GURL& url,
-                               GURL* out) override {
+  bool GetUrlsFromPickle(const std::string& app_id,
+                         const base::Pickle& pickle,
+                         std::vector<GURL>* out_urls) override {
     return false;
   }
 
diff --git a/components/exo/data_offer.cc b/components/exo/data_offer.cc
index 13fca50..c0239ae 100644
--- a/components/exo/data_offer.cc
+++ b/components/exo/data_offer.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/pickle.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task_scheduler/post_task.h"
 #include "components/exo/data_offer_delegate.h"
@@ -52,6 +53,49 @@
     DLOG(ERROR) << "Failed to write drop data";
 }
 
+// Gets a comma-separated list of urls extracted from |data|->file.
+bool GetUrlListFromDataFile(FileHelper* file_helper,
+                            const ui::OSExchangeData& data,
+                            base::string16* url_list_string) {
+  if (!data.HasFile())
+    return false;
+  std::vector<ui::FileInfo> files;
+  if (data.GetFilenames(&files)) {
+    for (const auto& info : files) {
+      GURL url;
+      // TODO(niwa): Need to fill the correct app_id.
+      if (file_helper->GetUrlFromPath(/* app_id */ "", info.path, &url)) {
+        if (!url_list_string->empty())
+          *url_list_string += base::UTF8ToUTF16(kUriListSeparator);
+        *url_list_string += base::UTF8ToUTF16(url.spec());
+      }
+    }
+  }
+  return !url_list_string->empty();
+}
+
+// Gets a comma-separated list of urls extracted from |data|->pickle.
+bool GetUrlListFromDataPickle(FileHelper* file_helper,
+                              const ui::OSExchangeData& data,
+                              base::string16* url_list_string) {
+  static const char kFormatString[] = "chromium/x-file-system-files";
+  CR_DEFINE_STATIC_LOCAL(ui::Clipboard::FormatType, formatType,
+                         (ui::Clipboard::GetFormatType(kFormatString)));
+  base::Pickle pickle;
+  if (!data.GetPickledData(formatType, &pickle))
+    return false;
+  std::vector<GURL> app_urls;
+  // TODO(niwa): Need to fill the correct app_id.
+  if (file_helper->GetUrlsFromPickle(/* app_id */ "", pickle, &app_urls)) {
+    for (const GURL& app_url : app_urls) {
+      if (!url_list_string->empty())
+        *url_list_string += base::UTF8ToUTF16(kUriListSeparator);
+      *url_list_string += base::UTF8ToUTF16(app_url.spec());
+    }
+  }
+  return !url_list_string->empty();
+}
+
 }  // namespace
 
 DataOffer::DataOffer(DataOfferDelegate* delegate) : delegate_(delegate) {}
@@ -102,30 +146,21 @@
 void DataOffer::SetDropData(FileHelper* file_helper,
                             const ui::OSExchangeData& data) {
   DCHECK_EQ(0u, data_.size());
-  if (data.HasString()) {
+
+  base::string16 url_list_string;
+  bool found_urls =
+      GetUrlListFromDataFile(file_helper, data, &url_list_string) ||
+      GetUrlListFromDataPickle(file_helper, data, &url_list_string);
+  if (found_urls) {
+    data_.emplace(file_helper->GetMimeTypeForUriList(),
+                  RefCountedString16::TakeString(std::move(url_list_string)));
+  } else if (data.HasString()) {
     base::string16 string_content;
     if (data.GetString(&string_content)) {
       data_.emplace(std::string(ui::Clipboard::kMimeTypeText),
                     RefCountedString16::TakeString(std::move(string_content)));
     }
   }
-  if (data.HasFile()) {
-    std::vector<ui::FileInfo> files;
-    if (data.GetFilenames(&files)) {
-      base::string16 url_list;
-      for (const auto& info : files) {
-        GURL url;
-        // TODO(hirono): Need to fill the corret app_id.
-        if (file_helper->GetUrlFromPath(/* app_id */ "", info.path, &url)) {
-          if (!url_list.empty())
-            url_list += base::UTF8ToUTF16(kUriListSeparator);
-          url_list += base::UTF8ToUTF16(url.spec());
-        }
-      }
-      data_.emplace(file_helper->GetMimeTypeForUriList(),
-                    RefCountedString16::TakeString(std::move(url_list)));
-    }
-  }
   for (const auto& pair : data_) {
     delegate_->OnOffer(pair.first);
   }
diff --git a/components/exo/data_offer_unittest.cc b/components/exo/data_offer_unittest.cc
index 0c8976ec..59508d3 100644
--- a/components/exo/data_offer_unittest.cc
+++ b/components/exo/data_offer_unittest.cc
@@ -77,10 +77,13 @@
     *out = GURL("file://" + path.AsUTF8Unsafe());
     return true;
   }
-  bool GetUrlFromFileSystemUrl(const std::string& app_id,
-                               const GURL& url,
-                               GURL* out) override {
-    return false;
+  bool GetUrlsFromPickle(const std::string& app_id,
+                         const base::Pickle& pickle,
+                         std::vector<GURL>* out_urls) override {
+    // TODO(niwa): Check app_id once we start filling app_id in DataOffer.
+    out_urls->push_back(
+        GURL("content://org.chromium.arc.chromecontentprovider/path/to/file1"));
+    return true;
   }
 
  private:
@@ -165,6 +168,26 @@
   EXPECT_EQ("text/uri-list", delegate.mime_types()[0]);
 }
 
+TEST_F(DataOfferTest, SetPickleDropData) {
+  TestDataOfferDelegate delegate;
+  DataOffer data_offer(&delegate);
+
+  TestFileHelper file_helper;
+  ui::OSExchangeData data;
+
+  base::Pickle pickle;
+  pickle.WriteUInt32(1);  // num files
+  pickle.WriteString("filesystem:chrome-extension://path/to/file1");
+  pickle.WriteInt64(1000);   // file size
+  pickle.WriteString("id");  // filesystem id
+  data.SetPickledData(
+      ui::Clipboard::GetFormatType("chromium/x-file-system-files"), pickle);
+  data_offer.SetDropData(&file_helper, data);
+
+  EXPECT_EQ(1u, delegate.mime_types().size());
+  EXPECT_EQ("text/uri-list", delegate.mime_types()[0]);
+}
+
 TEST_F(DataOfferTest, ReceiveString) {
   TestDataOfferDelegate delegate;
   DataOffer data_offer(&delegate);
@@ -203,6 +226,35 @@
   EXPECT_EQ(base::ASCIIToUTF16("file:///test/downloads/file"), result);
 }
 
+TEST_F(DataOfferTest, ReceiveUriListFromPickle) {
+  TestDataOfferDelegate delegate;
+  DataOffer data_offer(&delegate);
+
+  TestFileHelper file_helper;
+  ui::OSExchangeData data;
+
+  base::Pickle pickle;
+  pickle.WriteUInt32(1);  // num files
+  pickle.WriteString("filesystem:chrome-extension://path/to/file1");
+  pickle.WriteInt64(1000);   // file size
+  pickle.WriteString("id");  // filesystem id
+  data.SetPickledData(
+      ui::Clipboard::GetFormatType("chromium/x-file-system-files"), pickle);
+  data_offer.SetDropData(&file_helper, data);
+
+  base::ScopedFD read_pipe;
+  base::ScopedFD write_pipe;
+  CreatePipe(&read_pipe, &write_pipe);
+
+  data_offer.Receive("text/uri-list", std::move(write_pipe));
+  base::string16 result;
+  ASSERT_TRUE(ReadString16(std::move(read_pipe), &result));
+  EXPECT_EQ(
+      base::ASCIIToUTF16(
+          "content://org.chromium.arc.chromecontentprovider/path/to/file1"),
+      result);
+}
+
 TEST_F(DataOfferTest, SetClipboardData) {
   TestDataOfferDelegate delegate;
   DataOffer data_offer(&delegate);
diff --git a/components/exo/display_unittest.cc b/components/exo/display_unittest.cc
index cf56b55..bd0836d 100644
--- a/components/exo/display_unittest.cc
+++ b/components/exo/display_unittest.cc
@@ -232,9 +232,9 @@
                       GURL* out) override {
     return true;
   }
-  bool GetUrlFromFileSystemUrl(const std::string& app_id,
-                               const GURL& url,
-                               GURL* out) override {
+  bool GetUrlsFromPickle(const std::string& app_id,
+                         const base::Pickle& pickle,
+                         std::vector<GURL>* out_urls) override {
     return false;
   }
 };
diff --git a/components/exo/file_helper.h b/components/exo/file_helper.h
index 28702db..b1dfb5ba 100644
--- a/components/exo/file_helper.h
+++ b/components/exo/file_helper.h
@@ -33,11 +33,12 @@
                               const base::FilePath& path,
                               GURL* out) = 0;
 
-  // Converts filesystem:// URL to something that applications can understand.
-  // e.g. content:// URI for Android apps.
-  virtual bool GetUrlFromFileSystemUrl(const std::string& app_id,
-                                       const GURL& file_system_url,
-                                       GURL* out) = 0;
+  // Takes in |pickle| constructed by the web contents view, reads filesystem
+  // URLs from it and converts the URLs to something that applications can
+  // understand.  e.g. content:// URI for Android apps.
+  virtual bool GetUrlsFromPickle(const std::string& app_id,
+                                 const base::Pickle& pickle,
+                                 std::vector<GURL>* out_urls) = 0;
 };
 
 }  // namespace exo