Home | History | Annotate | Download | only in base
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.base;
      6 
      7 import android.content.ContentResolver;
      8 import android.content.Context;
      9 import android.content.res.AssetFileDescriptor;
     10 import android.database.Cursor;
     11 import android.net.Uri;
     12 import android.os.Build;
     13 import android.os.ParcelFileDescriptor;
     14 import android.provider.DocumentsContract;
     15 import android.util.Log;
     16 import android.webkit.MimeTypeMap;
     17 
     18 import org.chromium.base.annotations.CalledByNative;
     19 
     20 import java.io.File;
     21 import java.io.FileNotFoundException;
     22 import java.io.IOException;
     23 
     24 /**
     25  * This class provides methods to access content URI schemes.
     26  */
     27 public abstract class ContentUriUtils {
     28     private static final String TAG = "ContentUriUtils";
     29     private static FileProviderUtil sFileProviderUtil;
     30 
     31     // Guards access to sFileProviderUtil.
     32     private static final Object sLock = new Object();
     33 
     34     /**
     35      * Provides functionality to translate a file into a content URI for use
     36      * with a content provider.
     37      */
     38     public interface FileProviderUtil {
     39         /**
     40          * Generate a content URI from the given file.
     41          * @param context Application context.
     42          * @param file The file to be translated.
     43          */
     44         Uri getContentUriFromFile(Context context, File file);
     45     }
     46 
     47     // Prevent instantiation.
     48     private ContentUriUtils() {}
     49 
     50     public static void setFileProviderUtil(FileProviderUtil util) {
     51         synchronized (sLock) {
     52             sFileProviderUtil = util;
     53         }
     54     }
     55 
     56     public static Uri getContentUriFromFile(Context context, File file) {
     57         synchronized (sLock) {
     58             if (sFileProviderUtil != null) {
     59                 return sFileProviderUtil.getContentUriFromFile(context, file);
     60             }
     61         }
     62         return null;
     63     }
     64 
     65     /**
     66      * Opens the content URI for reading, and returns the file descriptor to
     67      * the caller. The caller is responsible for closing the file desciptor.
     68      *
     69      * @param context {@link Context} in interest
     70      * @param uriString the content URI to open
     71      * @return file desciptor upon success, or -1 otherwise.
     72      */
     73     @CalledByNative
     74     public static int openContentUriForRead(Context context, String uriString) {
     75         AssetFileDescriptor afd = getAssetFileDescriptor(context, uriString);
     76         if (afd != null) {
     77             return afd.getParcelFileDescriptor().detachFd();
     78         }
     79         return -1;
     80     }
     81 
     82     /**
     83      * Check whether a content URI exists.
     84      *
     85      * @param context {@link Context} in interest.
     86      * @param uriString the content URI to query.
     87      * @return true if the URI exists, or false otherwise.
     88      */
     89     @CalledByNative
     90     public static boolean contentUriExists(Context context, String uriString) {
     91         AssetFileDescriptor asf = null;
     92         try {
     93             asf = getAssetFileDescriptor(context, uriString);
     94             return asf != null;
     95         } finally {
     96             // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
     97             // does not implement Closeable until KitKat.
     98             if (asf != null) {
     99                 try {
    100                     asf.close();
    101                 } catch (IOException e) {
    102                     // Closing quietly.
    103                 }
    104             }
    105         }
    106     }
    107 
    108     /**
    109      * Retrieve the MIME type for the content URI.
    110      *
    111      * @param context {@link Context} in interest.
    112      * @param uriString the content URI to look up.
    113      * @return MIME type or null if the input params are empty or invalid.
    114      */
    115     @CalledByNative
    116     public static String getMimeType(Context context, String uriString) {
    117         ContentResolver resolver = context.getContentResolver();
    118         Uri uri = Uri.parse(uriString);
    119         if (isVirtualDocument(uri, context)) {
    120             String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
    121             return (streamTypes != null && streamTypes.length > 0) ? streamTypes[0] : null;
    122         }
    123         return resolver.getType(uri);
    124     }
    125 
    126     /**
    127      * Helper method to open a content URI and returns the ParcelFileDescriptor.
    128      *
    129      * @param context {@link Context} in interest.
    130      * @param uriString the content URI to open.
    131      * @return AssetFileDescriptor of the content URI, or NULL if the file does not exist.
    132      */
    133     private static AssetFileDescriptor getAssetFileDescriptor(Context context, String uriString) {
    134         ContentResolver resolver = context.getContentResolver();
    135         Uri uri = Uri.parse(uriString);
    136 
    137         try {
    138             if (isVirtualDocument(uri, context)) {
    139                 String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
    140                 if (streamTypes != null && streamTypes.length > 0) {
    141                     AssetFileDescriptor afd =
    142                             resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null);
    143                     if (afd.getStartOffset() != 0) {
    144                         // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
    145                         // does not implement Closeable until KitKat.
    146                         try {
    147                             afd.close();
    148                         } catch (IOException e) {
    149                             // Closing quietly.
    150                         }
    151                         throw new SecurityException("Cannot open files with non-zero offset type.");
    152                     }
    153                     return afd;
    154                 }
    155             } else {
    156                 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
    157                 if (pfd != null) {
    158                     return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
    159                 }
    160             }
    161         } catch (FileNotFoundException e) {
    162             Log.w(TAG, "Cannot find content uri: " + uriString, e);
    163         } catch (SecurityException e) {
    164             Log.w(TAG, "Cannot open content uri: " + uriString, e);
    165         } catch (IllegalArgumentException e) {
    166             Log.w(TAG, "Unknown content uri: " + uriString, e);
    167         } catch (IllegalStateException e) {
    168             Log.w(TAG, "Unknown content uri: " + uriString, e);
    169         }
    170 
    171         return null;
    172     }
    173 
    174     /**
    175      * Method to resolve the display name of a content URI.
    176      *
    177      * @param uri the content URI to be resolved.
    178      * @param context {@link Context} in interest.
    179      * @param columnField the column field to query.
    180      * @return the display name of the @code uri if present in the database
    181      *  or an empty string otherwise.
    182      */
    183     public static String getDisplayName(Uri uri, Context context, String columnField) {
    184         if (uri == null) return "";
    185         ContentResolver contentResolver = context.getContentResolver();
    186         Cursor cursor = null;
    187         try {
    188             cursor = contentResolver.query(uri, null, null, null, null);
    189 
    190             if (cursor != null && cursor.getCount() >= 1) {
    191                 cursor.moveToFirst();
    192                 int displayNameIndex = cursor.getColumnIndex(columnField);
    193                 if (displayNameIndex == -1) {
    194                     return "";
    195                 }
    196                 String displayName = cursor.getString(displayNameIndex);
    197                 // For Virtual documents, try to modify the file extension so it's compatible
    198                 // with the alternative MIME type.
    199                 if (hasVirtualFlag(cursor)) {
    200                     String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*");
    201                     if (mimeTypes != null && mimeTypes.length > 0) {
    202                         String ext =
    203                                 MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeTypes[0]);
    204                         if (ext != null) {
    205                             // Just append, it's simpler and more secure than altering an
    206                             // existing extension.
    207                             displayName += "." + ext;
    208                         }
    209                     }
    210                 }
    211                 return displayName;
    212             }
    213         } catch (NullPointerException e) {
    214             // Some android models don't handle the provider call correctly.
    215             // see crbug.com/345393
    216             return "";
    217         } finally {
    218             StreamUtil.closeQuietly(cursor);
    219         }
    220         return "";
    221     }
    222 
    223     /**
    224      * Checks whether the passed Uri represents a virtual document.
    225      *
    226      * @param uri the content URI to be resolved.
    227      * @param contentResolver the content resolver to query.
    228      * @return True for virtual file, false for any other file.
    229      */
    230     private static boolean isVirtualDocument(Uri uri, Context context) {
    231         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return false;
    232         if (uri == null) return false;
    233         if (!DocumentsContract.isDocumentUri(context, uri)) return false;
    234         ContentResolver contentResolver = context.getContentResolver();
    235         Cursor cursor = null;
    236         try {
    237             cursor = contentResolver.query(uri, null, null, null, null);
    238 
    239             if (cursor != null && cursor.getCount() >= 1) {
    240                 cursor.moveToFirst();
    241                 return hasVirtualFlag(cursor);
    242             }
    243         } catch (NullPointerException e) {
    244             // Some android models don't handle the provider call correctly.
    245             // see crbug.com/345393
    246             return false;
    247         } finally {
    248             StreamUtil.closeQuietly(cursor);
    249         }
    250         return false;
    251     }
    252 
    253     /**
    254      * Checks whether the passed cursor for a document has a virtual document flag.
    255      *
    256      * The called must close the passed cursor.
    257      *
    258      * @param cursor Cursor with COLUMN_FLAGS.
    259      * @return True for virtual file, false for any other file.
    260      */
    261     private static boolean hasVirtualFlag(Cursor cursor) {
    262         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false;
    263         int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
    264         if (index > -1) {
    265             return (cursor.getLong(index) & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
    266         }
    267         return false;
    268     }
    269 }
    270