Merge "Fix DATA column docs" into sc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 336e8eb..d57a013 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -89,10 +89,6 @@
<data android:scheme="package" />
</intent-filter>
<intent-filter>
- <action android:name="android.intent.action.MEDIA_MOUNTED" />
- <data android:scheme="file" />
- </intent-filter>
- <intent-filter>
<action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
<data android:scheme="file" />
</intent-filter>
diff --git a/apex/Android.bp b/apex/Android.bp
index a5e9942..2668806 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -17,7 +17,8 @@
apex_defaults {
name: "com.android.mediaprovider-defaults",
- java_libs: ["framework-mediaprovider"],
+ bootclasspath_fragments: ["com.android.mediaprovider-bootclasspath-fragment"],
+ prebuilts: ["current_sdkinfo"],
key: "com.android.mediaprovider.key",
certificate: ":com.android.mediaprovider.certificate",
file_contexts: ":com.android.mediaprovider-file_contexts",
@@ -45,3 +46,10 @@
"framework-mediaprovider",
],
}
+
+// Encapsulate the contributions made by the com.android.mediaprovider to the bootclasspath.
+bootclasspath_fragment {
+ name: "com.android.mediaprovider-bootclasspath-fragment",
+ contents: ["framework-mediaprovider"],
+ apex_available: ["com.android.mediaprovider"],
+}
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index 6d8da53..d87ce8d 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.mediaprovider",
- "version": 309999900
+ "version": 309999910
}
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index a482e01..833e9c6 100755
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -749,13 +749,8 @@
ATRACE_CALL();
struct fuse* fuse = get_fuse(req);
- if (mode) {
- fuse_reply_err(req, EOPNOTSUPP);
- return;
- }
-
handle* h = reinterpret_cast<handle*>(fi->fh);
- auto err = posix_fallocate(h->fd, offset, length);
+ auto err = fallocate(h->fd, mode, offset, length);
fuse_reply_err(req, err);
}
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index f853150..7bcdf62 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -26,7 +26,6 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
@@ -62,7 +61,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.playlist.Playlist;
import com.android.providers.media.util.BackgroundThread;
import com.android.providers.media.util.DatabaseUtils;
@@ -106,8 +104,6 @@
*/
public static final String CURRENT_GENERATION_CLAUSE = "SELECT generation FROM local_metadata";
- static final String PREF_DB_FROM_VERSION = "db_from_version";
-
private static final int NOTIFY_BATCH_SIZE = 256;
final Context mContext;
@@ -359,8 +355,6 @@
updateDatabase(db, oldV, newV);
} finally {
mSchemaLock.writeLock().unlock();
- SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE);
- prefs.edit().putInt(PREF_DB_FROM_VERSION, oldV).commit();
}
}
@@ -1412,7 +1406,8 @@
private static void updateAddRecording(SQLiteDatabase db, boolean internal) {
db.execSQL("ALTER TABLE files ADD COLUMN is_recording INTEGER DEFAULT 0;");
- updateRecordingForSUpdate(db, internal);
+ // We add the column is_recording, rescan all music files
+ db.execSQL("UPDATE files SET date_modified=0 WHERE is_music=1;");
}
private static void updateAddRedactedUriId(SQLiteDatabase db) {
@@ -1567,7 +1562,6 @@
UserHandle.myUserId()));
}
-
private static void recomputeDataValues(SQLiteDatabase db, boolean internal) {
try (Cursor c = db.query("files", new String[] { FileColumns._ID, FileColumns.DATA },
null, null, null, null, null, null)) {
@@ -1619,13 +1613,6 @@
}
}
- static void updateRecordingForSUpdate(SQLiteDatabase db, boolean internal) {
- if (SdkLevel.isAtLeastS()) {
- // Rescan all music files to update is_recording type
- db.execSQL("UPDATE files SET date_modified=0 WHERE is_music=1;");
- }
- }
-
static final int VERSION_J = 509;
static final int VERSION_K = 700;
static final int VERSION_L = 700;
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index d19627c..60b4e0e 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -63,6 +63,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.util.LongArray;
+import com.android.providers.media.util.UserCache;
import java.util.Locale;
@@ -104,7 +105,8 @@
private static final long UNKNOWN_ROW_ID = -1;
- public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider) {
+ public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider,
+ UserCache userCache) {
String callingPackage = provider.getCallingPackageUnchecked();
int binderUid = Binder.getCallingUid();
if (callingPackage == null) {
@@ -128,6 +130,12 @@
} else {
user = UserHandle.getUserHandleForUid(binderUid);
}
+ if (!userCache.userSharesMediaWithParent(user)) {
+ // It's possible that we got a cross-profile intent from a regular work profile; in
+ // that case, the request was explicitly targeted at the media database of the owner
+ // user; reflect that here.
+ user = Process.myUserHandle();
+ }
return new LocalCallingIdentity(context, Binder.getCallingPid(), binderUid,
user, callingPackage, callingAttributionTag);
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 69efcae..e76bde9 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -194,7 +194,7 @@
import com.android.providers.media.DatabaseHelper.OnLegacyMigrationListener;
import com.android.providers.media.fuse.ExternalStorageServiceImpl;
import com.android.providers.media.fuse.FuseDaemon;
-import com.android.providers.media.metrics.StatsdPuller;
+import com.android.providers.media.metrics.PulledMetrics;
import com.android.providers.media.playlist.Playlist;
import com.android.providers.media.scan.MediaScanner;
import com.android.providers.media.scan.ModernMediaScanner;
@@ -210,6 +210,7 @@
import com.android.providers.media.util.MimeUtils;
import com.android.providers.media.util.PermissionUtils;
import com.android.providers.media.util.SQLiteQueryBuilder;
+import com.android.providers.media.util.UserCache;
import com.android.providers.media.util.XmpInterface;
import com.google.common.hash.Hashing;
@@ -416,9 +417,21 @@
return mVolumeCache.getVolumeId(file);
}
- public @NonNull Collection<File> getVolumeScanPaths(String volumeName)
+ private @NonNull Collection<File> getAllowedVolumePaths(String volumeName)
throws FileNotFoundException {
- return mVolumeCache.getVolumeScanPaths(volumeName, mCallingIdentity.get().getUser());
+ // This method is used to verify whether a path belongs to a certain volume name;
+ // we can't always use the calling user's identity here to determine exactly which
+ // volume is meant, because the MediaScanner may scan paths belonging to another user,
+ // eg a clone user.
+ // So, for volumes like external_primary, just return allowed paths for all users.
+ List<UserHandle> users = mUserCache.getUsersCached();
+ ArrayList<File> allowedPaths = new ArrayList<>();
+ for (UserHandle user : users) {
+ Collection<File> volumeScanPaths = mVolumeCache.getVolumeScanPaths(volumeName, user);
+ allowedPaths.addAll(volumeScanPaths);
+ }
+
+ return allowedPaths;
}
/**
@@ -442,6 +455,7 @@
private DevicePolicyManager mDevicePolicyManager;
private UserManager mUserManager;
+ private UserCache mUserCache;
private VolumeCache mVolumeCache;
private int mExternalStorageAuthorityAppId;
@@ -519,7 +533,7 @@
final LocalCallingIdentity cached = mCachedCallingIdentity
.get(Binder.getCallingUid());
return (cached != null) ? cached
- : LocalCallingIdentity.fromBinder(getContext(), this);
+ : LocalCallingIdentity.fromBinder(getContext(), this, mUserCache);
}
});
@@ -896,6 +910,8 @@
public boolean onCreate() {
final Context context = getContext();
+ mUserCache = new UserCache(context);
+
// Shift call statistics back to the original caller
Binder.setProxyTransactListener(mTransactListener);
@@ -904,7 +920,7 @@
mPackageManager = context.getPackageManager();
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mUserManager = context.getSystemService(UserManager.class);
- mVolumeCache = new VolumeCache(context);
+ mVolumeCache = new VolumeCache(context, mUserCache);
// Reasonable thumbnail size is half of the smallest screen edge width
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
@@ -994,7 +1010,7 @@
mExternalStorageAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
}
- StatsdPuller.initialize(context);
+ PulledMetrics.initialize(context);
return true;
}
@@ -1296,28 +1312,14 @@
UserHandle user1 = UserHandle.of(userId1);
UserHandle user2 = UserHandle.of(userId2);
- // Need to create system package context here as the clone profile user doesn't run
- // a MediaProvider instance of its own, and hence we can't use
- // createContextAsUser which uses the current MediaProvider module package.
- final Context userContext1 = getContext().createPackageContextAsUser("system", 0,
- user1);
- boolean isMediaSharedWithParent1 = userContext1.getSystemService(
- UserManager.class).isMediaSharedWithParent();
- final Context userContext2 = getContext().createPackageContextAsUser("system", 0,
- user2);
- boolean isMediaSharedWithParent2 = userContext2.getSystemService(
- UserManager.class).isMediaSharedWithParent();
-
- // Clone profiles share media with the parent user
- if (SdkLevel.isAtLeastS() && (isMediaSharedWithParent1
- || isMediaSharedWithParent2)) {
- return mUserManager.isSameProfileGroup(user1, user2);
+ if (SdkLevel.isAtLeastS() && (mUserCache.userSharesMediaWithParent(user1)
+ || mUserCache.userSharesMediaWithParent(user2))) {
+ return true;
}
Method isAppCloneUserPair = StorageManager.class.getMethod("isAppCloneUserPair",
int.class, int.class);
return (Boolean) isAppCloneUserPair.invoke(mStorageManager, userId1, userId2);
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
- NameNotFoundException e) {
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Log.w(TAG, "isAppCloneUserPair failed. Users: " + userId1 + " and " + userId2);
return false;
}
@@ -1924,6 +1926,7 @@
public String[] getFilesInDirectoryForFuse(String path, int uid) {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
try {
if (isPrivatePackagePathNotAccessibleByCaller(path)) {
@@ -2516,6 +2519,7 @@
final String errorMessage = "Rename " + oldPath + " to " + newPath + " failed. ";
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), oldPath);
try {
if (isPrivatePackagePathNotAccessibleByCaller(oldPath)
@@ -2699,6 +2703,8 @@
private Cursor queryInternal(Uri uri, String[] projection, Bundle queryArgs,
CancellationSignal signal, boolean forSelf) throws FallbackException {
+ final String volumeName = getVolumeName(uri);
+ PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), volumeName);
queryArgs = (queryArgs != null) ? queryArgs : new Bundle();
// INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
@@ -2718,7 +2724,6 @@
uri = safeUncanonicalize(uri);
- final String volumeName = getVolumeName(uri);
final int targetSdkVersion = getCallingPackageTargetSdkVersion();
final boolean allowHidden = isCallingPackageAllowedHidden();
final int table = matchUri(uri, allowHidden);
@@ -3054,6 +3059,7 @@
Environment.DIRECTORY_MUSIC,
Environment.DIRECTORY_NOTIFICATIONS,
Environment.DIRECTORY_PODCASTS,
+ FileUtils.DIRECTORY_RECORDINGS,
Environment.DIRECTORY_RINGTONES);
}
break;
@@ -3202,6 +3208,7 @@
mimeType = values.getAsString(MediaColumns.MIME_TYPE);
// Quick check MIME type against table
if (mimeType != null) {
+ PulledMetrics.logMimeTypeAccess(getCallingUidOrSelf(), mimeType);
final int actualMediaType = MimeUtils.resolveMediaType(mimeType);
if (defaultMediaType == FileColumns.MEDIA_TYPE_NONE) {
// Give callers an opportunity to work with playlists and
@@ -3485,7 +3492,7 @@
final String volumeName = resolveVolumeName(uri);
try {
// Quick check that the requested path actually lives on volume
- final Collection<File> allowed = getVolumeScanPaths(volumeName);
+ final Collection<File> allowed = getAllowedVolumePaths(volumeName);
final File actual = new File(values.getAsString(MediaColumns.DATA))
.getCanonicalFile();
if (!FileUtils.contains(allowed, actual)) {
@@ -4061,6 +4068,9 @@
private @Nullable Uri insertInternal(@NonNull Uri uri, @Nullable ContentValues initialValues,
@Nullable Bundle extras) throws FallbackException {
+ final String originalVolumeName = getVolumeName(uri);
+ PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), originalVolumeName);
+
extras = (extras != null) ? extras : new Bundle();
// REDACTED_URI_BUNDLE_KEY extra should only be set inside MediaProvider.
extras.remove(QUERY_ARG_REDACTED_URI);
@@ -4072,7 +4082,6 @@
final int match = matchUri(uri, allowHidden);
final int targetSdkVersion = getCallingPackageTargetSdkVersion();
- final String originalVolumeName = getVolumeName(uri);
final String resolvedVolumeName = resolveVolumeName(uri);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
@@ -5155,6 +5164,9 @@
private int deleteInternal(@NonNull Uri uri, @Nullable Bundle extras)
throws FallbackException {
+ final String volumeName = getVolumeName(uri);
+ PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), volumeName);
+
extras = (extras != null) ? extras : new Bundle();
// REDACTED_URI_BUNDLE_KEY extra should only be set inside MediaProvider.
extras.remove(QUERY_ARG_REDACTED_URI);
@@ -5199,7 +5211,6 @@
int count = 0;
- final String volumeName = getVolumeName(uri);
final int targetSdkVersion = getCallingPackageTargetSdkVersion();
// handle MEDIA_SCANNER before calling getDatabaseForUri()
@@ -5280,6 +5291,8 @@
final int isDownload = c.getInt(3);
final String mimeType = c.getString(4);
+ // TODO(b/188782594) Consider logging mime type access on delete too.
+
// Forget that caller is owner of this item
mCallingIdentity.get().setOwned(id, false);
@@ -6150,6 +6163,9 @@
private int updateInternal(@NonNull Uri uri, @Nullable ContentValues initialValues,
@Nullable Bundle extras) throws FallbackException {
+ final String volumeName = getVolumeName(uri);
+ PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), volumeName);
+
extras = (extras != null) ? extras : new Bundle();
// REDACTED_URI_BUNDLE_KEY extra should only be set inside MediaProvider.
extras.remove(QUERY_ARG_REDACTED_URI);
@@ -6185,7 +6201,6 @@
int count;
- final String volumeName = getVolumeName(uri);
final int targetSdkVersion = getCallingPackageTargetSdkVersion();
final boolean allowHidden = isCallingPackageAllowedHidden();
final int match = matchUri(uri, allowHidden);
@@ -7071,31 +7086,37 @@
final String volumeName = getVolumeName(uri);
// Handle some legacy cases where we need to redirect thumbnails
- switch (match) {
- case AUDIO_ALBUMART_ID: {
- final long albumId = Long.parseLong(uri.getPathSegments().get(3));
- final Uri targetUri = ContentUris
- .withAppendedId(Audio.Albums.getContentUri(volumeName), albumId);
- return ensureThumbnail(targetUri, signal);
+ try {
+ switch (match) {
+ case AUDIO_ALBUMART_ID: {
+ final long albumId = Long.parseLong(uri.getPathSegments().get(3));
+ final Uri targetUri = ContentUris
+ .withAppendedId(Audio.Albums.getContentUri(volumeName), albumId);
+ return ensureThumbnail(targetUri, signal);
+ }
+ case AUDIO_ALBUMART_FILE_ID: {
+ final long audioId = Long.parseLong(uri.getPathSegments().get(3));
+ final Uri targetUri = ContentUris
+ .withAppendedId(Audio.Media.getContentUri(volumeName), audioId);
+ return ensureThumbnail(targetUri, signal);
+ }
+ case VIDEO_MEDIA_ID_THUMBNAIL: {
+ final long videoId = Long.parseLong(uri.getPathSegments().get(3));
+ final Uri targetUri = ContentUris
+ .withAppendedId(Video.Media.getContentUri(volumeName), videoId);
+ return ensureThumbnail(targetUri, signal);
+ }
+ case IMAGES_MEDIA_ID_THUMBNAIL: {
+ final long imageId = Long.parseLong(uri.getPathSegments().get(3));
+ final Uri targetUri = ContentUris
+ .withAppendedId(Images.Media.getContentUri(volumeName), imageId);
+ return ensureThumbnail(targetUri, signal);
+ }
}
- case AUDIO_ALBUMART_FILE_ID: {
- final long audioId = Long.parseLong(uri.getPathSegments().get(3));
- final Uri targetUri = ContentUris
- .withAppendedId(Audio.Media.getContentUri(volumeName), audioId);
- return ensureThumbnail(targetUri, signal);
- }
- case VIDEO_MEDIA_ID_THUMBNAIL: {
- final long videoId = Long.parseLong(uri.getPathSegments().get(3));
- final Uri targetUri = ContentUris
- .withAppendedId(Video.Media.getContentUri(volumeName), videoId);
- return ensureThumbnail(targetUri, signal);
- }
- case IMAGES_MEDIA_ID_THUMBNAIL: {
- final long imageId = Long.parseLong(uri.getPathSegments().get(3));
- final Uri targetUri = ContentUris
- .withAppendedId(Images.Media.getContentUri(volumeName), imageId);
- return ensureThumbnail(targetUri, signal);
- }
+ } finally {
+ // We have to log separately here because openFileAndEnforcePathPermissionsHelper calls
+ // a public MediaProvider API and so logs the access there.
+ PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), volumeName);
}
return openFileAndEnforcePathPermissionsHelper(uri, match, mode, signal, opts);
@@ -8015,6 +8036,9 @@
int transformsReason, boolean forWrite, boolean redact, boolean logTransformsMetrics) {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
+
boolean isSuccess = false;
final int originalUid = getBinderUidForFuse(uid, tid);
@@ -8351,6 +8375,7 @@
public int insertFileIfNecessaryForFuse(@NonNull String path, int uid) {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
try {
if (isPrivatePackagePathNotAccessibleByCaller(path)) {
@@ -8476,6 +8501,8 @@
public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
+
try {
if (isPrivatePackagePathNotAccessibleByCaller(path)) {
Log.e(TAG, "Can't delete a file in another app's external directory!");
@@ -8538,6 +8565,7 @@
@NonNull String path, int uid, boolean forCreate) {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
try {
// App dirs are not indexed, so we don't create an entry for the file.
@@ -8591,6 +8619,7 @@
public int isOpendirAllowedForFuse(@NonNull String path, int uid, boolean forWrite) {
final LocalCallingIdentity token =
clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ PulledMetrics.logFileAccessViaFuse(getCallingUidOrSelf(), path);
try {
if ("/storage/emulated".equals(path)) {
return OsConstants.EPERM;
@@ -9213,8 +9242,8 @@
final String volumeName = resolveVolumeName(uri);
synchronized (mAttachedVolumes) {
boolean volumeAttached = false;
+ UserHandle user = mCallingIdentity.get().getUser();
for (MediaVolume vol : mAttachedVolumes) {
- UserHandle user = mCallingIdentity.get().getUser();
if (vol.getName().equals(volumeName) && vol.isVisibleToUser(user)) {
volumeAttached = true;
break;
@@ -9632,6 +9661,10 @@
return mCallingIdentity.get().hasPermission(PERMISSION_IS_SYSTEM_GALLERY);
}
+ private int getCallingUidOrSelf() {
+ return mCallingIdentity.get().uid;
+ }
+
@Deprecated
private String getCallingPackageOrSelf() {
return mCallingIdentity.get().getPackageName();
diff --git a/src/com/android/providers/media/MediaService.java b/src/com/android/providers/media/MediaService.java
index 5e3e10f..bc7330a 100644
--- a/src/com/android/providers/media/MediaService.java
+++ b/src/com/android/providers/media/MediaService.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
+import android.os.UserHandle;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
import android.util.Log;
@@ -43,6 +44,21 @@
public class MediaService extends JobIntentService {
private static final int JOB_ID = -300;
+ private static final String ACTION_SCAN_VOLUME
+ = "com.android.providers.media.action.SCAN_VOLUME";
+
+ private static final String EXTRA_MEDIAVOLUME = "MediaVolume";
+
+ private static final String EXTRA_SCAN_REASON = "scan_reason";
+
+
+ public static void queueVolumeScan(Context context, MediaVolume volume, int reason) {
+ Intent intent = new Intent(ACTION_SCAN_VOLUME);
+ intent.putExtra(EXTRA_MEDIAVOLUME, volume) ;
+ intent.putExtra(EXTRA_SCAN_REASON, reason);
+ enqueueWork(context, intent);
+ }
+
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, MediaService.class, JOB_ID, work);
}
@@ -69,8 +85,10 @@
onScanFile(this, intent.getData());
break;
}
- case Intent.ACTION_MEDIA_MOUNTED: {
- onScanVolume(this, intent, REASON_MOUNTED);
+ case ACTION_SCAN_VOLUME: {
+ final MediaVolume volume = intent.getParcelableExtra(EXTRA_MEDIAVOLUME);
+ int reason = intent.getIntExtra(EXTRA_SCAN_REASON, REASON_DEMAND);
+ onScanVolume(this, volume, reason);
break;
}
default: {
@@ -116,6 +134,11 @@
public static void onScanVolume(Context context, MediaVolume volume, int reason)
throws IOException {
final String volumeName = volume.getName();
+ UserHandle owner = volume.getUser();
+ if (owner == null) {
+ // Can happen for the internal volume
+ owner = context.getUser();
+ }
// If we're about to scan any external storage, scan internal first
// to ensure that we have ringtones ready to roll before a possibly very
// long external storage scan
@@ -129,7 +152,7 @@
// in the situation where a volume is ejected mid-scan
final Uri broadcastUri;
if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
- broadcastUri = Uri.fromFile(FileUtils.getVolumePath(context, volumeName));
+ broadcastUri = Uri.fromFile(volume.getPath());
} else {
broadcastUri = null;
}
@@ -146,20 +169,24 @@
Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values);
if (broadcastUri != null) {
- context.sendBroadcast(
- new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, broadcastUri));
+ context.sendBroadcastAsUser(
+ new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, broadcastUri), owner);
}
- for (File dir : FileUtils.getVolumeScanPaths(context, volumeName)) {
- provider.scanDirectory(dir, reason);
+ if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+ for (File dir : FileUtils.getVolumeScanPaths(context, volumeName)) {
+ provider.scanDirectory(dir, reason);
+ }
+ } else {
+ provider.scanDirectory(volume.getPath(), reason);
}
resolver.delete(scanUri, null, null);
} finally {
if (broadcastUri != null) {
- context.sendBroadcast(
- new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, broadcastUri));
+ context.sendBroadcastAsUser(
+ new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, broadcastUri), owner);
}
}
}
diff --git a/src/com/android/providers/media/MediaUpgradeReceiver.java b/src/com/android/providers/media/MediaUpgradeReceiver.java
index 320e1c2..abb326a 100644
--- a/src/com/android/providers/media/MediaUpgradeReceiver.java
+++ b/src/com/android/providers/media/MediaUpgradeReceiver.java
@@ -24,7 +24,6 @@
import android.util.Log;
import com.android.providers.media.util.ForegroundThread;
-import com.android.providers.media.util.Logging;
import com.android.providers.media.util.Metrics;
import java.io.File;
@@ -43,8 +42,6 @@
static final String TAG = "MediaUpgradeReceiver";
static final String PREF_DB_VERSION = "db_version";
- private static final int UPDATE_RECORDING_VERSION = 1205;
-
@Override
public void onReceive(Context context, Intent intent) {
// We are now running with the system up, but no apps started,
@@ -67,10 +64,6 @@
prefs.edit().putInt(PREF_DB_VERSION, dbVersion).commit();
try {
- // Lookup the last from_version when upgrade database
- prefs = context.getSharedPreferences(Logging.TAG, Context.MODE_PRIVATE);
- final int fromVersion = prefs.getInt(DatabaseHelper.PREF_DB_FROM_VERSION, 0);
-
File dbDir = context.getDatabasePath("foo").getParentFile();
String[] files = dbDir.list();
if (files == null) return;
@@ -86,13 +79,7 @@
MediaProvider.MIGRATION_LISTENER, null);
helper.runWithTransaction((db) -> {
// Perform just enough to force database upgrade
- final int version = db.getVersion();
- // update the is_recording column for R OS upgraded to S OS
- if (fromVersion >= UPDATE_RECORDING_VERSION
- && prefVersion < DatabaseHelper.VERSION_S) {
- helper.updateRecordingForSUpdate(db, helper.mInternal);
- }
- return version;
+ return db.getVersion();
});
helper.close();
} catch (Throwable t) {
diff --git a/src/com/android/providers/media/MediaVolume.java b/src/com/android/providers/media/MediaVolume.java
index 577a040..461f67d 100644
--- a/src/com/android/providers/media/MediaVolume.java
+++ b/src/com/android/providers/media/MediaVolume.java
@@ -16,6 +16,8 @@
package com.android.providers.media;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
@@ -37,7 +39,7 @@
* In addition to that, we keep the path and ID of the volume cached in here as well
* for easy access.
*/
-public final class MediaVolume {
+public final class MediaVolume implements Parcelable {
/**
* Name of the volume.
*/
@@ -81,6 +83,13 @@
this.mId = id;
}
+ private MediaVolume (Parcel in) {
+ this.mName = in.readString();
+ this.mUser = in.readParcelable(null);
+ this.mPath = new File(in.readString());
+ this.mId = in.readString();
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
@@ -115,4 +124,30 @@
return new MediaVolume(name, null, null, null);
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeParcelable(mUser, flags);
+ dest.writeString(mPath.toString());
+ dest.writeString(mId);
+ }
+
+ public static final @android.annotation.NonNull Creator<MediaVolume> CREATOR
+ = new Creator<MediaVolume>() {
+ @Override
+ public MediaVolume createFromParcel(Parcel in) {
+ return new MediaVolume(in);
+ }
+
+ @Override
+ public MediaVolume[] newArray(int size) {
+ return new MediaVolume[size];
+ }
+ };
}
diff --git a/src/com/android/providers/media/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index e1ffbca..9a74ddc 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -437,8 +437,8 @@
CountDownLatch latch = null;
long startTime = SystemClock.elapsedRealtime();
boolean result = false;
- int errorCode = TranscodingSession.ERROR_NONE;
- int failureReason = TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN;
+ int errorCode = TranscodingSession.ERROR_SERVICE_DIED;
+ int failureReason = TRANSCODING_DATA__FAILURE_CAUSE__TRANSCODING_SERVICE_ERROR;
try {
synchronized (mLock) {
@@ -481,6 +481,16 @@
transcodingSession.cancel();
}
} finally {
+ if (storageSession == null) {
+ Log.w(TAG, "Failed to create a StorageTranscodingSession");
+ // We were unable to even queue the request. Which means the media service is
+ // in a very bad state
+ reportTranscodingResult(uid, result, errorCode, failureReason,
+ SystemClock.elapsedRealtime() - startTime, reason,
+ src, dst, false /* hasAnr */);
+ return false;
+ }
+
storageSession.notifyFinished(failureReason, errorCode);
if (errorCode == TranscodingSession.ERROR_DROPPED_BY_SERVICE) {
// If the transcoding service drops a request for a uid the uid will be denied
diff --git a/src/com/android/providers/media/VolumeCache.java b/src/com/android/providers/media/VolumeCache.java
index d94ddfa..cbcd230 100644
--- a/src/com/android/providers/media/VolumeCache.java
+++ b/src/com/android/providers/media/VolumeCache.java
@@ -19,7 +19,6 @@
import static com.android.providers.media.util.Logging.TAG;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
@@ -35,6 +34,7 @@
import androidx.annotation.NonNull;
import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.UserCache;
import java.io.File;
import java.io.FileNotFoundException;
@@ -54,6 +54,7 @@
private final Object mLock = new Object();
private final UserManager mUserManager;
+ private final UserCache mUserCache;
@GuardedBy("mLock")
private final ArrayList<MediaVolume> mExternalVolumes = new ArrayList<>();
@@ -64,12 +65,10 @@
@GuardedBy("mLock")
private Collection<File> mCachedInternalScanPaths;
- @GuardedBy("mLock")
- private final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
-
- public VolumeCache(Context context) {
+ public VolumeCache(Context context, UserCache userCache) {
mContext = context;
mUserManager = context.getSystemService(UserManager.class);
+ mUserCache = userCache;
}
public @NonNull List<MediaVolume> getExternalVolumes() {
@@ -112,7 +111,7 @@
// Try again by using FileUtils below
}
- final Context userContext = getContextForUser(user);
+ final Context userContext = mUserCache.getContextForUser(user);
return FileUtils.getVolumePath(userContext, volumeName);
}
}
@@ -134,7 +133,7 @@
}
// Nothing found above; let's ask directly
- final Context userContext = getContextForUser(user);
+ final Context userContext = mUserCache.getContextForUser(user);
final Collection<File> res = FileUtils.getVolumeScanPaths(userContext, volumeName);
return res;
@@ -167,22 +166,6 @@
return volume.getId();
}
- private @NonNull Context getContextForUser(UserHandle user) {
- synchronized (mLock) {
- Context userContext = mUserContexts.get(user.getIdentifier());
- if (userContext != null) {
- return userContext;
- }
- try {
- userContext = mContext.createPackageContextAsUser("system", 0, user);
- mUserContexts.put(user.getIdentifier(), userContext);
- return userContext;
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException("Failed to create context for user " + user, e);
- }
- }
- }
-
@GuardedBy("mLock")
private void updateExternalVolumesForUserLocked(Context userContext) {
final StorageManager sm = userContext.getSystemService(StorageManager.class);
@@ -210,17 +193,10 @@
Log.wtf(TAG, "Failed to update volume " + MediaStore.VOLUME_INTERNAL,e );
}
mExternalVolumes.clear();
- for (UserHandle profile : mUserManager.getEnabledProfiles()) {
- if (profile.equals(mContext.getUser())) {
- // Volumes of the user id that MediaProvider runs as
- updateExternalVolumesForUserLocked(mContext);
- } else {
- Context userContext = getContextForUser(profile);
- if (userContext.getSystemService(UserManager.class).isMediaSharedWithParent()) {
- // This profile shares media with its parent - add its volumes, too
- updateExternalVolumesForUserLocked(userContext);
- }
- }
+ List<UserHandle> users = mUserCache.updateAndGetUsers();
+ for (UserHandle user : users) {
+ Context userContext = mUserCache.getContextForUser(user);
+ updateExternalVolumesForUserLocked(userContext);
}
}
}
diff --git a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
index 612889d..1f8f3cd 100644
--- a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
+++ b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.providers.media.fuse;
+import static com.android.providers.media.scan.MediaScanner.REASON_MOUNTED;
+
import android.annotation.BytesLong;
import android.content.ContentProviderClient;
import android.os.Environment;
@@ -30,6 +32,7 @@
import androidx.annotation.Nullable;
import com.android.providers.media.MediaProvider;
+import com.android.providers.media.MediaService;
import com.android.providers.media.MediaVolume;
import java.io.File;
@@ -83,8 +86,9 @@
switch(vol.getState()) {
case Environment.MEDIA_MOUNTED:
- mediaProvider.attachVolume(MediaVolume.fromStorageVolume(vol),
- /* validate */ false);
+ MediaVolume volume = MediaVolume.fromStorageVolume(vol);
+ mediaProvider.attachVolume(volume, /* validate */ false);
+ MediaService.queueVolumeScan(mediaProvider.getContext(), volume, REASON_MOUNTED);
break;
case Environment.MEDIA_UNMOUNTED:
case Environment.MEDIA_EJECTING:
diff --git a/src/com/android/providers/media/metrics/PulledMetrics.java b/src/com/android/providers/media/metrics/PulledMetrics.java
new file mode 100644
index 0000000..f9c5fac
--- /dev/null
+++ b/src/com/android/providers/media/metrics/PulledMetrics.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.metrics;
+
+import static com.android.providers.media.MediaProviderStatsLog.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS;
+import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import androidx.annotation.NonNull;
+
+import com.android.providers.media.fuse.FuseDaemon;
+import com.android.providers.media.util.BackgroundThread;
+
+import java.util.List;
+
+/** A class to initialise and log metrics pulled by statsd. */
+public class PulledMetrics {
+ private static final String TAG = "PulledMetrics";
+
+ private static final StatsPullCallbackHandler STATS_PULL_CALLBACK_HANDLER =
+ new StatsPullCallbackHandler();
+
+ private static final StorageAccessMetrics storageAccessMetrics = new StorageAccessMetrics();
+
+ private static boolean isInitialized = false;
+
+ public static void initialize(Context context) {
+ if (isInitialized) {
+ return;
+ }
+
+ final StatsManager statsManager = context.getSystemService(StatsManager.class);
+ if (statsManager == null) {
+ Log.e(TAG, "Error retrieving StatsManager. Cannot initialize PulledMetrics.");
+ } else {
+ Log.d(TAG, "Registering callback with StatsManager");
+
+ try {
+ // use the same callback handler for registering for all the tags.
+ statsManager.setPullAtomCallback(TRANSCODING_DATA, null /* metadata */,
+ BackgroundThread.getExecutor(),
+ STATS_PULL_CALLBACK_HANDLER);
+ statsManager.setPullAtomCallback(
+ GENERAL_EXTERNAL_STORAGE_ACCESS_STATS,
+ /*metadata*/null,
+ BackgroundThread.getExecutor(),
+ STATS_PULL_CALLBACK_HANDLER);
+ isInitialized = true;
+ } catch (NullPointerException e) {
+ Log.w(TAG, "Pulled metrics not supported. Could not register.", e);
+ }
+ }
+ }
+
+ // Storage Access Metrics log functions
+
+ /**
+ * Logs the mime type that was accessed by the given {@code uid}.
+ * Does nothing if the stats puller is not initialized.
+ */
+ public static void logMimeTypeAccess(int uid, @NonNull String mimeType) {
+ if (!isInitialized) {
+ return;
+ }
+
+ storageAccessMetrics.logMimeType(uid, mimeType);
+ }
+
+ /**
+ * Logs the storage access and attributes it to the given {@code uid}.
+ *
+ * <p>Should only be called from a FUSE thread.
+ */
+ public static void logFileAccessViaFuse(int uid, @NonNull String file) {
+ if (!isInitialized) {
+ return;
+ }
+
+ storageAccessMetrics.logAccessViaFuse(uid, file);
+ }
+
+ /**
+ * Logs the storage access and attributes it to the given {@code uid}.
+ *
+ * <p>This is a no-op if it's called on a FUSE thread.
+ */
+ public static void logVolumeAccessViaMediaProvider(int uid, @NonNull String volumeName) {
+ if (!isInitialized) {
+ return;
+ }
+
+ // We don't log if it's a FUSE thread because logAccessViaFuse should handle that.
+ if (FuseDaemon.native_is_fuse_thread()) {
+ return;
+ }
+ storageAccessMetrics.logAccessViaMediaProvider(uid, volumeName);
+ }
+
+ private static class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ // handle the tags appropriately.
+ List<StatsEvent> events = pullEvents(atomTag);
+ if (events == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ data.addAll(events);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private List<StatsEvent> pullEvents(int atomTag) {
+ switch (atomTag) {
+ case TRANSCODING_DATA:
+ return TranscodeMetrics.pullStatsEvents();
+ case GENERAL_EXTERNAL_STORAGE_ACCESS_STATS:
+ return storageAccessMetrics.pullStatsEvents();
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/com/android/providers/media/metrics/StatsdPuller.java b/src/com/android/providers/media/metrics/StatsdPuller.java
deleted file mode 100644
index 224ff34..0000000
--- a/src/com/android/providers/media/metrics/StatsdPuller.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.providers.media.metrics;
-
-import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA;
-
-import android.app.StatsManager;
-import android.content.Context;
-import android.util.Log;
-import android.util.StatsEvent;
-
-import com.android.providers.media.util.BackgroundThread;
-
-import java.util.List;
-
-/**
- * A "static" class providing the boilerplate for handling the pulling of metrics by statsd.
- * All individuals using this should accumulate their metrics per their policies.
- */
-public class StatsdPuller {
- private static final String TAG = "StatsdPuller";
-
- private static final StatsPullCallbackHandler STATS_PULL_CALLBACK_HANDLER =
- new StatsPullCallbackHandler();
-
- private static boolean isInitialized = false;
-
- public static void initialize(Context context) {
- if (isInitialized) {
- return;
- }
-
- final StatsManager statsManager = context.getSystemService(StatsManager.class);
- if (statsManager == null) {
- Log.e(TAG, "Error retrieving StatsManager. Cannot initialize StatsdPuller.");
- } else {
- Log.i(TAG, "Registering callback with StatsManager");
-
- try {
- // use the same callback handler for registering for all the tags.
- statsManager.setPullAtomCallback(TRANSCODING_DATA, null /* metadata */,
- BackgroundThread.getExecutor(),
- STATS_PULL_CALLBACK_HANDLER);
- isInitialized = true;
- } catch (NullPointerException e) {
- Log.w(TAG, "Pulled metrics not supported. Could not register.", e);
- }
- }
- }
-
- public static boolean isInitialized() {
- return isInitialized;
- }
-
- private static class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
- @Override
- public int onPullAtom(int atomTag, List<StatsEvent> data) {
- // handle the tags appropriately.
- switch (atomTag) {
- case TRANSCODING_DATA:
- return TranscodeMetrics.handleStatsEventDataRequest(atomTag,
- data);
- default:
- throw new UnsupportedOperationException("Unknown atomTag = " + atomTag);
- }
- }
- }
-}
diff --git a/src/com/android/providers/media/metrics/StorageAccessMetrics.java b/src/com/android/providers/media/metrics/StorageAccessMetrics.java
new file mode 100644
index 0000000..ce54e3f
--- /dev/null
+++ b/src/com/android/providers/media/metrics/StorageAccessMetrics.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.metrics;
+
+import static com.android.providers.media.MediaProviderStatsLog.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS;
+
+import static java.util.stream.Collectors.toList;
+
+import android.os.Process;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.StatsEvent;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.providers.media.MediaProviderStatsLog;
+import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.MimeUtils;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Metrics for {@link MediaProviderStatsLog#GENERAL_EXTERNAL_STORAGE_ACCESS_STATS}. This class
+ * gathers stats separately for each UID that accesses external storage.
+ */
+class StorageAccessMetrics {
+
+ private static final String TAG = "StorageAccessMetrics";
+
+ @VisibleForTesting
+ static final int UID_SAMPLES_COUNT_LIMIT = 50;
+
+ private final int mMyUid = Process.myUid();
+
+ @GuardedBy("mLock")
+ private final SparseArray<PackageStorageAccessStats> mAccessStatsPerPackage =
+ new SparseArray<>();
+ @GuardedBy("mLock")
+ private long mStartTimeMillis = SystemClock.uptimeMillis();
+
+ private final Object mLock = new Object();
+
+
+ /**
+ * Logs the mime type that was accessed by the given {@code uid}.
+ */
+ void logMimeType(int uid, @NonNull String mimeType) {
+ if (mimeType == null) {
+ Log.w(TAG, "Attempted to log null mime type access");
+ return;
+ }
+
+ synchronized (mLock) {
+ getOrGeneratePackageStatsObjectLocked(uid).mMimeTypes.add(mimeType);
+ }
+ }
+
+ /**
+ * Logs the storage access and attributes it to the given {@code uid}.
+ *
+ * <p>Should only be called from a FUSE thread.
+ */
+ void logAccessViaFuse(int uid, @NonNull String file) {
+ // We don't log the access if it's MediaProvider accessing.
+ if (mMyUid == uid) {
+ return;
+ }
+
+ incrementFilePathAccesses(uid);
+ final String volumeName = MediaStore.getVolumeName(
+ FileUtils.getContentUriForPath(file));
+ logGeneralExternalStorageAccess(uid, volumeName);
+ logMimeTypeFromFile(uid, file);
+ }
+
+
+ /**
+ * Logs the storage access and attributes it to the given {@code uid}.
+ */
+ void logAccessViaMediaProvider(int uid, @NonNull String volumeName) {
+ // We also don't log the access if it's MediaProvider accessing.
+ if (mMyUid == uid) {
+ return;
+ }
+
+ logGeneralExternalStorageAccess(uid, volumeName);
+ }
+
+ /**
+ * Use this to log whenever a package accesses external storage via ContentResolver or FUSE.
+ * The given volume name helps us determine whether this was an access on primary or secondary
+ * storage.
+ */
+ private void logGeneralExternalStorageAccess(int uid, @NonNull String volumeName) {
+ switch (volumeName) {
+ case MediaStore.VOLUME_EXTERNAL:
+ case MediaStore.VOLUME_EXTERNAL_PRIMARY:
+ incrementTotalAccesses(uid);
+ break;
+ case MediaStore.VOLUME_INTERNAL:
+ case MediaStore.VOLUME_DEMO:
+ case MediaStore.MEDIA_SCANNER_VOLUME:
+ break;
+ default:
+ // Secondary external storage
+ incrementTotalAccesses(uid);
+ incrementSecondaryStorageAccesses(uid);
+ }
+ }
+
+ /**
+ * Logs that the mime type of the given {@param file} was accessed by the given {@param uid}.
+ */
+ private void logMimeTypeFromFile(int uid, @Nullable String file) {
+ logMimeType(uid, MimeUtils.resolveMimeType(new File(file)));
+ }
+
+ private void incrementTotalAccesses(int uid) {
+ synchronized (mLock) {
+ getOrGeneratePackageStatsObjectLocked(uid).mTotalAccesses += 1;
+ }
+ }
+
+ private void incrementFilePathAccesses(int uid) {
+ synchronized (mLock) {
+ getOrGeneratePackageStatsObjectLocked(uid).mFilePathAccesses += 1;
+ }
+ }
+
+ private void incrementSecondaryStorageAccesses(int uid) {
+ synchronized (mLock) {
+ getOrGeneratePackageStatsObjectLocked(uid).mSecondaryStorageAccesses += 1;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private PackageStorageAccessStats getOrGeneratePackageStatsObjectLocked(int uid) {
+ PackageStorageAccessStats stats = mAccessStatsPerPackage.get(uid);
+ if (stats == null) {
+ stats = new PackageStorageAccessStats(uid);
+ mAccessStatsPerPackage.put(uid, stats);
+ }
+ return stats;
+ }
+
+ /**
+ * Returns the list of {@link StatsEvent} since latest reset, for a random subset of tracked
+ * uids if there are more than {@link #UID_SAMPLES_COUNT_LIMIT} in total. Returns {@code null}
+ * when the time since reset is non-positive.
+ */
+ @Nullable
+ List<StatsEvent> pullStatsEvents() {
+ synchronized (mLock) {
+ final long timeInterval = SystemClock.uptimeMillis() - mStartTimeMillis;
+ List<PackageStorageAccessStats> stats = getSampleStats();
+ resetStats();
+ return stats
+ .stream()
+ .map(s -> s.toNormalizedStats(timeInterval).toStatsEvent())
+ .collect(toList());
+ }
+ }
+
+ @VisibleForTesting
+ List<PackageStorageAccessStats> getSampleStats() {
+ synchronized (mLock) {
+ List<PackageStorageAccessStats> result = new ArrayList<>();
+
+ List<Integer> sampledUids = new ArrayList<>();
+ for (int i = 0; i < mAccessStatsPerPackage.size(); i++) {
+ sampledUids.add(mAccessStatsPerPackage.keyAt(i));
+ }
+
+ if (sampledUids.size() > UID_SAMPLES_COUNT_LIMIT) {
+ Collections.shuffle(sampledUids);
+ sampledUids = sampledUids.subList(0, UID_SAMPLES_COUNT_LIMIT);
+ }
+ for (Integer uid : sampledUids) {
+ PackageStorageAccessStats stats = mAccessStatsPerPackage.get(uid);
+ result.add(stats);
+ }
+
+ return result;
+ }
+ }
+
+ private void resetStats() {
+ synchronized (mLock) {
+ mAccessStatsPerPackage.clear();
+ mStartTimeMillis = SystemClock.uptimeMillis();
+ }
+ }
+
+ @VisibleForTesting
+ static class PackageStorageAccessStats {
+ private final int mUid;
+ int mTotalAccesses = 0;
+ int mFilePathAccesses = 0;
+ int mSecondaryStorageAccesses = 0;
+
+ final ArraySet<String> mMimeTypes = new ArraySet<>();
+
+ PackageStorageAccessStats(int uid) {
+ this.mUid = uid;
+ }
+
+ PackageStorageAccessStats toNormalizedStats(long timeInterval) {
+ this.mTotalAccesses = normalizeAccessesPerDay(mTotalAccesses, timeInterval);
+ this.mFilePathAccesses = normalizeAccessesPerDay(mFilePathAccesses, timeInterval);
+ this.mSecondaryStorageAccesses =
+ normalizeAccessesPerDay(mSecondaryStorageAccesses, timeInterval);
+ return this;
+ }
+
+ StatsEvent toStatsEvent() {
+ return StatsEvent.newBuilder()
+ .setAtomId(GENERAL_EXTERNAL_STORAGE_ACCESS_STATS)
+ .writeInt(mUid)
+ .writeInt(mTotalAccesses)
+ .writeInt(mFilePathAccesses)
+ .writeInt(mSecondaryStorageAccesses)
+ .writeByteArray(getMimeTypesAsProto().getBytes())
+ .build();
+ }
+
+ private ProtoOutputStream getMimeTypesAsProto() {
+ ProtoOutputStream proto = new ProtoOutputStream();
+ for (int i = 0; i < mMimeTypes.size(); i++) {
+ String mime = mMimeTypes.valueAt(i);
+ proto.write(/*fieldId*/ProtoOutputStream.FIELD_TYPE_STRING
+ | ProtoOutputStream.FIELD_COUNT_REPEATED
+ | 1,
+ mime);
+ }
+ return proto;
+ }
+
+ private static int normalizeAccessesPerDay(int value, long interval) {
+ if (interval <= 0) {
+ return -1;
+ }
+
+ double multiplier = Double.valueOf(TimeUnit.DAYS.toMillis(1)) / interval;
+ double normalizedValue = value * multiplier;
+ return Double.valueOf(normalizedValue).intValue();
+ }
+
+ @VisibleForTesting
+ int getUid() {
+ return mUid;
+ }
+ }
+}
diff --git a/src/com/android/providers/media/metrics/TranscodeMetrics.java b/src/com/android/providers/media/metrics/TranscodeMetrics.java
index c3dcc56..ec6a685 100644
--- a/src/com/android/providers/media/metrics/TranscodeMetrics.java
+++ b/src/com/android/providers/media/metrics/TranscodeMetrics.java
@@ -31,7 +31,7 @@
/**
* Stores metrics for transcode sessions to be shared with statsd.
*/
-public final class TranscodeMetrics {
+final class TranscodeMetrics {
private static final List<TranscodingStatsData> TRANSCODING_STATS_DATA = new ArrayList<>();
// PLEASE update these if there's a change in the proto message, per the limit set in
@@ -44,29 +44,26 @@
// incoming data because of the hard limit on the size.
private static int sTotalStatsDataCount = 0;
- static int handleStatsEventDataRequest(int atomTag, List<StatsEvent> statsEvents) {
- if (TRANSCODING_DATA != atomTag) {
- return StatsManager.PULL_SKIP;
- }
-
+ static List<StatsEvent> pullStatsEvents() {
synchronized (TRANSCODING_STATS_DATA) {
if (TRANSCODING_STATS_DATA.size() > STATS_DATA_SAMPLE_LIMIT) {
doRandomSampling();
}
- fillStatsEventDataList(atomTag, statsEvents);
+ List<StatsEvent> result = getStatsEvents();
resetStatsData();
- return StatsManager.PULL_SUCCESS;
+ return result;
}
}
- private static void fillStatsEventDataList(int atomTag, List<StatsEvent> statsEvents) {
+ private static List<StatsEvent> getStatsEvents() {
synchronized (TRANSCODING_STATS_DATA) {
+ List<StatsEvent> result = new ArrayList<>();
StatsEvent event;
int dataCountToFill = Math.min(TRANSCODING_STATS_DATA.size(), STATS_DATA_SAMPLE_LIMIT);
for (int i = 0; i < dataCountToFill; ++i) {
TranscodingStatsData statsData = TRANSCODING_STATS_DATA.get(i);
- event = StatsEvent.newBuilder().setAtomId(atomTag)
+ event = StatsEvent.newBuilder().setAtomId(TRANSCODING_DATA)
.writeString(statsData.mRequestorPackage)
.writeInt(statsData.mAccessType)
.writeLong(statsData.mFileSizeBytes)
@@ -76,8 +73,9 @@
.writeLong(statsData.mFrameRate)
.writeInt(statsData.mAccessReason).build();
- statsEvents.add(event);
+ result.add(event);
}
+ return result;
}
}
@@ -97,33 +95,17 @@
}
}
- private static void resetStatsData() {
+ @VisibleForTesting
+ static void resetStatsData() {
synchronized (TRANSCODING_STATS_DATA) {
TRANSCODING_STATS_DATA.clear();
sTotalStatsDataCount = 0;
}
}
- /**
- * Saves the statsd data that'd eventually be shared in the pull callback.
- * The data is saved only if StatsdPuller is initialized.
- * Everyone should always use this method for saving the data.
- */
- public static void saveStatsData(TranscodingStatsData transcodingStatsData) {
- if (!StatsdPuller.isInitialized()) {
- // no need to accumulate data if statsd is not going to ask for it.
- return;
- }
-
- forceSaveStatsData(transcodingStatsData);
- }
-
- /**
- * {@link StatsManager} does not register callback for Android unit test. So, we have this
- * method exposed for unit-tests so that they can save data.
- */
+ /** Saves the statsd data that'd eventually be shared in the pull callback. */
@VisibleForTesting
- static void forceSaveStatsData(TranscodingStatsData transcodingStatsData) {
+ static void saveStatsData(TranscodingStatsData transcodingStatsData) {
checkAndLimitStatsDataSizeAfterAddition(transcodingStatsData);
}
@@ -171,7 +153,7 @@
}
/** This is the data to populate the proto shared to westworld. */
- public static final class TranscodingStatsData {
+ static final class TranscodingStatsData {
private final String mRequestorPackage;
private final short mAccessType;
private final long mFileSizeBytes;
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 9a38945..8927bfa 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -1226,6 +1226,8 @@
sAudioTypes.put(Environment.DIRECTORY_MUSIC, AudioColumns.IS_MUSIC);
if (SdkLevel.isAtLeastS()) {
sAudioTypes.put(Environment.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
+ } else {
+ sAudioTypes.put(FileUtils.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
}
}
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index cf7c23a..a6311bd 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -964,6 +964,12 @@
public static final Pattern PATTERN_OBB_OR_CHILD_PATH = Pattern.compile(
"(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:obb)(/?.*)");
+ /**
+ * The recordings directory. This is used for R OS. For S OS or later,
+ * we use {@link Environment#DIRECTORY_RECORDINGS} directly.
+ */
+ public static final String DIRECTORY_RECORDINGS = "Recordings";
+
@VisibleForTesting
public static final String[] DEFAULT_FOLDER_NAMES;
static {
@@ -995,6 +1001,7 @@
Environment.DIRECTORY_DCIM,
Environment.DIRECTORY_DOCUMENTS,
Environment.DIRECTORY_AUDIOBOOKS,
+ DIRECTORY_RECORDINGS,
};
}
}
@@ -1015,7 +1022,7 @@
* Regex that matches user-ids under well-known storage paths.
*/
private static final Pattern PATTERN_USER_ID = Pattern.compile(
- "(?i)^/storage/emulated/([0-9]+)/");
+ "(?i)^/storage/emulated/([0-9]+)");
private static final String CAMERA_RELATIVE_PATH =
String.format("%s/%s/", Environment.DIRECTORY_DCIM, "Camera");
diff --git a/src/com/android/providers/media/util/UserCache.java b/src/com/android/providers/media/util/UserCache.java
new file mode 100644
index 0000000..276e3f8
--- /dev/null
+++ b/src/com/android/providers/media/util/UserCache.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.LongSparseArray;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UserCache is a class that keeps track of all users that the current MediaProvider
+ * instance is responsible for. By default, it handles storage for the user it is running as,
+ * but as of Android API 31, it will also handle storage for profiles that share media
+ * with their parent - profiles for which @link{UserManager#isMediaSharedWithParent} is set.
+ *
+ * It also keeps a cache of user contexts, for improving these lookups.
+ *
+ * Note that we don't use the USER_ broadcasts for keeping this state up to date, because they
+ * aren't guaranteed to be received before the volume events for a user.
+ */
+public class UserCache {
+ final Object mLock = new Object();
+ final Context mContext;
+ final UserManager mUserManager;
+
+ @GuardedBy("mLock")
+ final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
+
+ @GuardedBy("mLock")
+ final ArrayList<UserHandle> mUsers = new ArrayList<>();
+
+ public UserCache(Context context) {
+ mContext = context;
+ mUserManager = context.getSystemService(UserManager.class);
+
+ update();
+ }
+
+ private void update() {
+ List<UserHandle> profiles = mUserManager.getEnabledProfiles();
+ synchronized (mLock) {
+ mUsers.clear();
+ // Add the user we're running as by default
+ mUsers.add(Process.myUserHandle());
+ // And find all profiles that share media with us
+ for (UserHandle profile : profiles) {
+ if (!profile.equals(mContext.getUser())) {
+ // Check if it's a profile that shares media with us
+ Context userContext = getContextForUser(profile);
+ if (userContext.getSystemService(UserManager.class).isMediaSharedWithParent()) {
+ mUsers.add(profile);
+ }
+ }
+ }
+ }
+ }
+
+ public @NonNull List<UserHandle> updateAndGetUsers() {
+ update();
+ synchronized (mLock) {
+ return (List<UserHandle>) mUsers.clone();
+ }
+ }
+
+ public @NonNull List<UserHandle> getUsersCached() {
+ synchronized (mLock) {
+ return (List<UserHandle>) mUsers.clone();
+ }
+ }
+
+ public @NonNull Context getContextForUser(@NonNull UserHandle user) {
+ Context userContext;
+ synchronized (mLock) {
+ userContext = mUserContexts.get(user.getIdentifier());
+ if (userContext != null) {
+ return userContext;
+ }
+ }
+ try {
+ userContext = mContext.createPackageContextAsUser("system", 0, user);
+ synchronized (mLock) {
+ mUserContexts.put(user.getIdentifier(), userContext);
+ }
+ return userContext;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Failed to create context for user " + user, e);
+ }
+ }
+
+ /**
+ * Returns whether the passed in user shares media with its parent (or peer).
+ * Note that the value returned here is based on cached data; it relies on
+ * other callers to keep the user cache up-to-date.
+ *
+ * @param user user to check
+ * @return whether the user shares media with its parent
+ */
+ public boolean userSharesMediaWithParent(@NonNull UserHandle user) {
+ synchronized (mLock) {
+ return mUsers.contains(user);
+ }
+ }
+}
diff --git a/tests/src/com/android/providers/media/metrics/StorageAccessMetricsTest.java b/tests/src/com/android/providers/media/metrics/StorageAccessMetricsTest.java
new file mode 100644
index 0000000..5be69ff
--- /dev/null
+++ b/tests/src/com/android/providers/media/metrics/StorageAccessMetricsTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.metrics;
+
+import static com.android.providers.media.metrics.StorageAccessMetrics.PackageStorageAccessStats;
+import static com.android.providers.media.metrics.StorageAccessMetrics.UID_SAMPLES_COUNT_LIMIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.MediaStore;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class StorageAccessMetricsTest {
+
+ private static StorageAccessMetrics storageAccessMetrics;
+
+ @Before
+ public void setUp() {
+ storageAccessMetrics = new StorageAccessMetrics();
+ }
+
+ @Test
+ public void testLogMimeType() {
+ storageAccessMetrics.logMimeType(3, "my-mime-type");
+ storageAccessMetrics.logMimeType(3, null);
+ storageAccessMetrics.logMimeType(3, "my-mime-type-2");
+ storageAccessMetrics.logMimeType(3, "my-mime-type-2");
+ storageAccessMetrics.logMimeType(3, "my-mime-type-3");
+ storageAccessMetrics.logMimeType(3, "my-mime-type-2");
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).hasSize(1);
+
+ PackageStorageAccessStats stats = statsList.get(0);
+ assertThat(stats.getUid()).isEqualTo(3);
+ assertThat(stats.mTotalAccesses).isEqualTo(0);
+ assertThat(stats.mFilePathAccesses).isEqualTo(0);
+ assertThat(stats.mSecondaryStorageAccesses).isEqualTo(0);
+ assertThat(stats.mMimeTypes.stream().toArray())
+ .asList()
+ .containsExactly("my-mime-type", "my-mime-type-2", "my-mime-type-3");
+ }
+
+ @Test
+ public void testIncrementAccessViaFuse() {
+ storageAccessMetrics.logAccessViaFuse(3, "filename.txt");
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).hasSize(1);
+
+ PackageStorageAccessStats stats = statsList.get(0);
+ assertThat(stats.getUid()).isEqualTo(3);
+ assertThat(stats.mTotalAccesses).isEqualTo(0);
+ assertThat(stats.mFilePathAccesses).isEqualTo(1);
+ assertThat(stats.mSecondaryStorageAccesses).isEqualTo(0);
+ assertThat(stats.mMimeTypes.stream().toArray())
+ .asList()
+ .containsExactly("text/plain");
+ }
+
+ @Test
+ public void testIncrementAccessViaMediaProvider_externalVolumes() {
+ storageAccessMetrics.logAccessViaMediaProvider(3, MediaStore.VOLUME_EXTERNAL);
+ storageAccessMetrics.logAccessViaMediaProvider(
+ 3, MediaStore.VOLUME_EXTERNAL_PRIMARY);
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).hasSize(1);
+
+ PackageStorageAccessStats stats = statsList.get(0);
+ assertThat(stats.getUid()).isEqualTo(3);
+ assertThat(stats.mTotalAccesses).isEqualTo(2);
+ assertThat(stats.mFilePathAccesses).isEqualTo(0);
+ assertThat(stats.mSecondaryStorageAccesses).isEqualTo(0);
+ assertThat(stats.mMimeTypes.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testIncrementAccessViaMediaProvider_ignoredVolumes() {
+ storageAccessMetrics.logAccessViaMediaProvider(3, MediaStore.VOLUME_INTERNAL);
+ storageAccessMetrics.logAccessViaMediaProvider(3, MediaStore.VOLUME_DEMO);
+ storageAccessMetrics.logAccessViaMediaProvider(3, MediaStore.MEDIA_SCANNER_VOLUME);
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).isEmpty();
+ }
+
+ @Test
+ public void testIncrementAccessViaMediaProvider_secondaryVolumes() {
+ storageAccessMetrics.logAccessViaMediaProvider(3, "my-volume");
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).hasSize(1);
+
+ PackageStorageAccessStats stats = statsList.get(0);
+ assertThat(stats.getUid()).isEqualTo(3);
+ assertThat(stats.mTotalAccesses).isEqualTo(1);
+ assertThat(stats.mFilePathAccesses).isEqualTo(0);
+ assertThat(stats.mSecondaryStorageAccesses).isEqualTo(1);
+ assertThat(stats.mMimeTypes.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testUidCountGreaterThanLimit() {
+ int i = 0;
+ for (; i < UID_SAMPLES_COUNT_LIMIT; i++) {
+ storageAccessMetrics.logAccessViaFuse(i, "myfile.txt");
+ }
+ // Add 1 more
+ storageAccessMetrics.logAccessViaFuse(i, "myfile.txt");
+ // Pull stats
+ List<PackageStorageAccessStats> statsList =
+ storageAccessMetrics.getSampleStats();
+
+ assertThat(statsList).hasSize(UID_SAMPLES_COUNT_LIMIT);
+ }
+}
diff --git a/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java b/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
index 5c55047..8b635df 100644
--- a/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
+++ b/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
@@ -16,11 +16,6 @@
package com.android.providers.media.metrics;
-import static android.app.StatsManager.PULL_SKIP;
-import static android.app.StatsManager.PULL_SUCCESS;
-
-import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA;
-
import static com.google.common.truth.Truth.assertThat;
import android.util.StatsEvent;
@@ -31,7 +26,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -52,27 +46,13 @@
@After
public void tearDown() {
// this is to reset the saved data in TranscodeMetrics.
- TranscodeMetrics.handleStatsEventDataRequest(TRANSCODING_DATA, new ArrayList<>());
- }
-
- @Test
- public void testHandleStatsEventDataRequest_skipsUnknownTag() {
- List<StatsEvent> statsEvents = new ArrayList<>();
- int retValue = TranscodeMetrics.handleStatsEventDataRequest(UNKNOWN_ATOM_TAG, statsEvents);
- assertThat(retValue).isEqualTo(PULL_SKIP);
- }
-
- @Test
- public void testHandleStatsEventDataRequest_handlesKnownTag() {
- List<StatsEvent> statsEvents = new ArrayList<>();
- int retValue = TranscodeMetrics.handleStatsEventDataRequest(TRANSCODING_DATA, statsEvents);
- assertThat(retValue).isEqualTo(PULL_SUCCESS);
+ TranscodeMetrics.pullStatsEvents();
}
@Test
public void testSaveStatsData_doesNotGoBeyondHardLimit() {
for (int i = 0; i < TranscodeMetrics.getStatsDataCountHardLimit() + 5; ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
assertThat(TranscodeMetrics.getSavedStatsDataCount()).isEqualTo(
TranscodeMetrics.getStatsDataCountHardLimit());
@@ -82,7 +62,7 @@
public void testSaveStatsData_totalStatsDataCountEqualsPassedData() {
int totalRequestsToPass = TranscodeMetrics.getStatsDataCountHardLimit() + 5;
for (int i = 0; i < totalRequestsToPass; ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
assertThat(TranscodeMetrics.getTotalStatsDataCount()).isEqualTo(totalRequestsToPass);
}
@@ -91,7 +71,7 @@
public void testSaveStatsData_savedStatsDataCountEqualsPassedData_withinHardLimit() {
int totalRequestsToPass = TranscodeMetrics.getStatsDataCountHardLimit() - 5;
for (int i = 0; i < totalRequestsToPass; ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
assertThat(TranscodeMetrics.getSavedStatsDataCount()).isEqualTo(totalRequestsToPass);
}
@@ -99,11 +79,10 @@
@Test
public void testHandleStatsEventDataRequest_resetsData() {
for (int i = 0; i < TranscodeMetrics.getStatsDataCountHardLimit(); ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
- List<StatsEvent> statsEvents = new ArrayList<>();
- TranscodeMetrics.handleStatsEventDataRequest(TRANSCODING_DATA, statsEvents);
+ List<StatsEvent> statsEvents = TranscodeMetrics.pullStatsEvents();
assertThat(TranscodeMetrics.getSavedStatsDataCount()).isEqualTo(0);
assertThat(TranscodeMetrics.getTotalStatsDataCount()).isEqualTo(0);
@@ -112,11 +91,10 @@
@Test
public void testHandleStatsEventDataRequest_fillsExactlySampleLimit_excessData() {
for (int i = 0; i < TranscodeMetrics.getStatsDataCountHardLimit(); ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
- List<StatsEvent> statsEvents = new ArrayList<>();
- TranscodeMetrics.handleStatsEventDataRequest(TRANSCODING_DATA, statsEvents);
+ List<StatsEvent> statsEvents = TranscodeMetrics.pullStatsEvents();
assertThat(statsEvents.size()).isEqualTo(TranscodeMetrics.getStatsDataSampleLimit());
}
@@ -125,11 +103,10 @@
public void testHandleStatsEventDataRequest_fillsExactlyAsSaved_dataWithinSampleLimit() {
int totalRequestsToPass = TranscodeMetrics.getStatsDataSampleLimit() - 5;
for (int i = 0; i < totalRequestsToPass; ++i) {
- TranscodeMetrics.forceSaveStatsData(EMPTY_STATS_DATA);
+ TranscodeMetrics.saveStatsData(EMPTY_STATS_DATA);
}
- List<StatsEvent> statsEvents = new ArrayList<>();
- TranscodeMetrics.handleStatsEventDataRequest(TRANSCODING_DATA, statsEvents);
+ List<StatsEvent> statsEvents = TranscodeMetrics.pullStatsEvents();
assertThat(statsEvents.size()).isEqualTo(totalRequestsToPass);
}
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 7facf05..c434bd2 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -89,14 +89,9 @@
private static final TestApp LEGACY_TEST_APP = new TestApp("LegacyTestApp",
"com.android.providers.media.testapp.legacy", 1, false,
"LegacyMediaProviderTestApp.apk");
- private static final Map<String, Integer> pidMap = new HashMap<>();
- @BeforeClass
- public static void startTestApps() throws Exception {
- startTestApp(TEST_APP_WITH_STORAGE_PERMS);
- startTestApp(TEST_APP_WITHOUT_PERMS);
- startTestApp(LEGACY_TEST_APP);
- }
+ // Permission checks are based on uid, so we can pass -1 pid and avoid starting the test apps.
+ private static final int TEST_APP_PID = -1;
@Test
public void testConstructor() {
@@ -149,25 +144,24 @@
public void testDefaultPermissionsOnTestAppWithStoragePerms() throws Exception {
String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS);
try {
- assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
- assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+ assertThat(checkPermissionSelf(getContext(), TEST_APP_PID, testAppUid)).isFalse();
+ assertThat(checkPermissionShell(getContext(), TEST_APP_PID, testAppUid)).isFalse();
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
null)).isFalse();
assertThat(
- checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+ checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
assertThat(
- checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionAccessMtp(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
assertThat(
- checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteStorage(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
- checkReadPermissions(testAppPid, testAppUid, packageName, true);
+ checkReadPermissions(TEST_APP_PID, testAppUid, packageName, true);
} finally {
dropShellPermission();
}
@@ -177,36 +171,37 @@
public void testDefaultPermissionsOnTestAppWithoutPerms() throws Exception {
String packageName = TEST_APP_WITHOUT_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS);
try {
- assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
- assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+ assertThat(checkPermissionSelf(getContext(), TEST_APP_PID, testAppUid)).isFalse();
+ assertThat(checkPermissionShell(getContext(), TEST_APP_PID, testAppUid)).isFalse();
assertThat(
- checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionManager(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
- assertThat(checkPermissionManageMedia(getContext(), testAppPid, testAppUid, packageName,
- null)).isFalse();
+ assertThat(
+ checkPermissionManageMedia(
+ getContext(), TEST_APP_PID, testAppUid, packageName, null))
+ .isFalse();
- assertThat(checkPermissionAccessMediaLocation(getContext(), testAppPid, testAppUid,
+ assertThat(checkPermissionAccessMediaLocation(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
null)).isFalse();
assertThat(
- checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+ checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName,
null)).isFalse();
assertThat(
- checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionAccessMtp(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
assertThat(
- checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteStorage(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
- checkReadPermissions(testAppPid, testAppUid, packageName, false);
+ checkReadPermissions(TEST_APP_PID, testAppUid, packageName, false);
} finally {
dropShellPermission();
}
@@ -216,35 +211,36 @@
public void testDefaultPermissionsOnLegacyTestApp() throws Exception {
String packageName = LEGACY_TEST_APP.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS);
try {
- assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
- assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+ assertThat(checkPermissionSelf(getContext(), TEST_APP_PID, testAppUid)).isFalse();
+ assertThat(checkPermissionShell(getContext(), TEST_APP_PID, testAppUid)).isFalse();
assertThat(
- checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionManager(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
- assertThat(checkPermissionManageMedia(getContext(), testAppPid, testAppUid, packageName,
- null)).isFalse();
+ assertThat(
+ checkPermissionManageMedia(
+ getContext(), TEST_APP_PID, testAppUid, packageName, null))
+ .isFalse();
- assertThat(checkPermissionAccessMediaLocation(getContext(), testAppPid, testAppUid,
+ assertThat(checkPermissionAccessMediaLocation(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isTrue();
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
null)).isTrue();
assertThat(
- checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+ checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
assertThat(
- checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionAccessMtp(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
assertThat(
- checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteStorage(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
- checkReadPermissions(testAppPid, testAppUid, packageName, true);
+ checkReadPermissions(TEST_APP_PID, testAppUid, packageName, true);
} finally {
dropShellPermission();
}
@@ -254,19 +250,18 @@
public void testManageExternalStoragePermissionsOnTestApp() throws Exception {
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
final int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- final int testAppPid = pidMap.get(packageName);
final String op = AppOpsManager.permissionToOp(MANAGE_EXTERNAL_STORAGE);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ERRORED);
- assertThat(checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+ assertThat(checkPermissionManager(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ALLOWED);
- assertThat(checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+ assertThat(checkPermissionManager(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
} finally {
dropShellPermission();
@@ -278,20 +273,23 @@
public void testManageMediaPermissionsOnTestApp() throws Exception {
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
final int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- final int testAppPid = pidMap.get(packageName);
final String op = AppOpsManager.permissionToOp(MANAGE_MEDIA);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ERRORED);
- assertThat(checkPermissionManageMedia(getContext(), testAppPid, testAppUid, packageName,
- null)).isFalse();
+ assertThat(
+ checkPermissionManageMedia(
+ getContext(), TEST_APP_PID, testAppUid, packageName, null))
+ .isFalse();
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ALLOWED);
- assertThat(checkPermissionManageMedia(getContext(), testAppPid, testAppUid, packageName,
- null)).isTrue();
+ assertThat(
+ checkPermissionManageMedia(
+ getContext(), TEST_APP_PID, testAppUid, packageName, null))
+ .isTrue();
} finally {
dropShellPermission();
}
@@ -301,23 +299,22 @@
public void testSystemGalleryPermissionsOnTestApp() throws Exception {
String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
- checkPermissionsForGallery(testAppUid, testAppPid, packageName, false);
+ checkPermissionsForGallery(testAppUid, TEST_APP_PID, packageName, false);
final String[] SYSTEM_GALERY_APPOPS =
{OPSTR_WRITE_MEDIA_IMAGES, OPSTR_WRITE_MEDIA_VIDEO};
for (String op : SYSTEM_GALERY_APPOPS) {
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ALLOWED);
}
- checkPermissionsForGallery(testAppUid, testAppPid, packageName, true);
+ checkPermissionsForGallery(testAppUid, TEST_APP_PID, packageName, true);
for (String op : SYSTEM_GALERY_APPOPS) {
modifyAppOp(testAppUid, op, AppOpsManager.MODE_ERRORED);
}
- checkPermissionsForGallery(testAppUid, testAppPid, packageName, false);
+ checkPermissionsForGallery(testAppUid, TEST_APP_PID, packageName, false);
} finally {
dropShellPermission();
}
@@ -353,22 +350,21 @@
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(
packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
assertThat(
- checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadVideo(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_VIDEO, AppOpsManager.MODE_ERRORED);
assertThat(
- checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadVideo(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_VIDEO, AppOpsManager.MODE_ALLOWED);
assertThat(
- checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadVideo(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
} finally {
dropShellPermission();
@@ -380,22 +376,21 @@
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(
packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
assertThat(
- checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
modifyAppOp(testAppUid, OPSTR_WRITE_MEDIA_AUDIO, AppOpsManager.MODE_ALLOWED);
assertThat(
- checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
modifyAppOp(testAppUid, OPSTR_WRITE_MEDIA_AUDIO, AppOpsManager.MODE_ERRORED);
assertThat(
- checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionWriteAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
} finally {
dropShellPermission();
@@ -407,22 +402,21 @@
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(
packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
assertThat(
- checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_AUDIO, AppOpsManager.MODE_ERRORED);
assertThat(
- checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_AUDIO, AppOpsManager.MODE_ALLOWED);
assertThat(
- checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadAudio(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
} finally {
dropShellPermission();
@@ -433,22 +427,21 @@
public void testReadImagesOnTestApp() throws Exception {
final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
- int testAppPid = pidMap.get(packageName);
adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
try {
assertThat(
- checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadImages(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_IMAGES, AppOpsManager.MODE_ERRORED);
assertThat(
- checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadImages(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isFalse();
modifyAppOp(testAppUid, OPSTR_READ_MEDIA_IMAGES, AppOpsManager.MODE_ALLOWED);
assertThat(
- checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+ checkPermissionReadImages(getContext(), TEST_APP_PID, testAppUid, packageName,
null)).isTrue();
} finally {
dropShellPermission();
@@ -477,23 +470,10 @@
checkAppOpRequestInstallPackagesForSharedUid(getContext(), testAppUid,
packageName, null)).isFalse();
} finally {
- // App gets killed when we try to give it OPSTR_REQUEST_INSTALL_PACKAGES. Restart the
- // app so it doesn't affect other tests.
- startTestApp(TEST_APP_WITH_STORAGE_PERMS);
dropShellPermission();
}
}
- static private void startTestApp(TestApp testApp) throws Exception {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setPackage(testApp.getPackageName());
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.putExtra(QUERY_TYPE, RUN_INFINITE_ACTIVITY);
- getContext().startActivity(intent);
- pidMap.put(testApp.getPackageName(), getPid(testApp.getPackageName()));
- }
-
static private void modifyAppOp(int uid, String op, int mode) {
getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
}
@@ -506,7 +486,9 @@
checkPermissionWriteImages(getContext(), pid, uid, packageName, null));
assertEquals(expected,
checkPermissionWriteVideo(getContext(), pid, uid, packageName, null));
- assertThat(checkPermissionWriteAudio(getContext(), pid, uid, packageName, null)).isFalse();
+ assertThat(
+ checkPermissionWriteAudio(getContext(), pid, uid, packageName, null))
+ .isFalse();
}
static private void checkReadPermissions(int pid, int uid, String packageName,