summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/BUILD.gn1
-rw-r--r--base/android/content_uri_utils.cc10
-rw-r--r--base/android/content_uri_utils.h10
-rw-r--r--base/android/content_uri_utils_unittest.cc37
-rw-r--r--base/android/java/src/org/chromium/base/ContentUriUtils.java42
-rw-r--r--base/base.gyp1
-rw-r--r--chrome/browser/profiles/profile_io_data.cc9
-rw-r--r--content/browser/android/content_protocol_handler_impl.cc43
-rw-r--r--content/browser/android/content_protocol_handler_impl.h45
-rw-r--r--content/browser/android/url_request_content_job.cc228
-rw-r--r--content/browser/android/url_request_content_job.h103
-rw-r--r--content/browser/android/url_request_content_job_unittest.cc198
-rw-r--r--content/content_browser.gypi9
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/public/browser/android/content_protocol_handler.h32
-rw-r--r--content/test/data/android/red.pngbin0 -> 173 bytes
-rw-r--r--content/test/run_all_unittests.cc9
-rw-r--r--url/url_constants.cc1
-rw-r--r--url/url_constants.h2
19 files changed, 763 insertions, 18 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn
index a37b32d..59ffdaf 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1120,6 +1120,7 @@ source_set("protect_file_posix") {
test("base_unittests") {
sources = [
"android/application_status_listener_unittest.cc",
+ "android/content_uri_utils_unittest.cc",
"android/jni_android_unittest.cc",
"android/jni_array_unittest.cc",
"android/jni_string_unittest.cc",
diff --git a/base/android/content_uri_utils.cc b/base/android/content_uri_utils.cc
index 0e0c0ea..0482fee 100644
--- a/base/android/content_uri_utils.cc
+++ b/base/android/content_uri_utils.cc
@@ -35,4 +35,14 @@ File OpenContentUriForRead(const FilePath& content_uri) {
return File(fd);
}
+std::string GetContentUriMimeType(const FilePath& content_uri) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_uri =
+ ConvertUTF8ToJavaString(env, content_uri.value());
+ ScopedJavaLocalRef<jstring> j_mime =
+ Java_ContentUriUtils_getMimeType(
+ env, base::android::GetApplicationContext(), j_uri.obj());
+ return base::android::ConvertJavaStringToUTF8(env, j_mime.obj());
+}
+
} // namespace base
diff --git a/base/android/content_uri_utils.h b/base/android/content_uri_utils.h
index 827ec92..e66b770 100644
--- a/base/android/content_uri_utils.h
+++ b/base/android/content_uri_utils.h
@@ -16,13 +16,17 @@ namespace base {
bool RegisterContentUriUtils(JNIEnv* env);
-// Opens a content uri for read and returns the file descriptor to the caller.
-// Returns -1 if the uri is invalid.
+// Opens a content URI for read and returns the file descriptor to the caller.
+// Returns -1 if the URI is invalid.
BASE_EXPORT File OpenContentUriForRead(const FilePath& content_uri);
-// Check whether a content uri exists.
+// Check whether a content URI exists.
BASE_EXPORT bool ContentUriExists(const FilePath& content_uri);
+// Gets MIME type from a content URI. Returns an empty string if the URI is
+// invalid.
+BASE_EXPORT std::string GetContentUriMimeType(const FilePath& content_uri);
+
} // namespace base
#endif // BASE_ANDROID_CONTENT_URI_UTILS_H_
diff --git a/base/android/content_uri_utils_unittest.cc b/base/android/content_uri_utils_unittest.cc
new file mode 100644
index 0000000..c762035
--- /dev/null
+++ b/base/android/content_uri_utils_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 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.
+
+#include "base/android/content_uri_utils.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/test/test_file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+TEST(ContentUriUtilsTest, ContentUriMimeTest) {
+ // Get the test image path.
+ FilePath data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir));
+ data_dir = data_dir.AppendASCII("file_util");
+ ASSERT_TRUE(PathExists(data_dir));
+ FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png"));
+
+ // Insert the image into MediaStore. MediaStore will do some conversions, and
+ // return the content URI.
+ FilePath path = base::InsertImageIntoMediaStore(image_file);
+ EXPECT_TRUE(path.IsContentUri());
+ EXPECT_TRUE(PathExists(path));
+
+ std::string mime = GetContentUriMimeType(path);
+ EXPECT_EQ(mime, std::string("image/png"));
+
+ FilePath invalid_path("content://foo.bar");
+ mime = GetContentUriMimeType(invalid_path);
+ EXPECT_TRUE(mime.empty());
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
index 70c27ed..ef527e1 100644
--- a/base/android/java/src/org/chromium/base/ContentUriUtils.java
+++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -12,6 +12,7 @@ import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
+import java.io.FileNotFoundException;
/**
* This class provides methods to access content URI schemes.
@@ -26,11 +27,11 @@ public abstract class ContentUriUtils {
*/
public interface FileProviderUtil {
/**
- * Generate a content uri from the given file.
+ * Generate a content URI from the given file.
* @param context Application context.
* @param file The file to be translated.
*/
- public Uri getContentUriFromFile(Context context, File file);
+ Uri getContentUriFromFile(Context context, File file);
}
// Prevent instantiation.
@@ -54,7 +55,7 @@ public abstract class ContentUriUtils {
*
* @param context {@link Context} in interest
* @param uriString the content URI to open
- * @returns file desciptor upon sucess, or -1 otherwise.
+ * @return file desciptor upon sucess, or -1 otherwise.
*/
@CalledByNative
public static int openContentUriForRead(Context context, String uriString) {
@@ -70,23 +71,34 @@ public abstract class ContentUriUtils {
*
* @param context {@link Context} in interest.
* @param uriString the content URI to query.
- * @returns true if the uri exists, or false otherwise.
+ * @return true if the URI exists, or false otherwise.
*/
@CalledByNative
public static boolean contentUriExists(Context context, String uriString) {
- ParcelFileDescriptor pfd = getParcelFileDescriptor(context, uriString);
- if (pfd == null) {
- return false;
- }
- return true;
+ return getParcelFileDescriptor(context, uriString) != null;
+ }
+
+ /**
+ * Retrieve the MIME type for the content URI.
+ *
+ * @param context {@link Context} in interest.
+ * @param uriString the content URI to look up.
+ * @return MIME type or null if the input params are empty or invalid.
+ */
+ @CalledByNative
+ public static String getMimeType(Context context, String uriString) {
+ ContentResolver resolver = context.getContentResolver();
+ if (resolver == null) return null;
+ Uri uri = Uri.parse(uriString);
+ return resolver.getType(uri);
}
/**
- * Helper method to open a content URI and return the ParcelFileDescriptor.
+ * Helper method to open a content URI and returns the ParcelFileDescriptor.
*
* @param context {@link Context} in interest.
* @param uriString the content URI to open.
- * @returns ParcelFileDescriptor of the content URI, or NULL if the file does not exist.
+ * @return ParcelFileDescriptor of the content URI, or NULL if the file does not exist.
*/
private static ParcelFileDescriptor getParcelFileDescriptor(Context context, String uriString) {
ContentResolver resolver = context.getContentResolver();
@@ -95,8 +107,12 @@ public abstract class ContentUriUtils {
ParcelFileDescriptor pfd = null;
try {
pfd = resolver.openFileDescriptor(uri, "r");
- } catch (java.io.FileNotFoundException e) {
+ } catch (FileNotFoundException e) {
Log.w(TAG, "Cannot find content uri: " + uriString, e);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Cannot open content uri: " + uriString, e);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Unknown content uri: " + uriString, e);
}
return pfd;
}
@@ -107,7 +123,7 @@ public abstract class ContentUriUtils {
* @param uri the content URI to be resolved.
* @param contentResolver the content resolver to query.
* @param columnField the column field to query.
- * @returns the display name of the @code uri if present in the database
+ * @return the display name of the @code uri if present in the database
* or an empty string otherwise.
*/
public static String getDisplayName(
diff --git a/base/base.gyp b/base/base.gyp
index 188b9e7..64f88db 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -447,6 +447,7 @@
'type': '<(gtest_target_type)',
'sources': [
'android/application_status_listener_unittest.cc',
+ 'android/content_uri_utils_unittest.cc',
'android/jni_android_unittest.cc',
'android/jni_array_unittest.cc',
'android/jni_string_unittest.cc',
diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc
index 72e8d92..0868b90 100644
--- a/chrome/browser/profiles/profile_io_data.cc
+++ b/chrome/browser/profiles/profile_io_data.cc
@@ -112,6 +112,7 @@
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h"
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings_factory.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
+#include "content/public/browser/android/content_protocol_handler.h"
#endif // defined(OS_ANDROID)
#if defined(OS_CHROMEOS)
@@ -1152,6 +1153,14 @@ scoped_ptr<net::URLRequestJobFactory> ProfileIOData::SetUpJobFactoryDefaults(
DCHECK(set_protocol);
}
#endif // defined(OS_CHROMEOS)
+#if defined(OS_ANDROID)
+ set_protocol = job_factory->SetProtocolHandler(
+ url::kContentScheme,
+ content::ContentProtocolHandler::Create(
+ content::BrowserThread::GetBlockingPool()->
+ GetTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)));
+#endif
job_factory->SetProtocolHandler(
url::kAboutScheme, new chrome_browser_net::AboutProtocolHandler());
diff --git a/content/browser/android/content_protocol_handler_impl.cc b/content/browser/android/content_protocol_handler_impl.cc
new file mode 100644
index 0000000..f5e5d20
--- /dev/null
+++ b/content/browser/android/content_protocol_handler_impl.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2014 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.
+
+#include "content/browser/android/content_protocol_handler_impl.h"
+
+#include "base/task_runner.h"
+#include "content/browser/android/url_request_content_job.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+
+namespace content {
+
+// static
+ContentProtocolHandler* ContentProtocolHandler::Create(
+ const scoped_refptr<base::TaskRunner>& content_task_runner) {
+ return new ContentProtocolHandlerImpl(content_task_runner);
+}
+
+ContentProtocolHandlerImpl::ContentProtocolHandlerImpl(
+ const scoped_refptr<base::TaskRunner>& content_task_runner)
+ : content_task_runner_(content_task_runner) {}
+
+ContentProtocolHandlerImpl::~ContentProtocolHandlerImpl() {}
+
+net::URLRequestJob* ContentProtocolHandlerImpl::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ if (!network_delegate) {
+ return new net::URLRequestErrorJob(
+ request, network_delegate, net::ERR_ACCESS_DENIED);
+ }
+ return new URLRequestContentJob(
+ request, network_delegate, base::FilePath(request->url().spec()),
+ content_task_runner_);
+}
+
+bool ContentProtocolHandlerImpl::IsSafeRedirectTarget(
+ const GURL& location) const {
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/android/content_protocol_handler_impl.h b/content/browser/android/content_protocol_handler_impl.h
new file mode 100644
index 0000000..f593a84
--- /dev/null
+++ b/content/browser/android/content_protocol_handler_impl.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2014 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.
+
+#ifndef CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/android/content_protocol_handler.h"
+
+class GURL;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+class NetworkDelegate;
+class URLRequestJob;
+}
+
+namespace content {
+
+// Implements a ProtocolHandler for content scheme jobs. If |network_delegate_|
+// is NULL, then all file requests will fail with ERR_ACCESS_DENIED.
+class ContentProtocolHandlerImpl : public ContentProtocolHandler {
+ public:
+ explicit ContentProtocolHandlerImpl(
+ const scoped_refptr<base::TaskRunner>& content_task_runner);
+ ~ContentProtocolHandlerImpl() override;
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+ bool IsSafeRedirectTarget(const GURL& location) const override;
+
+ private:
+ const scoped_refptr<base::TaskRunner> content_task_runner_;
+ DISALLOW_COPY_AND_ASSIGN(ContentProtocolHandlerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
diff --git a/content/browser/android/url_request_content_job.cc b/content/browser/android/url_request_content_job.cc
new file mode 100644
index 0000000..0ba13e0
--- /dev/null
+++ b/content/browser/android/url_request_content_job.cc
@@ -0,0 +1,228 @@
+// Copyright (c) 2014 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.
+
+#include "content/browser/android/url_request_content_job.h"
+
+#include "base/android/content_uri_utils.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/task_runner.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_error_job.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// TODO(qinmin): Refactor this class to reuse the common code in
+// url_request_file_job.cc.
+URLRequestContentJob::ContentMetaInfo::ContentMetaInfo()
+ : content_exists(false),
+ content_size(0) {
+}
+
+URLRequestContentJob::URLRequestContentJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& content_path,
+ const scoped_refptr<base::TaskRunner>& content_task_runner)
+ : net::URLRequestJob(request, network_delegate),
+ content_path_(content_path),
+ stream_(new net::FileStream(content_task_runner)),
+ content_task_runner_(content_task_runner),
+ remaining_bytes_(0),
+ io_pending_(false),
+ weak_ptr_factory_(this) {}
+
+void URLRequestContentJob::Start() {
+ ContentMetaInfo* meta_info = new ContentMetaInfo();
+ content_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&URLRequestContentJob::FetchMetaInfo, content_path_,
+ base::Unretained(meta_info)),
+ base::Bind(&URLRequestContentJob::DidFetchMetaInfo,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(meta_info)));
+}
+
+void URLRequestContentJob::Kill() {
+ stream_.reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ net::URLRequestJob::Kill();
+}
+
+bool URLRequestContentJob::ReadRawData(net::IOBuffer* dest,
+ int dest_size,
+ int* bytes_read) {
+ DCHECK_GT(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ int rv = stream_->Read(dest,
+ dest_size,
+ base::Bind(&URLRequestContentJob::DidRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ make_scoped_refptr(dest)));
+ if (rv >= 0) {
+ // Data is immediately available.
+ *bytes_read = rv;
+ remaining_bytes_ -= rv;
+ DCHECK_GE(remaining_bytes_, 0);
+ return true;
+ }
+
+ // Otherwise, a read error occured. We may just need to wait...
+ if (rv == net::ERR_IO_PENDING) {
+ io_pending_ = true;
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+bool URLRequestContentJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ return false;
+}
+
+bool URLRequestContentJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(request_);
+ if (!meta_info_.mime_type.empty()) {
+ *mime_type = meta_info_.mime_type;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestContentJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header))
+ return;
+
+ // We only care about "Range" header here.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests.
+ NotifyDone(net::URLRequestStatus(
+ net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
+ }
+}
+
+URLRequestContentJob::~URLRequestContentJob() {}
+
+void URLRequestContentJob::FetchMetaInfo(const base::FilePath& content_path,
+ ContentMetaInfo* meta_info) {
+ base::File::Info file_info;
+ meta_info->content_exists = base::GetFileInfo(content_path, &file_info);
+ if (meta_info->content_exists) {
+ meta_info->content_size = file_info.size;
+ meta_info->mime_type = base::GetContentUriMimeType(content_path);
+ }
+}
+
+void URLRequestContentJob::DidFetchMetaInfo(const ContentMetaInfo* meta_info) {
+ meta_info_ = *meta_info;
+
+ if (!meta_info_.content_exists) {
+ DidOpen(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ int flags = base::File::FLAG_OPEN |
+ base::File::FLAG_READ |
+ base::File::FLAG_ASYNC;
+ int rv = stream_->Open(content_path_, flags,
+ base::Bind(&URLRequestContentJob::DidOpen,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING)
+ DidOpen(rv);
+}
+
+void URLRequestContentJob::DidOpen(int result) {
+ if (result != net::OK) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ if (!byte_range_.ComputeBounds(meta_info_.content_size)) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) {
+ int rv = stream_->Seek(base::File::FROM_BEGIN,
+ byte_range_.first_byte_position(),
+ base::Bind(&URLRequestContentJob::DidSeek,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING) {
+ // stream_->Seek() failed, so pass an intentionally erroneous value
+ // into DidSeek().
+ DidSeek(-1);
+ }
+ } else {
+ // We didn't need to call stream_->Seek() at all, so we pass to DidSeek()
+ // the value that would mean seek success. This way we skip the code
+ // handling seek failure.
+ DidSeek(byte_range_.first_byte_position());
+ }
+}
+
+void URLRequestContentJob::DidSeek(int64 result) {
+ if (result != byte_range_.first_byte_position()) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ set_expected_content_size(remaining_bytes_);
+ NotifyHeadersComplete();
+}
+
+void URLRequestContentJob::DidRead(
+ scoped_refptr<net::IOBuffer> buf, int result) {
+ if (result > 0) {
+ SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+ }
+
+ DCHECK(io_pending_);
+ io_pending_ = false;
+
+ if (result == 0) {
+ NotifyDone(net::URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ }
+
+ NotifyReadComplete(result);
+}
+
+} // namespace content
diff --git a/content/browser/android/url_request_content_job.h b/content/browser/android/url_request_content_job.h
new file mode 100644
index 0000000..5d3d933
--- /dev/null
+++ b/content/browser/android/url_request_content_job.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2014 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.
+
+#ifndef CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
+#define CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace file_util {
+struct FileInfo;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace content {
+
+// A request job that handles reading content URIs
+class CONTENT_EXPORT URLRequestContentJob : public net::URLRequestJob {
+ public:
+ URLRequestContentJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& content_path,
+ const scoped_refptr<base::TaskRunner>& content_task_runner);
+
+ // net::URLRequestJob:
+ void Start() override;
+ void Kill() override;
+ bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override;
+ bool IsRedirectResponse(GURL* location, int* http_status_code) override;
+ bool GetMimeType(std::string* mime_type) const override;
+ void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
+
+ protected:
+ ~URLRequestContentJob() override;
+
+ private:
+ // Meta information about the content URI. It's used as a member in the
+ // URLRequestContentJob and also passed between threads because disk access is
+ // necessary to obtain it.
+ struct ContentMetaInfo {
+ ContentMetaInfo();
+ // Flag showing whether the content URI exists.
+ bool content_exists;
+ // Size of the content URI.
+ int64 content_size;
+ // Mime type associated with the content URI.
+ std::string mime_type;
+ };
+
+ // Fetches content URI info on a background thread.
+ static void FetchMetaInfo(const base::FilePath& content_path,
+ ContentMetaInfo* meta_info);
+
+ // Callback after fetching content URI info on a background thread.
+ void DidFetchMetaInfo(const ContentMetaInfo* meta_info);
+
+ // Callback after opening content URI on a background thread.
+ void DidOpen(int result);
+
+ // Callback after seeking to the beginning of |byte_range_| in the content URI
+ // on a background thread.
+ void DidSeek(int64 result);
+
+ // Callback after data is asynchronously read from the content URI into |buf|.
+ void DidRead(scoped_refptr<net::IOBuffer> buf, int result);
+
+ // The full path of the content URI.
+ base::FilePath content_path_;
+
+ scoped_ptr<net::FileStream> stream_;
+ ContentMetaInfo meta_info_;
+ const scoped_refptr<base::TaskRunner> content_task_runner_;
+
+ net::HttpByteRange byte_range_;
+ int64 remaining_bytes_;
+
+ bool io_pending_;
+
+ base::WeakPtrFactory<URLRequestContentJob> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContentJob);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
diff --git a/content/browser/android/url_request_content_job_unittest.cc b/content/browser/android/url_request_content_job_unittest.cc
new file mode 100644
index 0000000..e4b5f9e
--- /dev/null
+++ b/content/browser/android/url_request_content_job_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2014 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.
+
+#include "content/browser/android/url_request_content_job.h"
+
+#include <algorithm>
+
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_file_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+
+namespace content {
+
+namespace {
+
+// A URLRequestJobFactory that will return URLRequestContentJobWithCallbacks
+// instances for content:// scheme URLs.
+class CallbacksJobFactory : public net::URLRequestJobFactory {
+ public:
+ class JobObserver {
+ public:
+ virtual void OnJobCreated(URLRequestContentJob* job) = 0;
+ };
+
+ CallbacksJobFactory(const base::FilePath& path, JobObserver* observer)
+ : path_(path), observer_(observer) {
+ }
+
+ ~CallbacksJobFactory() override {}
+
+ net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ URLRequestContentJob* job =
+ new URLRequestContentJob(
+ request,
+ network_delegate,
+ path_,
+ const_cast<base::MessageLoop*>(&message_loop_)->task_runner());
+ observer_->OnJobCreated(job);
+ return job;
+ }
+
+ net::URLRequestJob* MaybeInterceptRedirect(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const GURL& location) const override {
+ return nullptr;
+ }
+
+ net::URLRequestJob* MaybeInterceptResponse(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return nullptr;
+ }
+
+ bool IsHandledProtocol(const std::string& scheme) const override {
+ return scheme == "content";
+ }
+
+ bool IsHandledURL(const GURL& url) const override {
+ return IsHandledProtocol(url.scheme());
+ }
+
+ bool IsSafeRedirectTarget(const GURL& location) const override {
+ return false;
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::FilePath path_;
+ JobObserver* observer_;
+};
+
+class JobObserverImpl : public CallbacksJobFactory::JobObserver {
+ public:
+ void OnJobCreated(URLRequestContentJob* job) override {
+ jobs_.push_back(job);
+ }
+
+ typedef std::vector<scoped_refptr<URLRequestContentJob> > JobList;
+
+ const JobList& jobs() { return jobs_; }
+
+ protected:
+ JobList jobs_;
+};
+
+// A simple holder for start/end used in http range requests.
+struct Range {
+ int start;
+ int end;
+
+ explicit Range(int start, int end) {
+ this->start = start;
+ this->end = end;
+ }
+};
+
+// A superclass for tests of the OnSeekComplete / OnReadComplete functions of
+// URLRequestContentJob.
+class URLRequestContentJobTest : public testing::Test {
+ public:
+ URLRequestContentJobTest();
+
+ protected:
+ // This inserts an image file into the android MediaStore and retrieves the
+ // content URI. Then creates and runs a URLRequestContentJob to get the
+ // contents out of it. If a Range is provided, this function will add the
+ // appropriate Range http header to the request and verify the bytes
+ // retrieved.
+ void RunRequest(const Range* range);
+
+ JobObserverImpl observer_;
+ net::TestURLRequestContext context_;
+ net::TestDelegate delegate_;
+};
+
+URLRequestContentJobTest::URLRequestContentJobTest() {}
+
+void URLRequestContentJobTest::RunRequest(const Range* range) {
+ base::FilePath test_dir;
+ PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
+ test_dir = test_dir.AppendASCII("content");
+ test_dir = test_dir.AppendASCII("test");
+ test_dir = test_dir.AppendASCII("data");
+ test_dir = test_dir.AppendASCII("android");
+ ASSERT_TRUE(base::PathExists(test_dir));
+ base::FilePath image_file = test_dir.Append(FILE_PATH_LITERAL("red.png"));
+
+ // Insert the image into MediaStore. MediaStore will do some conversions, and
+ // return the content URI.
+ base::FilePath path = base::InsertImageIntoMediaStore(image_file);
+ EXPECT_TRUE(path.IsContentUri());
+ EXPECT_TRUE(base::PathExists(path));
+ int64 file_size;
+ EXPECT_TRUE(base::GetFileSize(path, &file_size));
+ EXPECT_LT(0, file_size);
+ CallbacksJobFactory factory(path, &observer_);
+ context_.set_job_factory(&factory);
+
+ scoped_ptr<net::URLRequest> request(context_.CreateRequest(
+ GURL(path.value()), net::DEFAULT_PRIORITY, &delegate_, NULL));
+ int expected_length = file_size;
+ if (range) {
+ ASSERT_GE(range->start, 0);
+ ASSERT_GE(range->end, 0);
+ ASSERT_LE(range->start, range->end);
+ std::string range_value =
+ base::StringPrintf("bytes=%d-%d", range->start, range->end);
+ request->SetExtraRequestHeaderByName(
+ net::HttpRequestHeaders::kRange, range_value, true /*overwrite*/);
+ if (range->start <= file_size)
+ expected_length =
+ std::min(range->end, (int) (file_size - 1)) - range->start + 1;
+ else
+ expected_length = 0;
+ }
+ request->Start();
+
+ base::RunLoop loop;
+ loop.Run();
+
+ EXPECT_FALSE(delegate_.request_failed());
+ ASSERT_EQ(observer_.jobs().size(), 1u);
+ EXPECT_EQ(delegate_.bytes_received(), expected_length);
+}
+
+TEST_F(URLRequestContentJobTest, ContentURIWithoutRange) {
+ RunRequest(NULL);
+}
+
+TEST_F(URLRequestContentJobTest, ContentURIWithSmallRange) {
+ Range range(1, 10);
+ RunRequest(&range);
+}
+
+TEST_F(URLRequestContentJobTest, ContentURIWithLargeRange) {
+ Range range(1, 100000);
+ RunRequest(&range);
+}
+
+TEST_F(URLRequestContentJobTest, ContentURIWithZeroRange) {
+ Range range(0, 0);
+ RunRequest(&range);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index a477233..fcf4517 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -46,6 +46,7 @@
'public/browser/access_token_store.h',
'public/browser/android/compositor.h',
'public/browser/android/compositor_client.h',
+ 'public/browser/android/content_protocol_handler.h',
'public/browser/android/content_view_core.h',
'public/browser/android/content_view_layer_renderer.h',
'public/browser/android/devtools_auth.h',
@@ -288,6 +289,8 @@
'browser/android/child_process_launcher_android.h',
'browser/android/composited_touch_handle_drawable.cc',
'browser/android/composited_touch_handle_drawable.h',
+ 'browser/android/content_protocol_handler_impl.cc',
+ 'browser/android/content_protocol_handler_impl.h',
'browser/android/content_readback_handler.cc',
'browser/android/content_readback_handler.h',
'browser/android/content_settings.cc',
@@ -341,10 +344,12 @@
'browser/android/system_ui_resource_manager_impl.h',
'browser/android/tracing_controller_android.cc',
'browser/android/tracing_controller_android.h',
- 'browser/android/web_contents_observer_android.cc',
- 'browser/android/web_contents_observer_android.h',
'browser/android/ui_resource_provider_impl.cc',
'browser/android/ui_resource_provider_impl.h',
+ 'browser/android/url_request_content_job.cc',
+ 'browser/android/url_request_content_job.h',
+ 'browser/android/web_contents_observer_android.cc',
+ 'browser/android/web_contents_observer_android.h',
'browser/appcache/appcache.cc',
'browser/appcache/appcache.h',
'browser/appcache/appcache_backend_impl.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 269a057..9b52fac 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -959,6 +959,7 @@
'browser/android/java/jni_helper_unittest.cc',
'browser/android/overscroll_refresh_unittest.cc',
'browser/android/system_ui_resource_manager_impl_unittest.cc',
+ 'browser/android/url_request_content_job_unittest.cc',
'browser/renderer_host/input/motion_event_android_unittest.cc',
'renderer/java/gin_java_bridge_value_converter_unittest.cc',
],
diff --git a/content/public/browser/android/content_protocol_handler.h b/content/public/browser/android/content_protocol_handler.h
new file mode 100644
index 0000000..404ecb9
--- /dev/null
+++ b/content/public/browser/android/content_protocol_handler.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2014 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.
+
+#ifndef CONTENT_PUBLIC_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_H_
+#define CONTENT_PUBLIC_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace content {
+
+// ProtocolHandler for content scheme jobs.
+class CONTENT_EXPORT ContentProtocolHandler :
+ public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // Creates and returns a ContentProtocolHandler instance.
+ static ContentProtocolHandler* Create(
+ const scoped_refptr<base::TaskRunner>& content_task_runner);
+
+ protected:
+ virtual ~ContentProtocolHandler() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_PUBLIC_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_H_
diff --git a/content/test/data/android/red.png b/content/test/data/android/red.png
new file mode 100644
index 0000000..0806141
--- /dev/null
+++ b/content/test/data/android/red.png
Binary files differ
diff --git a/content/test/run_all_unittests.cc b/content/test/run_all_unittests.cc
index b6170ad..ed61a5c 100644
--- a/content/test/run_all_unittests.cc
+++ b/content/test/run_all_unittests.cc
@@ -8,7 +8,16 @@
#include "content/public/test/unittest_test_suite.h"
#include "content/test/content_test_suite.h"
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "base/test/test_file_util.h"
+#endif
+
int main(int argc, char** argv) {
+#if defined(OS_ANDROID)
+ // Register JNI bindings for android.
+ base::RegisterContentUriTestUtils(base::android::AttachCurrentThread());
+#endif
#if !defined(OS_IOS)
content::InitializeMojo();
#endif
diff --git a/url/url_constants.cc b/url/url_constants.cc
index 9ef0e63..2dc1478 100644
--- a/url/url_constants.cc
+++ b/url/url_constants.cc
@@ -10,6 +10,7 @@ const char kAboutBlankURL[] = "about:blank";
const char kAboutScheme[] = "about";
const char kBlobScheme[] = "blob";
+const char kContentScheme[] = "content";
const char kDataScheme[] = "data";
const char kFileScheme[] = "file";
const char kFileSystemScheme[] = "filesystem";
diff --git a/url/url_constants.h b/url/url_constants.h
index 3228bbb..c48dafc 100644
--- a/url/url_constants.h
+++ b/url/url_constants.h
@@ -13,6 +13,8 @@ URL_EXPORT extern const char kAboutBlankURL[];
URL_EXPORT extern const char kAboutScheme[];
URL_EXPORT extern const char kBlobScheme[];
+// The content scheme is specific to Android for identifying a stored file.
+URL_EXPORT extern const char kContentScheme[];
URL_EXPORT extern const char kDataScheme[];
URL_EXPORT extern const char kFileScheme[];
URL_EXPORT extern const char kFileSystemScheme[];