| 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;
|
| + }
|
| +}
|
|
|