diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 7e1df1b0e59cf..114ad874d0c46 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1885,9 +1885,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * in {@link android.provider.MediaStore.MediaColumns}.

* * @param uri The URI whose file is to be opened. - * @param mode Access mode for the file. May be "r" for read-only access, - * "rw" for read and write access, or "rwt" for read and write access - * that truncates any existing file. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * * @return Returns a new ParcelFileDescriptor which you can use to access * the file. @@ -1948,10 +1948,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * in {@link android.provider.MediaStore.MediaColumns}.

* * @param uri The URI whose file is to be opened. - * @param mode Access mode for the file. May be "r" for read-only access, - * "w" for write-only access, "rw" for read and write access, or - * "rwt" for read and write access that truncates any existing - * file. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @param signal A signal to cancel the operation in progress, or * {@code null} if none. For example, if you are downloading a * file from the network to service a "rw" mode request, you @@ -2011,11 +2010,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * containing at least the columns specified by {@link android.provider.OpenableColumns}.

* * @param uri The URI whose file is to be opened. - * @param mode Access mode for the file. May be "r" for read-only access, - * "w" for write-only access (erasing whatever data is currently in - * the file), "wa" for write-only access to append to any existing data, - * "rw" for read and write access on any existing data, and "rwt" for read - * and write access that truncates any existing file. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * * @return Returns a new AssetFileDescriptor which you can use to access * the file. @@ -2068,11 +2065,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * containing at least the columns specified by {@link android.provider.OpenableColumns}.

