diff options
-rw-r--r-- | base/BUILD.gn | 1 | ||||
-rw-r--r-- | base/android/content_uri_utils.cc | 10 | ||||
-rw-r--r-- | base/android/content_uri_utils.h | 10 | ||||
-rw-r--r-- | base/android/content_uri_utils_unittest.cc | 37 | ||||
-rw-r--r-- | base/android/java/src/org/chromium/base/ContentUriUtils.java | 42 | ||||
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | chrome/browser/profiles/profile_io_data.cc | 9 | ||||
-rw-r--r-- | content/browser/android/content_protocol_handler_impl.cc | 43 | ||||
-rw-r--r-- | content/browser/android/content_protocol_handler_impl.h | 45 | ||||
-rw-r--r-- | content/browser/android/url_request_content_job.cc | 228 | ||||
-rw-r--r-- | content/browser/android/url_request_content_job.h | 103 | ||||
-rw-r--r-- | content/browser/android/url_request_content_job_unittest.cc | 198 | ||||
-rw-r--r-- | content/content_browser.gypi | 9 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | content/public/browser/android/content_protocol_handler.h | 32 | ||||
-rw-r--r-- | content/test/data/android/red.png | bin | 0 -> 173 bytes | |||
-rw-r--r-- | content/test/run_all_unittests.cc | 9 | ||||
-rw-r--r-- | url/url_constants.cc | 1 | ||||
-rw-r--r-- | url/url_constants.h | 2 |
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 Binary files differnew file mode 100644 index 0000000..0806141 --- /dev/null +++ b/content/test/data/android/red.png 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[]; |