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..db16d9763f3b07d20b857da71f3ef0b6d614a2c8 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,102 @@ 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 columnField the column field to query. |
+ * @param context {@link Context} in interest. |
* @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; |
+ } |
} |