Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/BlockingFileProvider.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BlockingFileProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/BlockingFileProvider.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..06f4576ae591d81a4b4143de69e4909b6bb50490 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/BlockingFileProvider.java |
| @@ -0,0 +1,175 @@ |
| +// 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; |
| + |
| +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 BlockingFileProvider extends FileProvider { |
|
Ted C
2016/08/27 00:22:12
I'd probably just call this ChromeFileProvider or
ssid
2016/09/06 22:25:36
Done.
|
| + private static final String AUTHORITY_SUFFIX = ".FileProvider"; |
| + private static final String BLOCKED_FILE_PREFIX = "BlockingFileProvider_"; |
| + |
| + // All these static objects must be accesseed in a synchronized block: |
| + private static Object sLock = new Object(); |
| + private static boolean sIsFileReady; |
| + private static Uri sCurrentUuidUri; |
| + 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 activity Activity that is used to access package manager. |
| + */ |
| + public static Uri generateUriAndBlockAccess(final Context activity) { |
|
Ted C
2016/08/27 00:22:12
s/activity/context (and update javadoc)
ssid
2016/09/06 22:25:36
Done.
|
| + String authority = activity.getPackageName() + AUTHORITY_SUFFIX; |
| + String fileName = BLOCKED_FILE_PREFIX + String.valueOf(System.currentTimeMillis()); |
| + Uri uuidUri = |
|
Ted C
2016/08/27 00:22:12
When I think of uuid, I think of this:
https://en.
ssid
2016/09/06 22:25:35
Done.
|
| + new Uri.Builder().scheme("content").authority(authority).path(fileName).build(); |
| + synchronized (sLock) { |
| + sCurrentUuidUri = uuidUri; |
| + sFileUri = null; |
| + sIsFileReady = false; |
| + // In case the previous file never got ready. |
| + sLock.notify(); |
| + } |
| + return uuidUri; |
| + } |
| + |
| + /** |
| + * Notify that the file is ready to be accessed by the client application. |
| + * |
| + * @param uuidUri The unique uri that was generated by generateUriAndBlockAccess. |
| + * @param fileUri The Uri for actual file given by FileProvider. |
| + */ |
| + public static void notifyFileReady(Uri uuidUri, Uri fileUri) { |
| + synchronized (sLock) { |
| + sFileUri = fileUri; |
| + // Ready is set only if the current file is ready. |
| + sIsFileReady = doesMatchCurrentUuid(uuidUri); |
| + 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) { |
| + synchronized (sLock) { |
| + sFileUri = null; |
|
Ted C
2016/08/27 00:22:11
should we only do this if doesMatchCurrentUuid?
ssid
2016/09/06 22:25:35
Yes, missed it.
|
| + sIsFileReady = false; |
| + sCurrentUuidUri = 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. |
| + */ |
| + private Uri getFileUriWhenReady(Uri uri) { |
|
Ted C
2016/08/27 00:22:11
looking at the test, I'd make this protected (mark
ssid
2016/09/06 22:25:36
Done.
|
| + // If the uri passed is not a blocked file, then the given uri can be directly used. |
| + if (!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 && doesMatchCurrentUuid(uri)) { |
| + try { |
| + sLock.wait(); |
| + } catch (InterruptedException e) { |
| + break; |
| + } |
| + } |
| + // If the current file has changed while waiting, return null. |
| + if (doesMatchCurrentUuid(uri)) return sFileUri; |
| + } |
| + return null; |
| + } |
| + |
| + private static boolean doesMatchCurrentUuid(Uri uri) { |
| + return uri != null && sCurrentUuidUri != null |
| + && sCurrentUuidUri.toString().equals(uri.toString()); |
|
Ted C
2016/08/27 00:22:12
Uri.equals does this internally, so I think you ca
ssid
2016/09/06 22:25:36
Done.
|
| + } |
| + |
| + 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; |
| + } |
| +} |