summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2019-12-15 22:42:42 -0700
committerJeff Sharkey <jsharkey@google.com>2019-12-18 01:35:46 +0000
commit04b4ba1e15fce6af5ccf2ff55eb35e0a763ad4fd (patch)
treeac34cf07b62978eb9159433f87179688eb31315e
parentcd880e9596ed5ab3c3066a94c9f8dba6d0393d23 (diff)
Shuffling to prepare for MediaProvider APEX.
An upcoming change will move MediaStore to be within the recently created MediaProvider APEX. This means that MediaStore will need to be fully built against @SystemApi, and so this CL adjusts APIs to support a clean transition: -- Listing of "recent" storage volumes and scan paths for "internal" storage is now handled by StorageManager directly, so that partners retain control over what is deemed recent. -- StorageVolume now returns the MediaStore volume name and the filesystem directory where its contents are presented to apps. -- Conversion of legacy thumbnail "kind" values to dimensions now happens directly inside MediaStore. -- PendingParams and PendingSession are completely removed. -- Contributed media APIs are completely removed. -- Media for demo users is now surfaced as a unique StorageVolume. -- Migrate most MediaStore APIs to accept ContentResolver, which supports easy usage of ContentResolver.wrap(). Bug: 144247087, 137890034 Test: atest --test-mapping packages/providers/MediaProvider Exempt-From-Owner-Approval: in-place refactoring Change-Id: I445528b2779bb37b9f2558e67a3cfc9f60412092
-rw-r--r--api/current.txt8
-rw-r--r--api/removed.txt18
-rwxr-xr-xapi/system-current.txt5
-rw-r--r--api/test-current.txt11
-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
-rw-r--r--media/java/android/media/MediaScannerConnection.java3
-rw-r--r--media/java/android/media/RingtoneManager.java2
-rw-r--r--media/java/android/media/ThumbnailUtils.java13
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java16
-rw-r--r--media/java/android/mtp/MtpStorage.java6
-rw-r--r--packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java38
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java42
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java8
23 files changed, 359 insertions, 661 deletions
diff --git a/api/current.txt b/api/current.txt
index 6ddd847435f4..7c0a134063c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36025,7 +36025,8 @@ package android.os.storage {
method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
method public String getMountedObbPath(String);
method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
- method @Nullable public android.os.storage.StorageVolume getStorageVolume(java.io.File);
+ method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
+ method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
@@ -36051,6 +36052,8 @@ package android.os.storage {
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
method public int describeContents();
method public String getDescription(android.content.Context);
+ method @Nullable public java.io.File getDirectory();
+ method @Nullable public String getMediaStoreVolumeName();
method public String getState();
method @Nullable public String getUuid();
method public boolean isEmulated();
@@ -38721,6 +38724,7 @@ package android.provider {
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
method public static boolean getRequireOriginal(@NonNull android.net.Uri);
method @NonNull public static String getVersion(@NonNull android.content.Context);
method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
@@ -38986,6 +38990,7 @@ package android.provider {
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -39068,6 +39073,7 @@ package android.provider {
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
field @Deprecated public static final String DATA = "_data";
diff --git a/api/removed.txt b/api/removed.txt
index 4e8e325bce15..8b30d0a5cf39 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -436,10 +436,8 @@ package android.provider {
}
public final class MediaStore {
- method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
- method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
@@ -473,22 +471,6 @@ package android.provider {
field @Deprecated public static final String GROUP_ID = "group_id";
}
- @Deprecated public static class MediaStore.PendingParams {
- ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
- method public void setDownloadUri(@Nullable android.net.Uri);
- method public void setRefererUri(@Nullable android.net.Uri);
- method public void setRelativePath(@Nullable String);
- }
-
- @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
- method public void abandon();
- method public void close();
- method public void notifyProgress(@IntRange(from=0, to=100) int);
- method @NonNull public android.os.ParcelFileDescriptor open() throws java.io.FileNotFoundException;
- method @NonNull public java.io.OutputStream openOutputStream() throws java.io.FileNotFoundException;
- method @NonNull public android.net.Uri publish();
- }
-
public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ARTIST = "artist";
diff --git a/api/system-current.txt b/api/system-current.txt
index 8d55a33faedd..8943a155d199 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6365,6 +6365,7 @@ package android.os {
}
public class Environment {
+ method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
method @NonNull public static java.io.File getOdmDirectory();
method @NonNull public static java.io.File getOemDirectory();
method @NonNull public static java.io.File getProductDirectory();
@@ -7253,8 +7254,8 @@ package android.provider {
}
public final class MediaStore {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
}
public abstract class SearchIndexableData {
diff --git a/api/test-current.txt b/api/test-current.txt
index 1c6bce05b8f8..5381376d7568 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2467,14 +2467,9 @@ package android.provider {
}
public final class MediaStore {
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
- method public static android.net.Uri scanFile(android.content.Context, java.io.File);
- method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
- method public static void scanVolume(android.content.Context, java.io.File);
- method public static void waitForIdle(android.content.Context);
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+ method public static void waitForIdle(@NonNull android.content.ContentResolver);
}
public final class Settings {
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);
}
}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 515f6a8ce8a2..40e90731f2a2 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -19,6 +19,7 @@ package android.media;
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ServiceConnection;
import android.net.Uri;
@@ -197,7 +198,7 @@ public class MediaScannerConnection implements ServiceConnection {
private static Uri scanFileQuietly(ContentProviderClient client, File file) {
Uri uri = null;
try {
- uri = MediaStore.scanFile(client, file.getCanonicalFile());
+ uri = MediaStore.scanFile(ContentResolver.wrap(client), file.getCanonicalFile());
Log.d(TAG, "Scanned " + file + " to " + uri);
} catch (Exception e) {
Log.w(TAG, "Failed to scan " + file + ": " + e);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 9064e6891be6..a1e159174f95 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -908,7 +908,7 @@ public class RingtoneManager {
}
// Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
- return MediaStore.scanFile(mContext, outFile);
+ return MediaStore.scanFile(mContext.getContentResolver(), outFile);
}
private static final String getExternalDirectoryForType(final int type) {
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index a315c1eefb52..6705b0cb79f0 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -33,14 +33,13 @@ import android.graphics.ImageDecoder;
import android.graphics.ImageDecoder.ImageInfo;
import android.graphics.ImageDecoder.Source;
import android.graphics.Matrix;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.ThumbnailConstants;
+import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
@@ -77,15 +76,7 @@ public class ThumbnailUtils {
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
private static Size convertKind(int kind) {
- if (kind == ThumbnailConstants.MICRO_KIND) {
- return Point.convert(ThumbnailConstants.MICRO_SIZE);
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- return Point.convert(ThumbnailConstants.MINI_SIZE);
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ return MediaStore.Images.Thumbnails.getKindSize(kind);
}
private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 0f402eb0bf42..f3c071a06eba 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,8 +16,10 @@
package android.mtp;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -422,13 +424,13 @@ public class MtpDatabase implements AutoCloseable {
}
// Add the new file to MediaProvider
if (succeeded) {
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
}
@VisibleForNative
private void rescanFile(String path, int handle, int format) {
- MediaStore.scanFile(mContext, new File(path));
+ MediaStore.scanFile(mContext.getContentResolver(), new File(path));
}
@VisibleForNative
@@ -587,13 +589,13 @@ public class MtpDatabase implements AutoCloseable {
if (obj.isDir()) {
// for directories, check if renamed from something hidden to something non-hidden
if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
- MediaStore.scanFile(mContext, newPath.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
}
} else {
// for files, check if renamed from .nomedia to something else
if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
&& !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
- MediaStore.scanFile(mContext, newPath.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
}
}
return MtpConstants.RESPONSE_OK;
@@ -662,7 +664,7 @@ public class MtpDatabase implements AutoCloseable {
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} else {
// Old parent doesn't exist - add the object
- MediaStore.scanFile(mContext, path.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -689,7 +691,7 @@ public class MtpDatabase implements AutoCloseable {
if (!success) {
return;
}
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
@VisibleForNative
@@ -909,7 +911,7 @@ public class MtpDatabase implements AutoCloseable {
String[] whereArgs = new String[]{path.toString()};
if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
- MediaStore.scanFile(mContext, path.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
}
} else {
Log.i(TAG, "Mediaprovider didn't delete " + path);
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 65d0fef74b25..c7dbca61f90a 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -41,11 +41,7 @@ public class MtpStorage {
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
- if (volume.isPrimary()) {
- mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
- } else {
- mVolumeName = volume.getNormalizedUuid();
- }
+ mVolumeName = volume.getMediaStoreVolumeName();
}
/**
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
index 2d37b4c9c342..15fd1f7f4957 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -101,7 +101,7 @@ public class RingtoneOverlayService extends Service {
}
private Uri scanFile(@NonNull final File file) {
- return MediaStore.scanFile(this, file);
+ return MediaStore.scanFile(getContentResolver(), file);
}
private void set(@NonNull final String name, @NonNull final Uri uri) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 0383dee4f9c3..d3ccbeb0afb1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -371,7 +371,8 @@ public class RecordingService extends Service {
Bitmap thumbnailBitmap = null;
try {
ContentResolver resolver = getContentResolver();
- Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE);
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 76925b43cfb8..2f401e5271d8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -23,6 +23,8 @@ import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
@@ -48,7 +50,9 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -275,6 +279,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Context context = mParams.context;
+ ContentResolver resolver = context.getContentResolver();
Bitmap image = mParams.image;
Resources r = context.getResources();
@@ -284,23 +289,27 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
// Save the screenshot to the MediaStore
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
- params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
- + Environment.DIRECTORY_SCREENSHOTS);
-
- final Uri uri = MediaStore.createPending(context, params);
- final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+ values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
+ values.put(MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
// First, write the actual data for our screenshot
- try (OutputStream out = session.openOutputStream()) {
+ try (OutputStream out = resolver.openOutputStream(uri)) {
if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
throw new IOException("Failed to compress");
}
}
// Next, write metadata to help index the screenshot
- try (ParcelFileDescriptor pfd = session.open()) {
+ try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
exif.setAttribute(ExifInterface.TAG_SOFTWARE,
@@ -327,12 +336,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
exif.saveAttributes();
}
- session.publish();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ resolver.update(uri, values, null, null);
} catch (Exception e) {
- session.abandon();
+ resolver.delete(uri, null);
throw e;
- } finally {
- IoUtils.closeQuietly(session);
}
populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9b1d9e9a246c..5a78036911a8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -118,6 +118,7 @@ import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DataUnit;
import android.util.FeatureFlagUtils;
@@ -3161,16 +3162,20 @@ class StorageManagerService extends IStorageManager.Stub
final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+ final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
// point
final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
+ final boolean userIsDemo;
final boolean userKeyUnlocked;
final boolean storagePermission;
final long token = Binder.clearCallingIdentity();
try {
+ userIsDemo = LocalServices.getService(UserManagerInternal.class)
+ .getUserInfo(userId).isDemo();
userKeyUnlocked = isUserKeyUnlocked(userId);
storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
} finally {
@@ -3180,6 +3185,7 @@ class StorageManagerService extends IStorageManager.Stub
boolean foundPrimary = false;
final ArrayList<StorageVolume> res = new ArrayList<>();
+ final ArraySet<String> resUuids = new ArraySet<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final VolumeInfo vol = mVolumes.valueAt(i);
@@ -3222,7 +3228,43 @@ class StorageManagerService extends IStorageManager.Stub
} else {
res.add(userVol);
}
+ resUuids.add(userVol.getUuid());
}
+
+ if (includeRecent) {
+ final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+
+ // Skip if we've already included it above
+ if (resUuids.contains(rec.fsUuid)) continue;
+
+ // Treat as recent if mounted within the last week
+ if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+ final StorageVolume userVol = rec.buildStorageVolume(mContext);
+ res.add(userVol);
+ resUuids.add(userVol.getUuid());
+ }
+ }
+ }
+ }
+
+ // Synthesize a volume for preloaded media under demo users, so that
+ // it's scanned into MediaStore
+ if (userIsDemo) {
+ final String id = "demo";
+ final File path = Environment.getDataPreloadsMediaDirectory();
+ final boolean primary = false;
+ final boolean removable = false;
+ final boolean emulated = true;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(userId);
+ final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+ final String description = mContext.getString(android.R.string.unknownName);
+
+ res.add(new StorageVolume(id, path, path, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, id, envState));
}
if (!foundPrimary) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1153fb521c53..99d5e4a21df4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17962,14 +17962,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
- // Also delete contributed media, when requested
- if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
- try {
- MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
- } catch (IOException e) {
- Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
- }
- }
}
if (outInfo != null) {