Chromium Code Reviews| 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..f1d0ac586c6a86332dc54b672ffea0606056cf58 100644 |
| --- a/base/android/java/src/org/chromium/base/ContentUriUtils.java |
| +++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java |
| @@ -4,17 +4,22 @@ |
| package org.chromium.base; |
| +import android.annotation.SuppressLint; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| +import android.content.res.AssetFileDescriptor; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.ParcelFileDescriptor; |
| +import android.provider.DocumentsContract; |
| import android.util.Log; |
| +import android.webkit.MimeTypeMap; |
| import org.chromium.base.annotations.CalledByNative; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| +import java.io.IOException; |
| /** |
| * This class provides methods to access content URI schemes. |
| @@ -67,9 +72,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 +88,19 @@ 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 { |
| + if (asf != null) { |
|
agrieve
2016/12/07 16:44:54
nit: Use StreamUtil.closeQuietly(asf)
mtomasz
2016/12/08 06:36:37
Done.
|
| + try { |
| + asf.close(); |
| + } catch (IOException e) { |
| + // Closing quietly. |
| + } |
| + } |
| + } |
| } |
| /** |
| @@ -96,8 +113,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[0] : null; |
| + } |
| return resolver.getType(uri); |
| } |
| @@ -106,15 +126,35 @@ 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) { |
| + @SuppressLint("NewApi") |
| + 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) { |
|
agrieve
2016/12/07 16:44:53
should we check for empty array here as well?
mtomasz
2016/12/08 06:36:37
Done.
|
| + AssetFileDescriptor afd = |
| + resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null, null); |
| + if (afd.getStartOffset() != 0) { |
| + try { |
| + afd.close(); |
| + } catch (IOException e) { |
| + // Closing quietly. |
|
agrieve
2016/12/07 16:44:54
StreamUtil.closeQuietly()
mtomasz
2016/12/08 06:36:37
Done.
|
| + } |
| + 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,29 +164,57 @@ 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 ""; |
| + @SuppressLint("NewApi") |
| + 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 (DocumentsContract.isDocumentUri(context, uri)) { |
| + int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); |
| + if (flagsIndex > -1) { |
| + boolean isVirtual = |
| + (cursor.getLong(flagsIndex) |
| + & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) |
| + != 0; |
| + if (isVirtual) { |
| + String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*"); |
| + if (mimeTypes != null) { |
|
agrieve
2016/12/07 16:44:54
check non-empty?
mtomasz
2016/12/08 06:36:37
Done.
|
| + 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. |
| @@ -157,4 +225,39 @@ public abstract class ContentUriUtils { |
| } |
| 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. |
| + */ |
| + @SuppressLint("NewApi") |
|
agrieve
2016/12/07 16:44:54
Do you need to add Build.VERSION check somewhere b
mtomasz
2016/12/08 06:36:37
I don't think so. All we needed from the newer API
agrieve
2016/12/09 18:23:14
Depending on a compiler optimization for correctne
|
| + public static boolean isVirtualDocument(Uri uri, Context context) { |
|
agrieve
2016/12/07 16:44:53
Doesn't look like this is used outside of this cla
mtomasz
2016/12/08 06:36:37
Done.
|
| + if (uri == null) return false; |
|
agrieve
2016/12/07 16:44:53
nit: Make Uri @Nullable and state it can be null i
mtomasz
2016/12/08 06:36:37
I tried to be consistent with the rest of the file
agrieve
2016/12/09 18:23:14
Yes please. Only getDisplayName() seems to check i
|
| + 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(); |
| + int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); |
| + if (index > -1) { |
| + return (cursor.getLong(index) |
| + & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) |
| + != 0; |
| + } |
| + } |
| + } catch (NullPointerException e) { |
| + // Some android models don't handle the provider call correctly. |
| + // see crbug.com/345393 |
| + return false; |
| + } finally { |
| + if (cursor != null) cursor.close(); |
|
agrieve
2016/12/07 16:44:53
StreamUtil.closeQuietly()
mtomasz
2016/12/08 06:36:37
Done.
|
| + } |
| + return false; |
| + } |
| } |