| Index: base/android/java/src/org/chromium/base/ContentUriUtils.java
|
| diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
|
| index 5448aa0e9ebf4b30cbf138bbb77aa0705e817ef0..21080dc06b3b61ac64a59518451bb25dda73c32a 100644
|
| --- a/base/android/java/src/org/chromium/base/ContentUriUtils.java
|
| +++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
|
| @@ -6,10 +6,14 @@ package org.chromium.base;
|
|
|
| import android.content.ContentResolver;
|
| import android.content.Context;
|
| +import android.content.res.AssetFileDescriptor;
|
| import android.database.Cursor;
|
| import android.net.Uri;
|
| +import android.os.Build;
|
| import android.os.ParcelFileDescriptor;
|
| +import android.provider.DocumentsContract;
|
| import android.util.Log;
|
| +import android.webkit.MimeTypeMap;
|
|
|
| import org.chromium.base.annotations.CalledByNative;
|
|
|
| @@ -67,9 +71,9 @@ public abstract class ContentUriUtils {
|
| */
|
| @CalledByNative
|
| public static int openContentUriForRead(Context context, String uriString) {
|
| - ParcelFileDescriptor pfd = getParcelFileDescriptor(context, uriString);
|
| - if (pfd != null) {
|
| - return pfd.detachFd();
|
| + AssetFileDescriptor afd = getAssetFileDescriptor(context, uriString);
|
| + if (afd != null) {
|
| + return afd.getParcelFileDescriptor().detachFd();
|
| }
|
| return -1;
|
| }
|
| @@ -83,7 +87,13 @@ public abstract class ContentUriUtils {
|
| */
|
| @CalledByNative
|
| public static boolean contentUriExists(Context context, String uriString) {
|
| - return getParcelFileDescriptor(context, uriString) != null;
|
| + AssetFileDescriptor asf = null;
|
| + try {
|
| + asf = getAssetFileDescriptor(context, uriString);
|
| + return asf != null;
|
| + } finally {
|
| + StreamUtil.closeQuietly(asf);
|
| + }
|
| }
|
|
|
| /**
|
| @@ -96,8 +106,11 @@ public abstract class ContentUriUtils {
|
| @CalledByNative
|
| public static String getMimeType(Context context, String uriString) {
|
| ContentResolver resolver = context.getContentResolver();
|
| - if (resolver == null) return null;
|
| Uri uri = Uri.parse(uriString);
|
| + if (isVirtualDocument(uri, context)) {
|
| + String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
|
| + return (streamTypes != null && streamTypes.length > 0) ? streamTypes[0] : null;
|
| + }
|
| return resolver.getType(uri);
|
| }
|
|
|
| @@ -106,15 +119,30 @@ public abstract class ContentUriUtils {
|
| *
|
| * @param context {@link Context} in interest.
|
| * @param uriString the content URI to open.
|
| - * @return ParcelFileDescriptor of the content URI, or NULL if the file does not exist.
|
| + * @return AssetFileDescriptor of the content URI, or NULL if the file does not exist.
|
| */
|
| - private static ParcelFileDescriptor getParcelFileDescriptor(Context context, String uriString) {
|
| + private static AssetFileDescriptor getAssetFileDescriptor(Context context, String uriString) {
|
| ContentResolver resolver = context.getContentResolver();
|
| Uri uri = Uri.parse(uriString);
|
|
|
| - ParcelFileDescriptor pfd = null;
|
| try {
|
| - pfd = resolver.openFileDescriptor(uri, "r");
|
| + if (isVirtualDocument(uri, context)) {
|
| + String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
|
| + if (streamTypes != null && streamTypes.length > 0) {
|
| + AssetFileDescriptor afd =
|
| + resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null);
|
| + if (afd.getStartOffset() != 0) {
|
| + StreamUtil.closeQuietly(afd);
|
| + throw new SecurityException("Cannot open files with non-zero offset type.");
|
| + }
|
| + return afd;
|
| + }
|
| + } else {
|
| + ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
|
| + if (pfd != null) {
|
| + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
|
| + }
|
| + }
|
| } catch (FileNotFoundException e) {
|
| Log.w(TAG, "Cannot find content uri: " + uriString, e);
|
| } catch (SecurityException e) {
|
| @@ -124,37 +152,103 @@ public abstract class ContentUriUtils {
|
| } catch (IllegalStateException e) {
|
| Log.w(TAG, "Unknown content uri: " + uriString, e);
|
| }
|
| - return pfd;
|
| +
|
| + return null;
|
| }
|
|
|
| /**
|
| * Method to resolve the display name of a content URI.
|
| *
|
| * @param uri the content URI to be resolved.
|
| - * @param contentResolver the content resolver to query.
|
| + * @param context {@link Context} in interest.
|
| * @param columnField the column field to query.
|
| * @return the display name of the @code uri if present in the database
|
| * or an empty string otherwise.
|
| */
|
| - public static String getDisplayName(
|
| - Uri uri, ContentResolver contentResolver, String columnField) {
|
| - if (contentResolver == null || uri == null) return "";
|
| + public static String getDisplayName(Uri uri, Context context, String columnField) {
|
| + if (uri == null) return "";
|
| + ContentResolver contentResolver = context.getContentResolver();
|
| Cursor cursor = null;
|
| try {
|
| cursor = contentResolver.query(uri, null, null, null, null);
|
|
|
| if (cursor != null && cursor.getCount() >= 1) {
|
| cursor.moveToFirst();
|
| - int index = cursor.getColumnIndex(columnField);
|
| - if (index > -1) return cursor.getString(index);
|
| + int displayNameIndex = cursor.getColumnIndex(columnField);
|
| + if (displayNameIndex == -1) {
|
| + return "";
|
| + }
|
| + String displayName = cursor.getString(displayNameIndex);
|
| + // For Virtual documents, try to modify the file extension so it's compatible
|
| + // with the alternative MIME type.
|
| + if (hasVirtualFlag(cursor)) {
|
| + String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*");
|
| + if (mimeTypes != null && mimeTypes.length > 0) {
|
| + String ext =
|
| + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeTypes[0]);
|
| + if (ext != null) {
|
| + // Just append, it's simpler and more secure than altering an
|
| + // existing extension.
|
| + displayName += "." + ext;
|
| + }
|
| + }
|
| + }
|
| + return displayName;
|
| }
|
| } catch (NullPointerException e) {
|
| // Some android models don't handle the provider call correctly.
|
| // see crbug.com/345393
|
| return "";
|
| } finally {
|
| - if (cursor != null) cursor.close();
|
| + StreamUtil.closeQuietly(cursor);
|
| }
|
| return "";
|
| }
|
| +
|
| + /**
|
| + * Checks whether the passed Uri represents a virtual document.
|
| + *
|
| + * @param uri the content URI to be resolved.
|
| + * @param contentResolver the content resolver to query.
|
| + * @return True for virtual file, false for any other file.
|
| + */
|
| + private static boolean isVirtualDocument(Uri uri, Context context) {
|
| + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return false;
|
| + if (uri == null) return false;
|
| + if (!DocumentsContract.isDocumentUri(context, uri)) return false;
|
| + ContentResolver contentResolver = context.getContentResolver();
|
| + Cursor cursor = null;
|
| + try {
|
| + cursor = contentResolver.query(uri, null, null, null, null);
|
| +
|
| + if (cursor != null && cursor.getCount() >= 1) {
|
| + cursor.moveToFirst();
|
| + return hasVirtualFlag(cursor);
|
| + }
|
| + } catch (NullPointerException e) {
|
| + // Some android models don't handle the provider call correctly.
|
| + // see crbug.com/345393
|
| + return false;
|
| + } finally {
|
| + StreamUtil.closeQuietly(cursor);
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the passed cursor for a document has a virtual document flag.
|
| + *
|
| + * The called must close the passed cursor.
|
| + *
|
| + * @param cursor Cursor with COLUMN_FLAGS.
|
| + * @return True for virtual file, false for any other file.
|
| + */
|
| + private static boolean hasVirtualFlag(Cursor cursor) {
|
| + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false;
|
| + int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
|
| + if (index > -1) {
|
| + return (cursor.getLong(index) & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
|
| + }
|
| + return false;
|
| + }
|
| }
|
|
|