• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          *
42          * @param file The file to be translated.
43          */
getContentUriFromFile(File file)44         Uri getContentUriFromFile(File file);
45     }
46 
47     // Prevent instantiation.
ContentUriUtils()48     private ContentUriUtils() {}
49 
setFileProviderUtil(FileProviderUtil util)50     public static void setFileProviderUtil(FileProviderUtil util) {
51         synchronized (sLock) {
52             sFileProviderUtil = util;
53         }
54     }
55 
getContentUriFromFile(File file)56     public static Uri getContentUriFromFile(File file) {
57         synchronized (sLock) {
58             if (sFileProviderUtil != null) {
59                 return sFileProviderUtil.getContentUriFromFile(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 descriptor.
68      *
69      * @param uriString the content URI to open
70      * @return file descriptor upon success, or -1 otherwise.
71      */
72     @CalledByNative
openContentUriForRead(String uriString)73     public static int openContentUriForRead(String uriString) {
74         AssetFileDescriptor afd = getAssetFileDescriptor(uriString);
75         if (afd != null) {
76             return afd.getParcelFileDescriptor().detachFd();
77         }
78         return -1;
79     }
80 
81     /**
82      * Check whether a content URI exists.
83      *
84      * @param uriString the content URI to query.
85      * @return true if the URI exists, or false otherwise.
86      */
87     @CalledByNative
contentUriExists(String uriString)88     public static boolean contentUriExists(String uriString) {
89         AssetFileDescriptor asf = null;
90         try {
91             asf = getAssetFileDescriptor(uriString);
92             return asf != null;
93         } finally {
94             // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
95             // does not implement Closeable until KitKat.
96             if (asf != null) {
97                 try {
98                     asf.close();
99                 } catch (IOException e) {
100                     // Closing quietly.
101                 }
102             }
103         }
104     }
105 
106     /**
107      * Retrieve the MIME type for the content URI.
108      *
109      * @param uriString the content URI to look up.
110      * @return MIME type or null if the input params are empty or invalid.
111      */
112     @CalledByNative
getMimeType(String uriString)113     public static String getMimeType(String uriString) {
114         ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver();
115         Uri uri = Uri.parse(uriString);
116         if (isVirtualDocument(uri)) {
117             String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
118             return (streamTypes != null && streamTypes.length > 0) ? streamTypes[0] : null;
119         }
120         return resolver.getType(uri);
121     }
122 
123     /**
124      * Helper method to open a content URI and returns the ParcelFileDescriptor.
125      *
126      * @param uriString the content URI to open.
127      * @return AssetFileDescriptor of the content URI, or NULL if the file does not exist.
128      */
getAssetFileDescriptor(String uriString)129     private static AssetFileDescriptor getAssetFileDescriptor(String uriString) {
130         ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver();
131         Uri uri = Uri.parse(uriString);
132 
133         try {
134             if (isVirtualDocument(uri)) {
135                 String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
136                 if (streamTypes != null && streamTypes.length > 0) {
137                     AssetFileDescriptor afd =
138                             resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null);
139                     if (afd != null && afd.getStartOffset() != 0) {
140                         // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
141                         // does not implement Closeable until KitKat.
142                         try {
143                             afd.close();
144                         } catch (IOException e) {
145                             // Closing quietly.
146                         }
147                         throw new SecurityException("Cannot open files with non-zero offset type.");
148                     }
149                     return afd;
150                 }
151             } else {
152                 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
153                 if (pfd != null) {
154                     return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
155                 }
156             }
157         } catch (FileNotFoundException e) {
158             Log.w(TAG, "Cannot find content uri: " + uriString, e);
159         } catch (SecurityException e) {
160             Log.w(TAG, "Cannot open content uri: " + uriString, e);
161         } catch (Exception e) {
162             Log.w(TAG, "Unknown content uri: " + uriString, e);
163         }
164         return null;
165     }
166 
167     /**
168      * Method to resolve the display name of a content URI.
169      *
170      * @param uri         the content URI to be resolved.
171      * @param context     {@link Context} in interest.
172      * @param columnField the column field to query.
173      * @return the display name of the @code uri if present in the database
174      * or an empty string otherwise.
175      */
getDisplayName(Uri uri, Context context, String columnField)176     public static String getDisplayName(Uri uri, Context context, String columnField) {
177         if (uri == null) return "";
178         ContentResolver contentResolver = context.getContentResolver();
179         try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
180             if (cursor != null && cursor.getCount() >= 1) {
181                 cursor.moveToFirst();
182                 int displayNameIndex = cursor.getColumnIndex(columnField);
183                 if (displayNameIndex == -1) {
184                     return "";
185                 }
186                 String displayName = cursor.getString(displayNameIndex);
187                 // For Virtual documents, try to modify the file extension so it's compatible
188                 // with the alternative MIME type.
189                 if (hasVirtualFlag(cursor)) {
190                     String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*");
191                     if (mimeTypes != null && mimeTypes.length > 0) {
192                         String ext =
193                                 MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeTypes[0]);
194                         if (ext != null) {
195                             // Just append, it's simpler and more secure than altering an
196                             // existing extension.
197                             displayName += "." + ext;
198                         }
199                     }
200                 }
201                 return displayName;
202             }
203         } catch (NullPointerException e) {
204             // Some android models don't handle the provider call correctly.
205             // see crbug.com/345393
206             return "";
207         }
208         return "";
209     }
210 
211     /**
212      * Checks whether the passed Uri represents a virtual document.
213      *
214      * @param uri the content URI to be resolved.
215      * @return True for virtual file, false for any other file.
216      */
isVirtualDocument(Uri uri)217     private static boolean isVirtualDocument(Uri uri) {
218         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return false;
219         if (uri == null) return false;
220         if (!DocumentsContract.isDocumentUri(ContextUtils.getApplicationContext(), uri)) {
221             return false;
222         }
223         ContentResolver contentResolver = ContextUtils.getApplicationContext().getContentResolver();
224         try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
225             if (cursor != null && cursor.getCount() >= 1) {
226                 cursor.moveToFirst();
227                 return hasVirtualFlag(cursor);
228             }
229         } catch (NullPointerException e) {
230             // Some android models don't handle the provider call correctly.
231             // see crbug.com/345393
232             return false;
233         }
234         return false;
235     }
236 
237     /**
238      * Checks whether the passed cursor for a document has a virtual document flag.
239      *
240      * The called must close the passed cursor.
241      *
242      * @param cursor Cursor with COLUMN_FLAGS.
243      * @return True for virtual file, false for any other file.
244      */
hasVirtualFlag(Cursor cursor)245     private static boolean hasVirtualFlag(Cursor cursor) {
246         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false;
247         int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
248         return index > -1
249                 && (cursor.getLong(index) & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
250     }
251 }
252