* * @param uri The URI whose file is to be opened. - * @param mode Access mode for the file. May be "r" for read-only access, - * "w" for write-only access (erasing whatever data is currently in - * the file), "wa" for write-only access to append to any existing data, - * "rw" for read and write access on any existing data, and "rwt" for read - * and write access that truncates any existing file. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @param signal A signal to cancel the operation in progress, or * {@code null} if none. For example, if you are downloading a * file from the network to service a "rw" mode request, you @@ -2103,11 +2098,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * by looking up a column named "_data" at the given URI. * * @param uri The URI to be opened. - * @param mode The file mode. May be "r" for read-only access, - * "w" for write-only access (erasing whatever data is currently in - * the file), "wa" for write-only access to append to any existing data, - * "rw" for read and write access on any existing data, and "rwt" for read - * and write access that truncates any existing file. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * * @return Returns a new ParcelFileDescriptor that can be used by the * client to access the file. diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index aec39da973f08..1132991a57f8a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -63,7 +63,9 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.system.ErrnoException; import android.system.Int64Ref; +import android.system.Os; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -76,8 +78,10 @@ import com.android.internal.util.MimeIconUtils; import dalvik.system.CloseGuard; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -864,6 +868,20 @@ public abstract class ContentResolver implements ContentInterface { return wrap((ContentInterface) wrapped); } + /** + * Offer to locally truncate the given file when opened using the write-only + * mode. This is typically used to preserve legacy compatibility behavior. + */ + private static void maybeTruncate(FileDescriptor fd, String mode) throws FileNotFoundException { + if ("w".equals(mode)) { + try { + Os.ftruncate(fd, 0); + } catch (ErrnoException e) { + throw new FileNotFoundException("Failed to truncate: " + e.getMessage()); + } + } + } + /** @hide */ @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage @@ -1525,8 +1543,20 @@ public abstract class ContentResolver implements ContentInterface { } /** - * Synonym for {@link #openOutputStream(Uri, String) - * openOutputStream(uri, "w")}. + * Open a stream on to the content associated with a content URI. If there + * is no data associated with the URI, FileNotFoundException is thrown. + * + *
Accepts the following URI schemes:
+ * + * + *

See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + * + *

This method behaves like {@link FileOutputStream} and automatically + * truncates any existing contents. * * @param uri The desired URI. * @return an OutputStream or {@code null} if the provider recently crashed. @@ -1534,7 +1564,16 @@ public abstract class ContentResolver implements ContentInterface { */ public final @Nullable OutputStream openOutputStream(@NonNull Uri uri) throws FileNotFoundException { - return openOutputStream(uri, "w"); + AssetFileDescriptor fd = openAssetFileDescriptor(uri, "w", null); + if (fd == null) return null; + try { + final FileOutputStream res = fd.createOutputStream(); + // Unconditionally truncate to mirror FileOutputStream behavior + maybeTruncate(res.getFD(), "w"); + return res; + } catch (IOException e) { + throw new FileNotFoundException("Unable to create stream"); + } } /** @@ -1551,7 +1590,9 @@ public abstract class ContentResolver implements ContentInterface { * on these schemes. * * @param uri The desired URI. - * @param mode May be "w", "wa", "rw", or "rwt". + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @return an OutputStream or {@code null} if the provider recently crashed. * @throws FileNotFoundException if the provided URI could not be opened. * @see #openAssetFileDescriptor(Uri, String) @@ -1559,8 +1600,14 @@ public abstract class ContentResolver implements ContentInterface { public final @Nullable OutputStream openOutputStream(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode, null); + if (fd == null) return null; try { - return fd != null ? fd.createOutputStream() : null; + final FileOutputStream res = fd.createOutputStream(); + // Preserve legacy behavior by offering to truncate + if (mTargetSdkVersion < Build.VERSION_CODES.Q) { + maybeTruncate(res.getFD(), mode); + } + return res; } catch (IOException e) { throw new FileNotFoundException("Unable to create stream"); } @@ -1607,8 +1654,9 @@ public abstract class ContentResolver implements ContentInterface { * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. * * @param uri The desired URI to open. - * @param mode The file mode to use, as per {@link ContentProvider#openFile - * ContentProvider.openFile}. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the * provider recently crashed. You own this descriptor and are responsible for closing it * when done. @@ -1650,8 +1698,9 @@ public abstract class ContentResolver implements ContentInterface { * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. * * @param uri The desired URI to open. - * @param mode The file mode to use, as per {@link ContentProvider#openFile - * ContentProvider.openFile}. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @param cancellationSignal A signal to cancel the operation in progress, * or null if none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. @@ -1744,8 +1793,9 @@ public abstract class ContentResolver implements ContentInterface { * from any built-in data conversion that a provider implements. * * @param uri The desired URI to open. - * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile - * ContentProvider.openAssetFile}. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the * provider recently crashed. You own this descriptor and are responsible for closing it * when done. @@ -1798,8 +1848,9 @@ public abstract class ContentResolver implements ContentInterface { * from any built-in data conversion that a provider implements. * * @param uri The desired URI to open. - * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile - * ContentProvider.openAssetFile}. + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". See + * {@link ParcelFileDescriptor#parseMode} for more details. * @param cancellationSignal A signal to cancel the operation in progress, or null if * none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. @@ -1835,6 +1886,10 @@ public abstract class ContentResolver implements ContentInterface { } else if (SCHEME_FILE.equals(scheme)) { ParcelFileDescriptor pfd = ParcelFileDescriptor.open( new File(uri.getPath()), ParcelFileDescriptor.parseMode(mode)); + // Preserve legacy behavior by offering to truncate + if (mTargetSdkVersion < Build.VERSION_CODES.Q) { + maybeTruncate(pfd.getFileDescriptor(), mode); + } return new AssetFileDescriptor(pfd, 0, -1); } else { if ("r".equals(mode)) { @@ -1892,6 +1947,11 @@ public abstract class ContentResolver implements ContentInterface { // ParcelFileDescriptorInner do that when it is closed. stableProvider = null; + // Preserve legacy behavior by offering to truncate + if (mTargetSdkVersion < Build.VERSION_CODES.Q) { + maybeTruncate(pfd.getFileDescriptor(), mode); + } + return new AssetFileDescriptor(pfd, fd.getStartOffset(), fd.getDeclaredLength()); diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index c0847872a45a0..0c9ad1b17a7e4 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -31,6 +31,7 @@ import static android.system.OsConstants.S_ISLNK; import static android.system.OsConstants.S_ISREG; import static android.system.OsConstants.S_IWOTH; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -63,6 +64,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.io.UncheckedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.Socket; import java.nio.ByteOrder; @@ -110,6 +113,20 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { private final CloseGuard mGuard = CloseGuard.get(); + /** @hide */ + @IntDef(prefix = {"MODE_"}, value = { + MODE_WORLD_READABLE, + MODE_WORLD_WRITEABLE, + MODE_READ_ONLY, + MODE_WRITE_ONLY, + MODE_READ_WRITE, + MODE_CREATE, + MODE_TRUNCATE, + MODE_APPEND, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Mode { } + /** * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and * this file doesn't already exist, then create the file with permissions @@ -227,7 +244,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * be opened with the requested mode. * @see #parseMode(String) */ - public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException { + public static ParcelFileDescriptor open(File file, @Mode int mode) + throws FileNotFoundException { final FileDescriptor fd = openInternal(file, mode); if (fd == null) return null; @@ -259,7 +277,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { // We can't accept a generic Executor here, since we need to use // MessageQueue.addOnFileDescriptorEventListener() @SuppressLint("ExecutorRegistration") - public static ParcelFileDescriptor open(File file, int mode, Handler handler, + public static ParcelFileDescriptor open(File file, @Mode int mode, Handler handler, final OnCloseListener listener) throws IOException { if (handler == null) { throw new IllegalArgumentException("Handler must not be null"); @@ -330,7 +348,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return pfd; } - private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { + private static FileDescriptor openInternal(File file, @Mode int mode) + throws FileNotFoundException { final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC); int realMode = S_IRWXU | S_IRWXG; @@ -623,15 +642,36 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } /** - * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use - * with {@link #open}. + * Converts a string representing a file mode, such as "rw", into a bitmask + * suitable for use with {@link #open}. *

- * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" - * or "rwt". + * The argument must define at least one of the following base access modes: + *

+ * In addition to a base access mode, the following additional modes may + * requested: + * + * + * @param mode The string representation of the file mode. Can be "r", "w", + * "wt", "wa", "rw" or "rwt". * @return A bitmask representing the given file mode. - * @throws IllegalArgumentException if the given string does not match a known file mode. + * @throws IllegalArgumentException if the given string does not match a + * known file mode. */ - public static int parseMode(String mode) { + public static @Mode int parseMode(String mode) { return FileUtils.translateModePosixToPfd(FileUtils.translateModeStringToPosix(mode)); }