summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/DownloadManager.java4
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/os/Environment.java18
-rw-r--r--core/java/android/os/storage/StorageManager.java35
-rw-r--r--core/java/android/os/storage/StorageVolume.java46
-rw-r--r--core/java/android/os/storage/VolumeRecord.java25
-rw-r--r--core/java/android/provider/BaseColumns.java4
-rw-r--r--core/java/android/provider/MediaStore.java697
-rw-r--r--core/java/com/android/internal/content/FileSystemProvider.java6
9 files changed, 261 insertions, 584 deletions
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 80c9ba2a9c48..eb50581f3319 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1314,8 +1314,8 @@ public class DownloadManager {
// TODO: DownloadProvider.update() should take care of updating corresponding
// MediaProvider entries.
- MediaStore.scanFile(context, before);
- MediaStore.scanFile(context, after);
+ MediaStore.scanFile(mResolver, before);
+ MediaStore.scanFile(mResolver, after);
final ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_TITLE, displayName);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 94af5416aa8d..0d1f4046e70e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1513,16 +1513,6 @@ public abstract class PackageManager {
public static final int DELETE_DONT_KILL_APP = 0x00000008;
/**
- * Flag parameter for {@link #deletePackage} to indicate that any
- * contributed media should also be deleted during this uninstall. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
- *
- * @hide
- */
- public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a92237b9f17f..61da5e67e57b 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -34,6 +34,8 @@ import android.text.TextUtils;
import android.util.Log;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
/**
@@ -528,6 +530,22 @@ public class Environment {
}
/**
+ * Return locations where media files (such as ringtones, notification
+ * sounds, or alarm sounds) may be located on internal storage. These are
+ * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Collection<File> getInternalMediaDirectories() {
+ final ArrayList<File> res = new ArrayList<>();
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage directory. This directory may
* not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 62603fee1137..2e9f27e74544 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -264,6 +264,8 @@ public class StorageManager {
public static final int FLAG_REAL_STATE = 1 << 9;
/** {@hide} */
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_RECENT = 1 << 11;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1125,7 +1127,7 @@ public class StorageManager {
* Return the {@link StorageVolume} that contains the given file, or
* {@code null} if none.
*/
- public @Nullable StorageVolume getStorageVolume(File file) {
+ public @Nullable StorageVolume getStorageVolume(@NonNull File file) {
return getStorageVolume(getVolumeList(), file);
}
@@ -1140,7 +1142,7 @@ public class StorageManager {
return getPrimaryStorageVolume();
default:
for (StorageVolume vol : getStorageVolumes()) {
- if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+ if (Objects.equals(vol.getMediaStoreVolumeName(), volumeName)) {
return vol;
}
}
@@ -1201,12 +1203,13 @@ public class StorageManager {
}
/**
- * Return the list of shared/external storage volumes available to the
- * current user. This includes both the primary shared storage device and
- * any attached external volumes including SD cards and USB drives.
- *
- * @see Environment#getExternalStorageDirectory()
- * @see StorageVolume#createAccessIntent(String)
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user.
+ * <p>
+ * These storage volumes are actively attached to the device, but may be in
+ * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+ * both the primary shared storage device and any attached external volumes,
+ * including SD cards and USB drives.
*/
public @NonNull List<StorageVolume> getStorageVolumes() {
final ArrayList<StorageVolume> res = new ArrayList<>();
@@ -1216,6 +1219,22 @@ public class StorageManager {
}
/**
+ * Return the list of shared/external storage volumes both currently and
+ * recently available to the calling user.
+ * <p>
+ * Recently available storage volumes are likely to reappear in the future,
+ * so apps are encouraged to preserve any indexed metadata related to these
+ * volumes to optimize user experiences.
+ */
+ public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage volume available to the
* current user. This volume is the same storage device returned by
* {@link Environment#getExternalStorageDirectory()} and
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index aefe8430f9de..560d6171d5ee 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -29,6 +29,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.DocumentsContract;
+import android.provider.MediaStore;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -173,7 +174,7 @@ public final class StorageVolume implements Parcelable {
* @return the mount path
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
@TestApi
public String getPath() {
return mPath.toString();
@@ -190,12 +191,35 @@ public final class StorageVolume implements Parcelable {
}
/** {@hide} */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
public File getPathFile() {
return mPath;
}
/**
+ * Returns the directory where this volume is currently mounted.
+ * <p>
+ * Direct filesystem access via this path has significant emulation
+ * overhead, and apps are instead strongly encouraged to interact with media
+ * on storage volumes via the {@link MediaStore} APIs.
+ * <p>
+ * This directory does not give apps any additional access beyond what they
+ * already have via {@link MediaStore}.
+ *
+ * @return directory where this volume is mounted, or {@code null} if the
+ * volume is not currently mounted.
+ */
+ public @Nullable File getDirectory() {
+ switch (mState) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY:
+ return mPath;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Returns a user-visible description of the volume.
*
* @return the volume description
@@ -265,6 +289,24 @@ public final class StorageVolume implements Parcelable {
return mFsUuid;
}
+ /**
+ * Return the volume name that can be used to interact with this storage
+ * device through {@link MediaStore}.
+ *
+ * @return opaque volume name, or {@code null} if this volume is not indexed
+ * by {@link MediaStore}.
+ * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+ */
+ public @Nullable String getMediaStoreVolumeName() {
+ if (isPrimary()) {
+ return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+ } else {
+ return getNormalizedUuid();
+ }
+ }
+
/** {@hide} */
public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1a794ebf2a59..99b45d60c319 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -17,14 +17,18 @@
package android.os.storage;
import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.DebugUtils;
import android.util.TimeUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.util.Locale;
import java.util.Objects;
@@ -92,6 +96,27 @@ public class VolumeRecord implements Parcelable {
return (userFlags & USER_FLAG_SNOOZED) != 0;
}
+ public StorageVolume buildStorageVolume(Context context) {
+ final String id = "unknown:" + fsUuid;
+ final File userPath = new File("/dev/null");
+ final File internalPath = new File("/dev/null");
+ final boolean primary = false;
+ final boolean removable = true;
+ final boolean emulated = false;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+ final String envState = Environment.MEDIA_UNKNOWN;
+
+ String description = nickname;
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, fsUuid, envState);
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("VolumeRecord:");
pw.increaseIndent();
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index 00c9e72df880..b216e2b7ed8d 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -16,13 +16,11 @@
package android.provider;
-import android.database.Cursor;
-
public interface BaseColumns {
/**
* The unique ID for a row.
*/
- @Column(Cursor.FIELD_TYPE_INTEGER)
+ // @Column(Cursor.FIELD_TYPE_INTEGER)
public static final String _ID = "_id";
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 701ba91a8daf..63204d36f396 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -21,17 +21,15 @@ import android.annotation.CurrentTimeMillisLong;
import android.annotation.CurrentTimeSecondsLong;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
-import android.app.AppGlobals;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ContentProviderClient;
@@ -45,39 +43,28 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
-import android.graphics.Point;
import android.graphics.PostProcessor;
import android.media.ExifInterface;
-import android.media.MediaFile;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-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;
-
-import com.android.internal.annotations.GuardedBy;
+import android.util.Size;
import libcore.util.HexEncoding;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -162,9 +149,6 @@ public final class MediaStore {
/** {@hide} */
public static final String SCAN_VOLUME_CALL = "scan_volume";
/** {@hide} */
- public static final String SUICIDE_CALL = "suicide";
-
- /** {@hide} */
public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
/** {@hide} */
public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
@@ -173,42 +157,19 @@ public final class MediaStore {
/** {@hide} */
public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
- /**
- * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
- * the file path originated from shell.
- *
- * {@hide}
- */
- public static final String EXTRA_ORIGINATED_FROM_SHELL =
- "android.intent.extra.originated_from_shell";
-
- /**
- * The method name used by the media scanner and mtp to tell the media provider to
- * rescan and reclassify that have become unhidden because of renaming folders or
- * removing nomedia files
- * @hide
- */
- @Deprecated
- public static final String UNHIDE_CALL = "unhide";
-
- /**
- * The method name used by the media scanner service to reload all localized ringtone titles due
- * to a locale change.
- * @hide
- */
- public static final String RETRANSLATE_CALL = "update_titles";
/** {@hide} */
public static final String GET_VERSION_CALL = "get_version";
+
/** {@hide} */
public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
/** {@hide} */
public static final String GET_MEDIA_URI_CALL = "get_media_uri";
/** {@hide} */
- public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
+ public static final String EXTRA_URI = "uri";
/** {@hide} */
- public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
+ public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
/** {@hide} */
public static final String EXTRA_CLIP_DATA = "clip_data";
@@ -391,10 +352,10 @@ public final class MediaStore {
* service.
* <p>
* This meta-data should reference the fully qualified class name of the prewarm service
- * extending {@link CameraPrewarmService}.
+ * extending {@code CameraPrewarmService}.
* <p>
* The prewarm service will get bound and receive a prewarm signal
- * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
+ * {@code CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
* An application implementing a prewarm service should do the absolute minimum amount of work
* to initialize the camera in order to reduce startup time in likely case that shortly after a
* camera launch intent would be sent.
@@ -775,197 +736,6 @@ public final class MediaStore {
}
/**
- * Create a new pending media item using the given parameters. Pending items
- * are expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @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)
- * @removed
- */
- @Deprecated
- public static @NonNull Uri createPending(@NonNull Context context,
- @NonNull PendingParams params) {
- return context.getContentResolver().insert(params.insertUri, params.insertValues);
- }
-
- /**
- * Open a pending media item to make progress on it. You can open a pending
- * item multiple times before finally calling either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
- *
- * @param uri token which was previously returned from
- * {@link #createPending(Context, PendingParams)}.
- * @removed
- */
- @Deprecated
- public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
- return new PendingSession(context, uri);
- }
-
- /**
- * Parameters that describe a pending media item.
- *
- * @removed
- */
- @Deprecated
- public static class PendingParams {
- /** {@hide} */
- public final Uri insertUri;
- /** {@hide} */
- public final ContentValues insertValues;
-
- /**
- * Create parameters that describe a pending media item.
- *
- * @param insertUri the {@code content://} Uri where this pending item
- * should be inserted when finally published. For example, to
- * publish an image, use
- * {@link MediaStore.Images.Media#getContentUri(String)}.
- */
- public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
- @NonNull String mimeType) {
- this.insertUri = Objects.requireNonNull(insertUri);
- final long now = System.currentTimeMillis() / 1000;
- this.insertValues = new ContentValues();
- this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
- this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
- 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);
- }
-
- public void setRelativePath(@Nullable String relativePath) {
- if (relativePath == null) {
- this.insertValues.remove(MediaColumns.RELATIVE_PATH);
- } else {
- this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
- }
- }
-
- /**
- * Optionally set the Uri from where the file has been downloaded. This is used
- * for files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#DOWNLOAD_URI
- */
- public void setDownloadUri(@Nullable Uri downloadUri) {
- if (downloadUri == null) {
- this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
- } else {
- this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
- }
- }
-
- /**
- * Optionally set the Uri indicating HTTP referer of the file. This is used for
- * files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#REFERER_URI
- */
- public void setRefererUri(@Nullable Uri refererUri) {
- if (refererUri == null) {
- this.insertValues.remove(DownloadColumns.REFERER_URI);
- } else {
- this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
- }
- }
- }
-
- /**
- * Session actively working on a pending media item. Pending items are
- * expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @removed
- */
- @Deprecated
- public static class PendingSession implements AutoCloseable {
- /** {@hide} */
- private final Context mContext;
- /** {@hide} */
- private final Uri mUri;
-
- /** {@hide} */
- public PendingSession(Context context, Uri uri) {
- mContext = Objects.requireNonNull(context);
- mUri = Objects.requireNonNull(uri);
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
- return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link OutputStream#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
- return mContext.getContentResolver().openOutputStream(mUri);
- }
-
- /**
- * Notify of current progress on this pending media item. Gallery
- * applications may choose to surface progress information of this
- * pending item.
- *
- * @param progress a percentage between 0 and 100.
- */
- public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
- final Uri withProgress = mUri.buildUpon()
- .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
- mContext.getContentResolver().notifyChange(withProgress, null, 0);
- }
-
- /**
- * When this media item is successfully completed, call this method to
- * publish and make the final item visible to the user.
- *
- * @return the final {@code content://} Uri representing the newly
- * published media.
- */
- 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;
- }
-
- /**
- * When this media item has failed to be completed, call this method to
- * destroy the pending item record and any data related to it.
- */
- public void abandon() {
- mContext.getContentResolver().delete(mUri, null, null);
- }
-
- @Override
- public void close() {
- // No resources to close, but at least we can inform people that no
- // progress is being actively made.
- notifyProgress(-1);
- }
- }
-
- /**
* 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
@@ -1701,34 +1471,22 @@ public final class MediaStore {
return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static Uri getMtpObjectsUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
+ public static Uri getMtpObjectsUri(@NonNull String volumeName) {
+ return MediaStore.Files.getContentUri(volumeName);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpObjectsUri(String volumeName,
- long fileId) {
- return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
+ public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
- /**
- * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpReferencesUri(String volumeName,
- long fileId) {
- return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
- .build();
+ public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
/**
@@ -1851,9 +1609,21 @@ public final class MediaStore {
public static final int FULL_SCREEN_KIND = 2;
public static final int MICRO_KIND = 3;
- public static final Point MINI_SIZE = new Point(512, 384);
- public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
- public static final Point MICRO_SIZE = new Point(96, 96);
+ public static final Size MINI_SIZE = new Size(512, 384);
+ public static final Size FULL_SCREEN_SIZE = new Size(1024, 786);
+ public static final Size MICRO_SIZE = new Size(96, 96);
+
+ public static @NonNull Size getKindSize(int kind) {
+ if (kind == ThumbnailConstants.MICRO_KIND) {
+ return ThumbnailConstants.MICRO_SIZE;
+ } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+ return ThumbnailConstants.FULL_SCREEN_SIZE;
+ } else if (kind == ThumbnailConstants.MINI_KIND) {
+ return ThumbnailConstants.MINI_SIZE;
+ } else {
+ throw new IllegalArgumentException("Unsupported kind: " + kind);
+ }
+ }
}
/**
@@ -1957,22 +1727,22 @@ public final class MediaStore {
}
}
- /** {@hide} */
+ /**
+ * @deprecated since this method doesn't have a {@link Context}, we can't
+ * find the actual {@link StorageVolume} for the given path, so
+ * only a vague guess is returned. Callers should use
+ * {@link StorageManager#getStorageVolume(File)} instead.
+ * @hide
+ */
+ @Deprecated
public static @NonNull String getVolumeName(@NonNull File path) {
- if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- final StorageVolume sv = sm.getStorageVolume(path);
- if (sv != null) {
- if (sv.isPrimary()) {
- return VOLUME_EXTERNAL_PRIMARY;
- } else {
- return checkArgumentVolumeName(sv.getNormalizedUuid());
- }
- }
- throw new IllegalStateException("Unknown volume at " + path);
+ // Ideally we'd find the relevant StorageVolume, but we don't have a
+ // Context to obtain it from, so the best we can do is assume
+ if (path.getAbsolutePath()
+ .startsWith(Environment.getStorageDirectory().getAbsolutePath())) {
+ return MediaStore.VOLUME_EXTERNAL;
} else {
- return VOLUME_INTERNAL;
+ return MediaStore.VOLUME_INTERNAL;
}
}
@@ -1985,7 +1755,7 @@ public final class MediaStore {
/**
* Currently outstanding thumbnail requests that can be cancelled.
*/
- @GuardedBy("sPending")
+ // @GuardedBy("sPending")
private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
/**
@@ -1997,16 +1767,7 @@ public final class MediaStore {
@Deprecated
static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
int kind, @Nullable BitmapFactory.Options opts) {
- final Point size;
- if (kind == ThumbnailConstants.MICRO_KIND) {
- size = ThumbnailConstants.MICRO_SIZE;
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- size = ThumbnailConstants.FULL_SCREEN_SIZE;
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- size = ThumbnailConstants.MINI_SIZE;
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ final Size size = ThumbnailConstants.getKindSize(kind);
CancellationSignal signal = null;
synchronized (sPending) {
@@ -2018,7 +1779,7 @@ public final class MediaStore {
}
try {
- return cr.loadThumbnail(uri, Point.convert(size), signal);
+ return cr.loadThumbnail(uri, size, signal);
} catch (IOException e) {
Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
return null;
@@ -2215,26 +1976,14 @@ public final class MediaStore {
@Deprecated
public static final String insertImage(ContentResolver cr, String imagePath,
String name, String description) throws FileNotFoundException {
- final File file = new File(imagePath);
- final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
-
- if (TextUtils.isEmpty(name)) name = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
-
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (InputStream in = new FileInputStream(file);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- return session.publish().toString();
- } catch (Exception e) {
- Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
- return null;
+ final Bitmap source;
+ try {
+ source = ImageDecoder
+ .decodeBitmap(ImageDecoder.createSource(new File(imagePath)));
+ } catch (IOException e) {
+ throw new FileNotFoundException(e.getMessage());
}
+ return insertImage(cr, source, name, description);
}
/**
@@ -2251,22 +2000,34 @@ public final class MediaStore {
* control over lifecycle.
*/
@Deprecated
- public static final String insertImage(ContentResolver cr, Bitmap source,
- String title, String description) {
+ public static final String insertImage(ContentResolver cr, Bitmap source, String title,
+ String description) {
if (TextUtils.isEmpty(title)) title = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (OutputStream out = session.openOutputStream()) {
+ final long now = System.currentTimeMillis();
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DISPLAY_NAME, title);
+ values.put(MediaColumns.MIME_TYPE, "image/jpeg");
+ values.put(MediaColumns.DATE_ADDED, now / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, now / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (now + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ try {
+ try (OutputStream out = cr.openOutputStream(uri)) {
source.compress(Bitmap.CompressFormat.JPEG, 90, out);
}
- return session.publish().toString();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ cr.update(uri, values, null, null);
+ return uri.toString();
} catch (Exception e) {
Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
+ cr.delete(uri, null, null);
return null;
}
}
@@ -2526,6 +2287,14 @@ public final class MediaStore {
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The blob raw data of thumbnail
*
* @deprecated this column never existed internally, and could never
@@ -3780,6 +3549,14 @@ public final class MediaStore {
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The width of the thumbnal
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -3811,54 +3588,44 @@ public final class MediaStore {
*/
public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
- final Set<String> volumeNames = new ArraySet<>();
- for (VolumeInfo vi : sm.getVolumes()) {
- if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
- if (vi.isPrimary()) {
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
- } else {
- volumeNames.add(vi.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getStorageVolumes()) {
+ switch (sv.getState()) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY: {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
+ }
+ break;
}
}
}
- return volumeNames;
+ return res;
}
/**
- * Return list of all specific volume names that have recently been part of
+ * Return list of all recent volume names that have been part of
* {@link #VOLUME_EXTERNAL}.
* <p>
- * This includes both currently mounted volumes <em>and</em> recently
- * mounted (but currently unmounted) volumes. Any indexed metadata for these
- * volumes is preserved to optimize the speed of remounting at a later time.
- *
- * @hide
+ * These volume names are not currently mounted, but they're likely to
+ * reappear in the future, so apps are encouraged to preserve any indexed
+ * metadata related to these volumes to optimize user experiences.
+ * <p>
+ * Each specific volume name can be passed to APIs like
+ * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
+ * media on that storage device.
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
-
- // We always have primary storage
- final Set<String> volumeNames = new ArraySet<>();
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-
- final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
- for (VolumeRecord rec : sm.getVolumeRecords()) {
- // Skip volumes without valid UUIDs
- if (TextUtils.isEmpty(rec.fsUuid)) continue;
-
- final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
- if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
- && vi.isMountedReadable()) {
- // We're mounted right now
- volumeNames.add(rec.getNormalizedFsUuid());
- } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
- // We're not mounted right now, but we've been seen recently
- volumeNames.add(rec.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getRecentStorageVolumes()) {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
}
}
- return volumeNames;
+ return res;
}
/**
@@ -3911,97 +3678,6 @@ public final class MediaStore {
}
/**
- * Return path where the given specific volume is mounted. Not valid for
- * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
- * broad collections that cover many paths.
- *
- * @hide
- */
- @TestApi
- public static @NonNull File getVolumePath(@NonNull String volumeName)
- throws FileNotFoundException {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- return getVolumePath(sm.getVolumes(), volumeName);
- }
-
- /** {@hide} */
- public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
- @NonNull String volumeName) throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- switch (volumeName) {
- case VOLUME_INTERNAL:
- case VOLUME_EXTERNAL:
- throw new FileNotFoundException(volumeName + " has no associated path");
- }
-
- final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
- for (VolumeInfo volume : volumes) {
- final boolean matchPrimary = wantPrimary
- && volume.isPrimary();
- final boolean matchSecondary = !wantPrimary
- && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
- if (matchPrimary || matchSecondary) {
- final File path = volume.getPathForUser(UserHandle.myUserId());
- if (path != null) {
- return path;
- }
- }
- }
- throw new FileNotFoundException("Failed to find path for " + volumeName);
- }
-
- /**
- * Return paths that should be scanned for the given volume.
- *
- * @hide
- */
- @TestApi
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
- public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
- throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- final Context context = AppGlobals.getInitialApplication();
- final UserManager um = context.getSystemService(UserManager.class);
-
- final ArrayList<File> res = new ArrayList<>();
- if (VOLUME_INTERNAL.equals(volumeName)) {
- addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
- for (String exactVolume : getExternalVolumeNames(context)) {
- addCanonicalFile(res, getVolumePath(exactVolume));
- }
- if (um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- } else {
- addCanonicalFile(res, getVolumePath(volumeName));
- if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- }
- return res;
- }
-
- private static void addCanonicalFile(List<File> list, File file) {
- try {
- list.add(file.getCanonicalFile());
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve " + file + ": " + e);
- list.add(file);
- }
- }
-
- /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
@@ -4084,10 +3760,10 @@ public final class MediaStore {
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, mediaUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -4114,134 +3790,43 @@ public final class MediaStore {
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, documentUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
+ /** @hide */
+ @TestApi
+ public static void waitForIdle(@NonNull ContentResolver resolver) {
+ resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null);
+ }
+
/**
- * Calculate size of media contributed by given package under the calling
- * user. The meaning of "contributed" means it won't automatically be
- * deleted when the app is uninstalled.
+ * Perform a blocking scan of the given {@link File}, returning the
+ * {@link Uri} of the scanned file.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static @BytesLong long getContributedMediaSize(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
- return out.getLong(Intent.EXTRA_INDEX);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
+ @SuppressLint("StreamFiles")
+ public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) {
+ final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null);
+ return out.getParcelable(Intent.EXTRA_STREAM);
}
/**
- * Delete all media contributed by given package under the calling user. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
+ * Perform a blocking scan of the given storage volume.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static void deleteContributedMedia(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
- }
-
- /** @hide */
- @TestApi
- public static void waitForIdle(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- client.call(WAIT_FOR_IDLE_CALL, null, null);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /** @hide */
- public static void suicide(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver
- .acquireUnstableContentProviderClient(AUTHORITY)) {
- client.call(SUICIDE_CALL, null, null);
- } catch (Exception ignored) {
- }
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFile(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFileFromShell(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, true);
- }
-
- /** @hide */
- @TestApi
- public static void scanVolume(Context context, File file) {
- scan(context, SCAN_VOLUME_CALL, file, false);
- }
-
- /** @hide */
- public static Uri scanFile(ContentProviderClient client, File file) {
- return scan(client, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- private static Uri scan(Context context, String method, File file,
- boolean originatedFromShell) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- return scan(client, method, file, originatedFromShell);
- }
- }
-
- /** @hide */
- private static Uri scan(ContentProviderClient client, String method, File file,
- boolean originatedFromShell) {
- try {
- final Bundle in = new Bundle();
- in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
- in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
- final Bundle out = client.call(method, null, in);
- return out.getParcelable(Intent.EXTRA_STREAM);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) {
+ resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null);
}
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index f5708a5c89af..dec9ae701fb2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -259,7 +259,7 @@ public abstract class FileSystemProvider extends DocumentsProvider {
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
- MediaStore.scanFile(getContext(), file);
+ MediaStore.scanFile(getContext().getContentResolver(), file);
return childId;
}
@@ -316,10 +316,10 @@ public abstract class FileSystemProvider extends DocumentsProvider {
private void moveInMediaStore(@Nullable File oldVisibleFile, @Nullable File newVisibleFile) {
if (oldVisibleFile != null) {
- MediaStore.scanFile(getContext(), oldVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), oldVisibleFile);
}
if (newVisibleFile != null) {
- MediaStore.scanFile(getContext(), newVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), newVisibleFile);
}
}