Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5cc407f7 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Public APIs for "trashing" media.

The "delete" operation is immediate and permanent, and users may wish
to instead mark content as being "trashed", so they can recover
accidentally trashed items before they're permanently deleted.

The default trash timeout is 48 hours, which should be enough time
to recover items the user cares about.  Apps can also use a custom
timeout if desired.

This is implemented by recording an "expiration" time for trashed
items, and deleting expired items during the next idle maintenance
pass.  Also use this expiration time to clean up pending items that
haven't been published; by default apps have a day to publish
pending items.

Bug: 121227045
Test: atest MediaProviderTests
Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore*
Change-Id: I2e371b308dc135ad5363709a6f5385e4456bcb96
parent 7b148d7a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -37855,7 +37855,11 @@ package android.provider {
    method public static java.lang.String getVolumeName(android.net.Uri);
    method public static android.provider.MediaStore.PendingSession openPending(android.content.Context, android.net.Uri);
    method public static android.net.Uri setIncludePending(android.net.Uri);
    method public static android.net.Uri setIncludeTrashed(android.net.Uri);
    method public static android.net.Uri setRequireOriginal(android.net.Uri);
    method public static void trash(android.content.Context, android.net.Uri);
    method public static void trash(android.content.Context, android.net.Uri, long);
    method public static void untrash(android.content.Context, android.net.Uri);
    field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
    field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
    field public static final java.lang.String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -38123,11 +38127,13 @@ package android.provider {
  public static abstract interface MediaStore.MediaColumns implements android.provider.BaseColumns {
    field public static final deprecated java.lang.String DATA = "_data";
    field public static final java.lang.String DATE_ADDED = "date_added";
    field public static final java.lang.String DATE_EXPIRES = "date_expires";
    field public static final java.lang.String DATE_MODIFIED = "date_modified";
    field public static final java.lang.String DISPLAY_NAME = "_display_name";
    field public static final java.lang.String HASH = "_hash";
    field public static final java.lang.String HEIGHT = "height";
    field public static final java.lang.String IS_PENDING = "is_pending";
    field public static final java.lang.String IS_TRASHED = "is_trashed";
    field public static final java.lang.String MIME_TYPE = "mime_type";
    field public static final java.lang.String OWNER_PACKAGE_NAME = "owner_package_name";
    field public static final java.lang.String SIZE = "_size";
+111 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.provider;

import android.annotation.BytesLong;
import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@ import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.service.media.CameraPrewarmService;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -131,6 +133,8 @@ public final class MediaStore {
    /** {@hide} */
    public static final String PARAM_INCLUDE_PENDING = "includePending";
    /** {@hide} */
    public static final String PARAM_INCLUDE_TRASHED = "includeTrashed";
    /** {@hide} */
    public static final String PARAM_PROGRESS = "progress";
    /** {@hide} */
    public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal";
@@ -485,11 +489,28 @@ public final class MediaStore {
     * By default no pending items are returned.
     *
     * @see MediaColumns#IS_PENDING
     * @see MediaStore#setIncludePending(Uri)
     * @see MediaStore#createPending(Context, PendingParams)
     */
    public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
        return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_PENDING, "1").build();
    }

    /**
     * Update the given {@link Uri} to also include any trashed media items from
     * calls such as
     * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
     * By default no trashed items are returned.
     *
     * @see MediaColumns#IS_TRASHED
     * @see MediaStore#setIncludeTrashed(Uri)
     * @see MediaStore#trash(Context, Uri)
     * @see MediaStore#untrash(Context, Uri)
     */
    public static @NonNull Uri setIncludeTrashed(@NonNull Uri uri) {
        return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_TRASHED, "1").build();
    }

    /**
     * Update the given {@link Uri} to indicate that the caller requires the
     * original file contents when calling
@@ -516,6 +537,9 @@ public final class MediaStore {
     *
     * @return token which can be passed to {@link #openPending(Context, Uri)}
     *         to work with this pending item.
     * @see MediaColumns#IS_PENDING
     * @see MediaStore#setIncludePending(Uri)
     * @see MediaStore#createPending(Context, PendingParams)
     */
    public static @NonNull Uri createPending(@NonNull Context context,
            @NonNull PendingParams params) {
@@ -572,6 +596,8 @@ public final class MediaStore {
            this.insertValues.put(MediaColumns.DATE_ADDED, now);
            this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
            this.insertValues.put(MediaColumns.IS_PENDING, 1);
            this.insertValues.put(MediaColumns.DATE_EXPIRES,
                    (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
        }

        /**
@@ -696,6 +722,7 @@ public final class MediaStore {
        public @NonNull Uri publish() {
            final ContentValues values = new ContentValues();
            values.put(MediaColumns.IS_PENDING, 0);
            values.putNull(MediaColumns.DATE_EXPIRES);
            mContext.getContentResolver().update(mUri, values, null, null);
            return mUri;
        }
@@ -716,6 +743,67 @@ public final class MediaStore {
        }
    }

    /**
     * Mark the given item as being "trashed", meaning it should be deleted at
     * some point in the future. This is a more gentle operation than simply
     * calling {@link ContentResolver#delete(Uri, String, String[])}, which
     * would take effect immediately.
     * <p>
     * This method preserves trashed items for at least 48 hours before erasing
     * them, giving the user a chance to untrash the item.
     *
     * @see MediaColumns#IS_TRASHED
     * @see MediaStore#setIncludeTrashed(Uri)
     * @see MediaStore#trash(Context, Uri)
     * @see MediaStore#untrash(Context, Uri)
     */
    public static void trash(@NonNull Context context, @NonNull Uri uri) {
        trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
    }

    /**
     * Mark the given item as being "trashed", meaning it should be deleted at
     * some point in the future. This is a more gentle operation than simply
     * calling {@link ContentResolver#delete(Uri, String, String[])}, which
     * would take effect immediately.
     * <p>
     * This method preserves trashed items for at least the given timeout before
     * erasing them, giving the user a chance to untrash the item.
     *
     * @see MediaColumns#IS_TRASHED
     * @see MediaStore#setIncludeTrashed(Uri)
     * @see MediaStore#trash(Context, Uri)
     * @see MediaStore#untrash(Context, Uri)
     */
    public static void trash(@NonNull Context context, @NonNull Uri uri,
            @DurationMillisLong long timeoutMillis) {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException();
        }

        final ContentValues values = new ContentValues();
        values.put(MediaColumns.IS_TRASHED, 1);
        values.put(MediaColumns.DATE_EXPIRES,
                (System.currentTimeMillis() + timeoutMillis) / 1000);
        context.getContentResolver().update(uri, values, null, null);
    }

    /**
     * Mark the given item as being "untrashed", meaning it should no longer be
     * deleted as previously requested through {@link #trash(Context, Uri)}.
     *
     * @see MediaColumns#IS_TRASHED
     * @see MediaStore#setIncludeTrashed(Uri)
     * @see MediaStore#trash(Context, Uri)
     * @see MediaStore#untrash(Context, Uri)
     */
    public static void untrash(@NonNull Context context, @NonNull Uri uri) {
        final ContentValues values = new ContentValues();
        values.put(MediaColumns.IS_TRASHED, 0);
        values.putNull(MediaColumns.DATE_EXPIRES);
        context.getContentResolver().update(uri, values, null, null);
    }

    /**
     * Common fields for most MediaProvider tables
     */
@@ -821,11 +909,33 @@ public final class MediaStore {
         * <p>
         * Type: BOOLEAN
         *
         * @see MediaColumns#IS_PENDING
         * @see MediaStore#setIncludePending(Uri)
         * @see MediaStore#createPending(Context, PendingParams)
         * @see MediaStore#PARAM_INCLUDE_PENDING
         */
        public static final String IS_PENDING = "is_pending";

        /**
         * Flag indicating if a media item is trashed.
         * <p>
         * Type: BOOLEAN
         *
         * @see MediaColumns#IS_TRASHED
         * @see MediaStore#setIncludeTrashed(Uri)
         * @see MediaStore#trash(Context, Uri)
         * @see MediaStore#untrash(Context, Uri)
         */
        public static final String IS_TRASHED = "is_trashed";

        /**
         * The time the file should be considered expired. Units are seconds
         * since 1970. Typically only meaningful in the context of
         * {@link #IS_PENDING} or {@link #IS_TRASHED}.
         * <p>
         * Type: INTEGER
         */
        public static final String DATE_EXPIRES = "date_expires";

        /**
         * The width of the image/video in pixels.
         */