Index: chrome/android/java/src/org/chromium/chrome/browser/util/ChromeFileProvider.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/ChromeFileProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/util/ChromeFileProvider.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..527c9d1f4ed9f14d4fdc26551370559248acb6f3 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/ChromeFileProvider.java |
@@ -0,0 +1,177 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.util; |
+ |
+import android.content.Context; |
+import android.database.Cursor; |
+import android.database.MatrixCursor; |
+import android.net.Uri; |
+import android.os.ParcelFileDescriptor; |
+import android.provider.MediaStore; |
+import android.support.v4.content.FileProvider; |
+ |
+import java.io.FileNotFoundException; |
+import java.util.Arrays; |
+ |
+/** |
+ * A file provider class that can share a potentially non-existent file and blocks the client |
+ * application from accessing the file till it is written. |
+ * |
+ * This class serves as the default file provider, but also lets us share blocked files. |
+ * For blocked files, it generates an unique identifier for the file to be shared and the embedder |
+ * must write the file and notify that the file is ready using the unique uri generated. The client |
+ * application is blocked from accessing the file till the file is ready. This provider allows only |
+ * one blocked file to be shared at a given time. |
+ */ |
+public class ChromeFileProvider extends FileProvider { |
+ private static final String AUTHORITY_SUFFIX = ".FileProvider"; |
+ private static final String BLOCKED_FILE_PREFIX = "BlockedFile_"; |
+ |
+ // All these static objects must be accesseed in a synchronized block: |
+ private static Object sLock = new Object(); |
+ private static boolean sIsFileReady; |
+ private static Uri sCurrentBlockingUri; |
+ private static Uri sFileUri; |
+ |
+ /** |
+ * Returns an unique uri to identify the file to be shared and block access to it till |
+ * notifyFileReady is called. |
+ * |
+ * This function clobbers any uri that was previously created and the client application |
+ * accessing those uri will get a null file descriptor. |
+ * @param context Activity context that is used to access package manager. |
+ */ |
+ public static Uri generateUriAndBlockAccess(final Context context) { |
+ String authority = context.getPackageName() + AUTHORITY_SUFFIX; |
+ String fileName = BLOCKED_FILE_PREFIX + String.valueOf(System.nanoTime()); |
+ Uri blockingUri = |
+ new Uri.Builder().scheme("content").authority(authority).path(fileName).build(); |
+ synchronized (sLock) { |
+ sCurrentBlockingUri = blockingUri; |
+ sFileUri = null; |
+ sIsFileReady = false; |
+ // In case the previous file never got ready. |
+ sLock.notify(); |
+ } |
+ return blockingUri; |
+ } |
+ |
+ /** |
+ * Notify that the file is ready to be accessed by the client application. |
+ * |
+ * @param blockingUri The unique uri that was generated by generateUriAndBlockAccess. |
+ * @param fileUri The Uri for actual file given by FileProvider. |
+ */ |
+ public static void notifyFileReady(Uri blockingUri, Uri fileUri) { |
+ synchronized (sLock) { |
+ sFileUri = fileUri; |
+ // Ready is set only if the current file is ready. |
+ sIsFileReady = doesMatchCurrentBlockingUri(blockingUri); |
+ sLock.notify(); |
+ } |
+ } |
+ |
+ @Override |
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { |
+ Uri fileUri = getFileUriWhenReady(uri); |
+ return fileUri != null ? super.openFile(fileUri, mode) : null; |
+ } |
+ |
+ @Override |
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, |
+ String sortOrder) { |
+ Uri fileUri = getFileUriWhenReady(uri); |
+ if (fileUri == null) return null; |
+ |
+ // Workaround for a bad assumption that particular MediaStore columns exist by certain third |
+ // party applications. |
+ // http://crbug.com/467423. |
+ Cursor source = super.query(fileUri, projection, selection, selectionArgs, sortOrder); |
+ |
+ String[] columnNames = source.getColumnNames(); |
+ String[] newColumnNames = columnNamesWithData(columnNames); |
+ if (columnNames == newColumnNames) return source; |
+ |
+ MatrixCursor cursor = new MatrixCursor(newColumnNames, source.getCount()); |
+ |
+ source.moveToPosition(-1); |
+ while (source.moveToNext()) { |
+ MatrixCursor.RowBuilder row = cursor.newRow(); |
+ for (int i = 0; i < columnNames.length; i++) { |
+ switch (source.getType(i)) { |
+ case Cursor.FIELD_TYPE_INTEGER: |
+ row.add(source.getInt(i)); |
+ break; |
+ case Cursor.FIELD_TYPE_FLOAT: |
+ row.add(source.getFloat(i)); |
+ break; |
+ case Cursor.FIELD_TYPE_STRING: |
+ row.add(source.getString(i)); |
+ break; |
+ case Cursor.FIELD_TYPE_BLOB: |
+ row.add(source.getBlob(i)); |
+ break; |
+ case Cursor.FIELD_TYPE_NULL: |
+ default: |
+ row.add(null); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ source.close(); |
+ return cursor; |
+ } |
+ |
+ @Override |
+ public int delete(Uri uri, String selection, String[] selectionArgs) { |
+ if (uri != null && uri.getPath().contains(BLOCKED_FILE_PREFIX)) { |
+ synchronized (sLock) { |
+ if (!doesMatchCurrentBlockingUri(uri)) return 0; |
+ sFileUri = null; |
+ sIsFileReady = false; |
+ sCurrentBlockingUri = null; |
+ } |
+ } |
+ return super.delete(uri, selection, selectionArgs); |
+ } |
+ |
+ /** |
+ * Waits and returns file uri iff the file is ready to be accessed, or returns null if file is |
+ * replaced. |
+ */ |
+ protected static Uri getFileUriWhenReady(Uri uri) { |
+ // If the uri passed is not a blocked file, then the given uri can be directly used. |
+ if (uri == null || !uri.getPath().contains(BLOCKED_FILE_PREFIX)) return uri; |
+ |
+ synchronized (sLock) { |
+ // Wait only if the file is not ready and the current file has not changed. |
+ while (!sIsFileReady && doesMatchCurrentBlockingUri(uri)) { |
+ try { |
+ sLock.wait(); |
+ } catch (InterruptedException e) { |
+ break; |
+ } |
+ } |
+ // If the current file has changed while waiting, return null. |
+ if (doesMatchCurrentBlockingUri(uri)) return sFileUri; |
+ } |
+ return null; |
+ } |
+ |
+ private static boolean doesMatchCurrentBlockingUri(Uri uri) { |
+ return uri != null && sCurrentBlockingUri != null && sCurrentBlockingUri.equals(uri); |
+ } |
+ |
+ private String[] columnNamesWithData(String[] columnNames) { |
+ for (String columnName : columnNames) { |
+ if (MediaStore.MediaColumns.DATA.equals(columnName)) return columnNames; |
+ } |
+ |
+ String[] newColumnNames = Arrays.copyOf(columnNames, columnNames.length + 1); |
+ newColumnNames[columnNames.length] = MediaStore.MediaColumns.DATA; |
+ return newColumnNames; |
+ } |
+} |