diff options
23 files changed, 506 insertions, 31 deletions
diff --git a/base/android/base_jni_registrar.cc b/base/android/base_jni_registrar.cc index 0645c73..60ce975 100644 --- a/base/android/base_jni_registrar.cc +++ b/base/android/base_jni_registrar.cc @@ -6,6 +6,7 @@ #include "base/android/activity_status.h" #include "base/android/build_info.h" +#include "base/android/content_uri_utils.h" #include "base/android/cpu_features.h" #include "base/android/important_file_writer_android.h" #include "base/android/java_handler_thread.h" @@ -34,6 +35,7 @@ static RegistrationMethod kBaseRegisteredMethods[] = { #if defined(GOOGLE_TV) { "ContextTypes", base::android::RegisterContextTypes }, #endif + { "ContentUriUtils", base::RegisterContentUriUtils }, { "CpuFeatures", base::android::RegisterCpuFeatures }, { "ImportantFileWriterAndroid", base::android::RegisterImportantFileWriterAndroid }, diff --git a/base/android/content_uri_utils.cc b/base/android/content_uri_utils.cc new file mode 100644 index 0000000..057106f --- /dev/null +++ b/base/android/content_uri_utils.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2013 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/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/platform_file.h" +#include "jni/ContentUriUtils_jni.h" + +using base::android::ConvertUTF8ToJavaString; + +namespace base { + +bool RegisterContentUriUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +bool ContentUriExists(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + return Java_ContentUriUtils_contentUriExists( + env, base::android::GetApplicationContext(), j_uri.obj()); +} + +int OpenContentUriForRead(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + jint fd = Java_ContentUriUtils_openContentUriForRead( + env, base::android::GetApplicationContext(), j_uri.obj()); + if (fd < 0) + return base::kInvalidPlatformFileValue; + return fd; +} + +} // namespace base diff --git a/base/android/content_uri_utils.h b/base/android/content_uri_utils.h new file mode 100644 index 0000000..e738803 --- /dev/null +++ b/base/android/content_uri_utils.h @@ -0,0 +1,27 @@ +// Copyright (c) 2013 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 BASE_ANDROID_CONTENT_URI_UTILS_H_ +#define BASE_ANDROID_CONTENT_URI_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" + +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. +BASE_EXPORT int OpenContentUriForRead(const FilePath& content_uri); + +// Check whether a content uri exists. +BASE_EXPORT bool ContentUriExists(const FilePath& content_uri); + +} // namespace base + +#endif // BASE_ANDROID_CONTENT_URI_UTILS_H_ diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java new file mode 100644 index 0000000..c639396 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java @@ -0,0 +1,76 @@ +// Copyright (c) 2013 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. + +package org.chromium.base; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import org.chromium.base.CalledByNative; + +/** + * This class provides methods to access content URI schemes. + */ +abstract class ContentUriUtils { + private static final String TAG = "ContentUriUtils"; + + // Prevent instantiation. + private ContentUriUtils() {} + + /** + * Opens the content URI for reading, and returns the file descriptor to + * the caller. The caller is responsible for closing the file desciptor. + * + * @param context {@link Context} in interest + * @param uriString the content URI to open + * @returns file desciptor upon sucess, or -1 otherwise. + */ + @CalledByNative + public static int openContentUriForRead(Context context, String uriString) { + ParcelFileDescriptor pfd = getParcelFileDescriptor(context, uriString); + if (pfd != null) { + return pfd.detachFd(); + } + return -1; + } + + /** + * Check whether a content URI exists. + * + * @param context {@link Context} in interest. + * @param uriString the content URI to query. + * @returns 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; + } + + /** + * Helper method to open a content URI and return 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. + */ + private static ParcelFileDescriptor getParcelFileDescriptor(Context context, String uriString) { + ContentResolver resolver = context.getContentResolver(); + Uri uri = Uri.parse(uriString); + + ParcelFileDescriptor pfd = null; + try { + pfd = resolver.openFileDescriptor(uri, "r"); + } catch (java.io.FileNotFoundException e) { + Log.w(TAG, "Cannot find content uri: " + uriString, e); + } + return pfd; + } +} diff --git a/base/base.gyp b/base/base.gyp index fb096b9..0192461 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -896,6 +896,14 @@ 'test/test_file_util_linux.cc', ], }], + ['OS == "android"', { + 'dependencies': [ + 'base_unittests_jni_headers', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/base', + ], + }], ], 'sources': [ 'test/expectations/expectation.cc', @@ -948,6 +956,7 @@ 'test/task_runner_test_template.h', 'test/test_file_util.cc', 'test/test_file_util.h', + 'test/test_file_util_android.cc', 'test/test_file_util_linux.cc', 'test/test_file_util_mac.cc', 'test/test_file_util_posix.cc', @@ -1214,6 +1223,7 @@ 'sources': [ 'android/java/src/org/chromium/base/ActivityStatus.java', 'android/java/src/org/chromium/base/BuildInfo.java', + 'android/java/src/org/chromium/base/ContentUriUtils.java', 'android/java/src/org/chromium/base/CpuFeatures.java', 'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java', 'android/java/src/org/chromium/base/MemoryPressureListener.java', @@ -1239,6 +1249,17 @@ 'includes': [ '../build/jni_generator.gypi' ], }, { + 'target_name': 'base_unittests_jni_headers', + 'type': 'none', + 'sources': [ + 'test/android/java/src/org/chromium/base/ContentUriTestUtils.java', + ], + 'variables': { + 'jni_gen_package': 'base', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { 'target_name': 'base_java', 'type': 'none', 'variables': { @@ -1258,6 +1279,17 @@ ], }, { + 'target_name': 'base_java_unittest_support', + 'type': 'none', + 'dependencies': [ + 'base_java', + ], + 'variables': { + 'java_in_dir': '../base/test/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { 'target_name': 'base_java_activity_state', 'type': 'none', # This target is used to auto-generate ActivityState.java @@ -1339,6 +1371,7 @@ 'type': 'none', 'dependencies': [ 'base_java', + 'base_java_unittest_support', 'base_unittests', ], 'variables': { diff --git a/base/base.gypi b/base/base.gypi index 955233c..c46fac2 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -39,6 +39,8 @@ 'android/base_jni_registrar.h', 'android/build_info.cc', 'android/build_info.h', + 'android/content_uri_utils.cc', + 'android/content_uri_utils.h', 'android/cpu_features.cc', 'android/fifo_utils.cc', 'android/fifo_utils.h', diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc index a2dd19b..4546e31 100644 --- a/base/file_util_posix.cc +++ b/base/file_util_posix.cc @@ -48,6 +48,7 @@ #include "base/time/time.h" #if defined(OS_ANDROID) +#include "base/android/content_uri_utils.h" #include "base/os_compat_android.h" #endif @@ -79,6 +80,12 @@ static int CallLstat(const char *path, stat_wrapper_t *sb) { ThreadRestrictions::AssertIOAllowed(); return lstat64(path, sb); } +#if defined(OS_ANDROID) +static int CallFstat(int fd, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return fstat64(fd, sb); +} +#endif #endif // Helper for NormalizeFilePath(), defined below. @@ -308,6 +315,11 @@ bool CopyDirectory(const FilePath& from_path, bool PathExists(const FilePath& path) { ThreadRestrictions::AssertIOAllowed(); +#if defined(OS_ANDROID) + if (path.IsContentUri()) { + return ContentUriExists(path); + } +#endif return access(path.value().c_str(), F_OK) == 0; } @@ -569,8 +581,21 @@ bool IsLink(const FilePath& file_path) { bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) { stat_wrapper_t file_info; - if (CallStat(file_path.value().c_str(), &file_info) != 0) - return false; +#if defined(OS_ANDROID) + if (file_path.IsContentUri()) { + int fd = OpenContentUriForRead(file_path); + if (fd < 0) + return false; + ScopedFD scoped_fd(&fd); + if (base::CallFstat(fd, &file_info) != 0) + return false; + } else { +#endif // defined(OS_ANDROID) + if (CallStat(file_path.value().c_str(), &file_info) != 0) + return false; +#if defined(OS_ANDROID) + } +#endif // defined(OS_ANDROID) results->is_directory = S_ISDIR(file_info.st_mode); results->size = file_info.st_size; #if defined(OS_MACOSX) diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc index 1ca70b4..3594749 100644 --- a/base/file_util_unittest.cc +++ b/base/file_util_unittest.cc @@ -33,6 +33,10 @@ #include "base/win/windows_version.h" #endif +#if defined(OS_ANDROID) +#include "base/android/content_uri_utils.h" +#endif + // This macro helps avoid wrapped lines in the test structs. #define FPL(x) FILE_PATH_LITERAL(x) @@ -2319,6 +2323,52 @@ TEST_F(VerifyPathControlledByUserTest, WriteBitChecks) { sub_dir_, text_file_, uid_, ok_gids_)); } +#if defined(OS_ANDROID) +TEST_F(FileUtilTest, ValidContentUriTest) { + // Get the test image path. + FilePath data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(base::PathExists(data_dir)); + FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png")); + int64 image_size; + file_util::GetFileSize(image_file, &image_size); + EXPECT_LT(0, image_size); + + // Insert the image into MediaStore. MediaStore will do some conversions, and + // return the content URI. + base::FilePath path = file_util::InsertImageIntoMediaStore(image_file); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_TRUE(base::PathExists(path)); + // The file size may not equal to the input image as MediaStore may convert + // the image. + int64 content_uri_size; + file_util::GetFileSize(path, &content_uri_size); + EXPECT_EQ(image_size, content_uri_size); + + // We should be able to read the file. + char* buffer = new char[image_size]; + int fd = base::OpenContentUriForRead(path); + EXPECT_LT(0, fd); + EXPECT_TRUE(file_util::ReadFromFD(fd, buffer, image_size)); + delete[] buffer; +} + +TEST_F(FileUtilTest, NonExistentContentUriTest) { + base::FilePath path("content://foo.bar"); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_FALSE(base::PathExists(path)); + // Size should be smaller than 0. + int64 size; + file_util::GetFileSize(path, &size); + EXPECT_GT(0, size); + + // We should not be able to read the file. + int fd = base::OpenContentUriForRead(path); + EXPECT_EQ(-1, fd); +} +#endif + #endif // defined(OS_POSIX) } // namespace diff --git a/base/files/file_path.cc b/base/files/file_path.cc index cfae3a5..4cfa2e6 100644 --- a/base/files/file_path.cc +++ b/base/files/file_path.cc @@ -1280,6 +1280,12 @@ FilePath FilePath::NormalizePathSeparators() const { #endif } +#if defined(OS_ANDROID) +bool FilePath::IsContentUri() const { + return StartsWithASCII(path_, "content://", false /*case_sensitive*/); +} +#endif + } // namespace base void PrintTo(const base::FilePath& path, std::ostream* out) { diff --git a/base/files/file_path.h b/base/files/file_path.h index 4d03da4..33beb0b 100644 --- a/base/files/file_path.h +++ b/base/files/file_path.h @@ -387,6 +387,15 @@ class BASE_EXPORT FilePath { const StringType& string2); #endif +#if defined(OS_ANDROID) + // On android, file selection dialog can return a file with content uri + // scheme(starting with content://). Content uri needs to be opened with + // ContentResolver to guarantee that the app has appropriate permissions + // to access it. + // Returns true if the path is a content uri, or false otherwise. + bool IsContentUri() const; +#endif + private: // Remove trailing separators from this object. If the path is absolute, it // will never be stripped any more than to refer to the absolute root diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc index 8b2fcf5..1b6e465 100644 --- a/base/files/file_path_unittest.cc +++ b/base/files/file_path_unittest.cc @@ -1228,4 +1228,33 @@ TEST_F(FilePathTest, AsEndingWithSeparator) { } } +#if defined(OS_ANDROID) +TEST_F(FilePathTest, ContentUriTest) { + const struct UnaryBooleanTestData cases[] = { + { FPL("content://foo.bar"), true }, + { FPL("content://foo.bar/"), true }, + { FPL("content://foo/bar"), true }, + { FPL("CoNTenT://foo.bar"), true }, + { FPL("content://"), true }, + { FPL("content:///foo.bar"), true }, + { FPL("content://3foo/bar"), true }, + { FPL("content://_foo/bar"), true }, + { FPL(".. "), false }, + { FPL("foo.bar"), false }, + { FPL("content:foo.bar"), false }, + { FPL("content:/foo.ba"), false }, + { FPL("content:/dir/foo.bar"), false }, + { FPL("content: //foo.bar"), false }, + { FPL("content%2a%2f%2f"), false }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.IsContentUri(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} +#endif + } // namespace base diff --git a/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java new file mode 100644 index 0000000..f8241cd --- /dev/null +++ b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java @@ -0,0 +1,51 @@ +// Copyright (c) 2013 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. + +package org.chromium.base; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; + +import org.chromium.base.CalledByNative; + +/** + * Utilities for testing operations on content URI. + */ +public class ContentUriTestUtils { + /** + * Insert an image into the MediaStore, and return the content URI. If the + * image already exists in the MediaStore, just retrieve the URI. + * + * @param context Application context. + * @param path Path to the image file. + * @return Content URI of the image. + */ + @CalledByNative + private static String insertImageIntoMediaStore(Context context, String path) { + // Check whether the content URI exists. + Cursor c = context.getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new String[] { MediaStore.Video.VideoColumns._ID }, + MediaStore.Images.Media.DATA + " LIKE ?", + new String[] { path }, + null); + if (c != null && c.getCount() > 0) { + c.moveToFirst(); + int id = c.getInt(0); + return Uri.withAppendedPath( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id).toString(); + } + + // Insert the content URI into MediaStore. + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DATA, path); + Uri uri = context.getContentResolver().insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + return uri.toString(); + } +} diff --git a/base/test/data/file_util/red.png b/base/test/data/file_util/red.png Binary files differnew file mode 100644 index 0000000..0806141 --- /dev/null +++ b/base/test/data/file_util/red.png diff --git a/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc index e561f0e..3b5ebfe 100644 --- a/base/test/run_all_unittests.cc +++ b/base/test/run_all_unittests.cc @@ -7,6 +7,11 @@ #include "base/test/launcher/unit_test_launcher.h" #include "base/test/test_suite.h" +#if defined(OS_ANDROID) +#include "base/android/jni_android.h" +#include "base/test/test_file_util.h" +#endif + namespace { class NoAtExitBaseTestSuite : public base::TestSuite { @@ -23,7 +28,10 @@ int RunTestSuite(int argc, char** argv) { } // namespace int main(int argc, char** argv) { -#if !defined(OS_ANDROID) +#if defined(OS_ANDROID) + JNIEnv* env = base::android::AttachCurrentThread(); + file_util::RegisterContentUriTestUtils(env); +#else base::AtExitManager at_exit; #endif return base::LaunchUnitTests(argc, diff --git a/base/test/test_file_util.h b/base/test/test_file_util.h index cf20221..656babd 100644 --- a/base/test/test_file_util.h +++ b/base/test/test_file_util.h @@ -12,6 +12,11 @@ #include "base/compiler_specific.h" #include "base/files/file_path.h" +#if defined(OS_ANDROID) +#include <jni.h> +#include "base/basictypes.h" +#endif + namespace base { class FilePath; @@ -58,6 +63,15 @@ base::FilePath WStringAsFilePath(const std::wstring& path); bool MakeFileUnreadable(const base::FilePath& path) WARN_UNUSED_RESULT; bool MakeFileUnwritable(const base::FilePath& path) WARN_UNUSED_RESULT; +#if defined(OS_ANDROID) +// Register the ContentUriTestUrils JNI bindings. +bool RegisterContentUriTestUtils(JNIEnv* env); + +// Insert an image file into the MediaStore, and retrieve the content URI for +// testing purpose. +base::FilePath InsertImageIntoMediaStore(const base::FilePath& path); +#endif // defined(OS_ANDROID) + // Saves the current permissions for a path, and restores it on destruction. class PermissionRestorer { public: diff --git a/base/test/test_file_util_android.cc b/base/test/test_file_util_android.cc new file mode 100644 index 0000000..9bee1d1 --- /dev/null +++ b/base/test/test_file_util_android.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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/test/test_file_util.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "jni/ContentUriTestUtils_jni.h" + +namespace file_util { + +bool RegisterContentUriTestUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +base::FilePath InsertImageIntoMediaStore(const base::FilePath& path) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_path = + base::android::ConvertUTF8ToJavaString(env, path.value()); + ScopedJavaLocalRef<jstring> j_uri = + Java_ContentUriTestUtils_insertImageIntoMediaStore( + env, base::android::GetApplicationContext(), j_path.obj()); + std::string uri = base::android::ConvertJavaStringToUTF8(j_uri); + return base::FilePath(uri); +} + +} // namespace file_util diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc index b0ac66d..85ec83a 100644 --- a/content/browser/child_process_security_policy_impl.cc +++ b/content/browser/child_process_security_policy_impl.cc @@ -135,6 +135,22 @@ class ChildProcessSecurityPolicyImpl::SecurityState { return (it->second & permissions) == permissions; } +#if defined(OS_ANDROID) + // Determine if the certain permissions have been granted to a content URI. + bool HasPermissionsForContentUri(const base::FilePath& file, + int permissions) { + DCHECK(!file.empty()); + DCHECK(file.IsContentUri()); + if (!permissions) + return false; + base::FilePath file_path = file.StripTrailingSeparators(); + FileMap::const_iterator it = file_permissions_.find(file_path); + if (it != file_permissions_.end()) + return (it->second & permissions) == permissions; + return false; + } +#endif + void GrantBindings(int bindings) { enabled_bindings_ |= bindings; } @@ -171,6 +187,10 @@ class ChildProcessSecurityPolicyImpl::SecurityState { // Determine if the certain permissions have been granted to a file. bool HasPermissionsForFile(const base::FilePath& file, int permissions) { +#if defined(OS_ANDROID) + if (file.IsContentUri()) + return HasPermissionsForContentUri(file, permissions); +#endif if (!permissions || file.empty() || !file.IsAbsolute()) return false; base::FilePath current_path = file.StripTrailingSeparators(); diff --git a/net/base/file_stream_context.cc b/net/base/file_stream_context.cc index 2e77475..e7fe100 100644 --- a/net/base/file_stream_context.cc +++ b/net/base/file_stream_context.cc @@ -11,6 +11,10 @@ #include "net/base/file_stream_net_log_parameters.h" #include "net/base/net_errors.h" +#if defined(OS_ANDROID) +#include "base/android/content_uri_utils.h" +#endif + namespace { void CallInt64ToInt(const net::CompletionCallback& callback, int64 result) { @@ -193,13 +197,24 @@ void FileStream::Context::BeginOpenEvent(const base::FilePath& path) { FileStream::Context::OpenResult FileStream::Context::OpenFileImpl( const base::FilePath& path, int open_flags) { - // FileStream::Context actually closes the file asynchronously, independently - // from FileStream's destructor. It can cause problems for users wanting to - // delete the file right after FileStream deletion. Thus we are always - // adding SHARE_DELETE flag to accommodate such use case. - open_flags |= base::PLATFORM_FILE_SHARE_DELETE; - base::PlatformFile file = - base::CreatePlatformFile(path, open_flags, NULL, NULL); + base::PlatformFile file; +#if defined(OS_ANDROID) + if (path.IsContentUri()) { + // Check that only Read flags are set. + DCHECK_EQ(open_flags & ~base::PLATFORM_FILE_ASYNC, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ); + file = base::OpenContentUriForRead(path); + } else { +#endif // defined(OS_ANDROID) + // FileStream::Context actually closes the file asynchronously, + // independently from FileStream's destructor. It can cause problems for + // users wanting to delete the file right after FileStream deletion. Thus + // we are always adding SHARE_DELETE flag to accommodate such use case. + open_flags |= base::PLATFORM_FILE_SHARE_DELETE; + file = base::CreatePlatformFile(path, open_flags, NULL, NULL); +#if defined(OS_ANDROID) + } +#endif // defined(OS_ANDROID) if (file == base::kInvalidPlatformFileValue) return OpenResult(file, IOResult::FromOSError(GetLastErrno())); diff --git a/net/base/file_stream_unittest.cc b/net/base/file_stream_unittest.cc index c76f3d9..e721569 100644 --- a/net/base/file_stream_unittest.cc +++ b/net/base/file_stream_unittest.cc @@ -21,6 +21,10 @@ #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" +#if defined(OS_ANDROID) +#include "base/test/test_file_util.h" +#endif + namespace net { namespace { @@ -1173,6 +1177,55 @@ TEST_F(FileStreamTest, AsyncReadError) { base::ClosePlatformFile(file); } +#if defined(OS_ANDROID) +TEST_F(FileStreamTest, ContentUriAsyncRead) { + base::FilePath test_dir; + PathService::Get(base::DIR_SOURCE_ROOT, &test_dir); + test_dir = test_dir.AppendASCII("net"); + test_dir = test_dir.AppendASCII("data"); + test_dir = test_dir.AppendASCII("file_stream_unittest"); + 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 = file_util::InsertImageIntoMediaStore(image_file); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_TRUE(base::PathExists(path)); + int64 file_size; + EXPECT_TRUE(file_util::GetFileSize(path, &file_size)); + EXPECT_LT(0, file_size); + + FileStream stream(NULL, base::MessageLoopProxy::current()); + int flags = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_ASYNC; + TestCompletionCallback callback; + int rv = stream.Open(path, flags, callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + int64 total_bytes_avail = stream.Available(); + EXPECT_EQ(file_size, total_bytes_avail); + + int total_bytes_read = 0; + + std::string data_read; + for (;;) { + scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4); + rv = stream.Read(buf.get(), buf->size(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_LE(0, rv); + if (rv <= 0) + break; + total_bytes_read += rv; + data_read.append(buf->data(), rv); + } + EXPECT_EQ(file_size, total_bytes_read); +} +#endif + } // namespace } // namespace net diff --git a/net/data/file_stream_unittest/red.png b/net/data/file_stream_unittest/red.png Binary files differnew file mode 100644 index 0000000..0806141 --- /dev/null +++ b/net/data/file_stream_unittest/red.png diff --git a/net/net.gyp b/net/net.gyp index 3e5b04b..4f16adb 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -2993,6 +2993,7 @@ 'target_name': 'net_unittests_apk', 'type': 'none', 'dependencies': [ + '../base/base.gyp:base_java_unittest_support', 'net_java', 'net_javatests', 'net_unittests', diff --git a/net/test/run_all_unittests.cc b/net/test/run_all_unittests.cc index 3a51ba0..9b5dad2 100644 --- a/net/test/run_all_unittests.cc +++ b/net/test/run_all_unittests.cc @@ -13,6 +13,7 @@ #if defined(OS_ANDROID) #include "base/android/jni_android.h" +#include "base/test/test_file_util.h" #include "net/android/net_jni_registrar.h" #endif @@ -31,6 +32,7 @@ int main(int argc, char** argv) { // Register JNI bindings for android. Doing it early as the test suite setup // may initiate a call to Java. net::android::RegisterJni(base::android::AttachCurrentThread()); + file_util::RegisterContentUriTestUtils(base::android::AttachCurrentThread()); #endif NetTestSuite test_suite(argc, argv); diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java index f495767..4e269b0 100644 --- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java +++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java @@ -165,27 +165,11 @@ class SelectFileDialog implements WindowAndroid.IntentCallback{ return; } - Cursor cursor = null; - try { - // We get back a content:// URI from the system if the user picked a file from the - // gallery. The ContentView has functionality that will convert that content:// URI to - // a file path on disk that Chromium understands. - cursor = contentResolver.query(results.getData(), - new String[] { MediaStore.MediaColumns.DATA }, null, null, null); - if (cursor != null) { - if (cursor.getCount() == 1) { - cursor.moveToFirst(); - String path = cursor.getString(0); - if (path != null) { - // Not all providers support the MediaStore.DATA column. For example, - // Gallery3D (com.android.gallery3d.provider) does not support it for - // Picasa Web Album images. - nativeOnFileSelected(mNativeSelectFileDialog, path); - return; - } - } - } - } finally { if (cursor != null) { cursor.close(); } } + if (results.getScheme() != null + && results.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + nativeOnFileSelected(mNativeSelectFileDialog, results.getData().toString()); + return; + } onFileNotSelected(); window.showError(R.string.opening_file_error); |