Merge "Rename module dist files" into rvc-dev
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index a61075b..1688ee3 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -148,6 +148,8 @@
public interface OnLegacyMigrationListener {
public void onStarted(ContentProviderClient client, String volumeName);
+ public void onProgress(ContentProviderClient client, String volumeName,
+ long progress, long total);
public void onFinished(ContentProviderClient client, String volumeName);
}
@@ -366,11 +368,15 @@
mSchemaLock.writeLock().lock();
try {
+ // Temporarily drop indexes to improve migration performance
+ makePristineIndexes(db);
migrateFromLegacy(db);
+ createLatestIndexes(db, mInternal);
} finally {
mSchemaLock.writeLock().unlock();
}
}
+ Log.v(TAG, "onOpen() finished for " + mName);
}
@GuardedBy("mProjectionMapCache")
@@ -759,23 +765,9 @@
+ "play_order INTEGER NOT NULL)");
}
- db.execSQL("CREATE INDEX image_id_index on thumbnails(image_id)");
- db.execSQL("CREATE INDEX video_id_index on videothumbnails(video_id)");
- db.execSQL("CREATE INDEX album_id_idx ON files(album_id)");
- db.execSQL("CREATE INDEX artist_id_idx ON files(artist_id)");
- db.execSQL("CREATE INDEX genre_id_idx ON files(genre_id)");
- db.execSQL("CREATE INDEX bucket_index on files(bucket_id,media_type,datetaken, _id)");
- db.execSQL("CREATE INDEX bucket_name on files(bucket_id,media_type,bucket_display_name)");
- db.execSQL("CREATE INDEX format_index ON files(format)");
- db.execSQL("CREATE INDEX media_type_index ON files(media_type)");
- db.execSQL("CREATE INDEX parent_index ON files(parent)");
- db.execSQL("CREATE INDEX path_index ON files(_data)");
- db.execSQL("CREATE INDEX sort_index ON files(datetaken ASC, _id ASC)");
- db.execSQL("CREATE INDEX title_idx ON files(title)");
- db.execSQL("CREATE INDEX titlekey_index ON files(title_key)");
-
createLatestViews(db, mInternal);
createLatestTriggers(db, mInternal);
+ createLatestIndexes(db, mInternal);
// Since this code is used by both the legacy and modern providers, we
// only want to migrate when we're running as the modern provider
@@ -853,10 +845,16 @@
// To avoid SQLITE_NOMEM errors, we need to periodically
// flush the current transaction and start another one
- if ((c.getPosition() % 1_000) == 0) {
+ if ((c.getPosition() % 2_000) == 0) {
db.setTransactionSuccessful();
db.endTransaction();
db.beginTransaction();
+
+ // And announce that we're actively making progress
+ final int progress = c.getPosition();
+ final int total = c.getCount();
+ Log.v(TAG, "Migrated " + progress + " of " + total + "...");
+ mMigrationListener.onProgress(client, mVolumeName, progress, total);
}
}
@@ -1036,6 +1034,36 @@
+ " BEGIN SELECT _DELETE(" + deleteArg + "); END");
}
+ private static void makePristineIndexes(SQLiteDatabase db) {
+ // drop all indexes
+ Cursor c = db.query("sqlite_master", new String[] {"name"}, "type is 'index'",
+ null, null, null, null);
+ while (c.moveToNext()) {
+ if (c.getString(0).startsWith("sqlite_")) continue;
+ db.execSQL("DROP INDEX IF EXISTS " + c.getString(0));
+ }
+ c.close();
+ }
+
+ private static void createLatestIndexes(SQLiteDatabase db, boolean internal) {
+ makePristineIndexes(db);
+
+ db.execSQL("CREATE INDEX image_id_index on thumbnails(image_id)");
+ db.execSQL("CREATE INDEX video_id_index on videothumbnails(video_id)");
+ db.execSQL("CREATE INDEX album_id_idx ON files(album_id)");
+ db.execSQL("CREATE INDEX artist_id_idx ON files(artist_id)");
+ db.execSQL("CREATE INDEX genre_id_idx ON files(genre_id)");
+ db.execSQL("CREATE INDEX bucket_index on files(bucket_id,media_type,datetaken, _id)");
+ db.execSQL("CREATE INDEX bucket_name on files(bucket_id,media_type,bucket_display_name)");
+ db.execSQL("CREATE INDEX format_index ON files(format)");
+ db.execSQL("CREATE INDEX media_type_index ON files(media_type)");
+ db.execSQL("CREATE INDEX parent_index ON files(parent)");
+ db.execSQL("CREATE INDEX path_index ON files(_data)");
+ db.execSQL("CREATE INDEX sort_index ON files(datetaken ASC, _id ASC)");
+ db.execSQL("CREATE INDEX title_idx ON files(title)");
+ db.execSQL("CREATE INDEX titlekey_index ON files(title_key)");
+ }
+
private static void updateCollationKeys(SQLiteDatabase db) {
// Delete albums and artists, then clear the modification time on songs, which
// will cause the media scanner to rescan everything, rebuilding the artist and
@@ -1425,6 +1453,12 @@
// Empty version bump to ensure triggers are recreated
}
+ // If this is the legacy database, it's not worth recomputing data
+ // values locally, since they'll be recomputed after the migration
+ if (mLegacyProvider) {
+ recomputeDataValues = false;
+ }
+
if (recomputeDataValues) {
recomputeDataValues(db, internal);
}
diff --git a/src/com/android/providers/media/MediaDocumentsProvider.java b/src/com/android/providers/media/MediaDocumentsProvider.java
index 72ec916..65fac9b 100644
--- a/src/com/android/providers/media/MediaDocumentsProvider.java
+++ b/src/com/android/providers/media/MediaDocumentsProvider.java
@@ -130,10 +130,12 @@
static final String TYPE_DOCUMENTS_BUCKET = "documents_bucket";
static final String TYPE_DOCUMENT = "document";
- private static boolean sReturnedImagesEmpty = false;
- private static boolean sReturnedVideosEmpty = false;
- private static boolean sReturnedAudioEmpty = false;
- private static boolean sReturnedDocumentsEmpty = false;
+ private static volatile boolean sMediaStoreReady = false;
+
+ private static volatile boolean sReturnedImagesEmpty = false;
+ private static volatile boolean sReturnedVideosEmpty = false;
+ private static volatile boolean sReturnedAudioEmpty = false;
+ private static volatile boolean sReturnedDocumentsEmpty = false;
private static String joinNewline(String... args) {
return TextUtils.join("\n", args);
@@ -201,6 +203,15 @@
}
/**
+ * When underlying provider is ready, we kick off a notification of roots
+ * changed so they can be refreshed.
+ */
+ static void onMediaStoreReady(Context context, String volumeName) {
+ sMediaStoreReady = true;
+ notifyRootsChanged(context);
+ }
+
+ /**
* When inserting the first item of each type, we need to trigger a roots
* refresh to clear a previously reported {@link Root#FLAG_EMPTY}.
*/
@@ -558,10 +569,15 @@
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
- includeImagesRoot(result);
- includeVideosRoot(result);
- includeAudioRoot(result);
- includeDocumentsRoot(result);
+ // Skip all roots when the underlying provider isn't ready yet so that
+ // we avoid triggering an ANR; we'll circle back to notify and refresh
+ // once it's ready
+ if (sMediaStoreReady) {
+ includeImagesRoot(result);
+ includeVideosRoot(result);
+ includeAudioRoot(result);
+ includeDocumentsRoot(result);
+ }
return result;
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 762dbf4..50ce212 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -636,6 +636,12 @@
}
@Override
+ public void onProgress(ContentProviderClient client, String volumeName,
+ long progress, long total) {
+ // TODO: notify blocked threads of progress once we can change APIs
+ }
+
+ @Override
public void onFinished(ContentProviderClient client, String volumeName) {
MediaStore.finishLegacyMigration(ContentResolver.wrap(client), volumeName);
}
@@ -847,7 +853,6 @@
AppOpsManager.OPSTR_CAMERA
}, context.getMainExecutor(), mActiveListener);
-
mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE,
null /* all packages */, mModeListener);
mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE,
@@ -2081,8 +2086,9 @@
}
private void ensureUniqueFileColumns(int match, @NonNull Uri uri, @NonNull Bundle extras,
- @NonNull ContentValues values) throws VolumeArgumentException {
- ensureFileColumns(match, uri, extras, values, true, null /* currentPath */);
+ @NonNull ContentValues values, @Nullable String currentPath)
+ throws VolumeArgumentException {
+ ensureFileColumns(match, uri, extras, values, true, currentPath);
}
private void ensureNonUniqueFileColumns(int match, @NonNull Uri uri,
@@ -2305,8 +2311,10 @@
// Require that content lives under well-defined directories to help
// keep the user's content organized
- // Start by saying unchanged paths are valid
- boolean validPath = res.getAbsolutePath().equals(currentPath);
+ // Start by saying unchanged directories are valid
+ final String currentDir = (currentPath != null)
+ ? new File(currentPath).getParent() : null;
+ boolean validPath = res.getParent().equals(currentDir);
// Next, consider allowing based on allowed primary directory
final String[] relativePath = values.getAsString(MediaColumns.RELATIVE_PATH).split("/");
@@ -2646,7 +2654,7 @@
// Make sure all file-related columns are defined
try {
- ensureUniqueFileColumns(match, uri, extras, values);
+ ensureUniqueFileColumns(match, uri, extras, values, null);
} catch (VolumeArgumentException e) {
if (getCallingPackageTargetSdkVersion() >= Build.VERSION_CODES.Q) {
throw new IllegalArgumentException(e.getMessage());
@@ -3028,7 +3036,7 @@
MediaStore.Images.Media.getContentUri(resolvedVolumeName), imageId),
extras, true);
- ensureUniqueFileColumns(match, uri, extras, initialValues);
+ ensureUniqueFileColumns(match, uri, extras, initialValues, null);
rowId = qb.insert(helper, initialValues);
if (rowId > 0) {
@@ -3050,7 +3058,7 @@
MediaStore.Video.Media.getContentUri(resolvedVolumeName), videoId),
Bundle.EMPTY, true);
- ensureUniqueFileColumns(match, uri, extras, initialValues);
+ ensureUniqueFileColumns(match, uri, extras, initialValues, null);
rowId = qb.insert(helper, initialValues);
if (rowId > 0) {
@@ -3132,7 +3140,7 @@
throw new UnsupportedOperationException("no internal album art allowed");
}
- ensureUniqueFileColumns(match, uri, extras, initialValues);
+ ensureUniqueFileColumns(match, uri, extras, initialValues, null);
rowId = qb.insert(helper, initialValues);
if (rowId > 0) {
@@ -4700,6 +4708,11 @@
// make sure metadata is updated
if (MediaColumns.IS_PENDING.equals(column)) {
triggerScan = true;
+
+ // Explicitly clear columns used to ignore no-op scans,
+ // since we need to force a scan on publish
+ initialValues.putNull(MediaColumns.DATE_MODIFIED);
+ initialValues.putNull(MediaColumns.SIZE);
}
}
@@ -4817,7 +4830,7 @@
// Now that we've confirmed an actual movement is taking place,
// ensure we have a unique destination
initialValues.remove(MediaColumns.DATA);
- ensureUniqueFileColumns(match, uri, extras, initialValues);
+ ensureUniqueFileColumns(match, uri, extras, initialValues, beforePath);
final String afterPath = initialValues.getAsString(MediaColumns.DATA);
@@ -4845,6 +4858,19 @@
if (initialValues.containsKey(FileColumns.DATA)) {
// If we're changing paths, invalidate any thumbnails
triggerInvalidate = true;
+
+ // If the new file exists, trigger a scan to adjust any metadata
+ // that might be derived from the path
+ final String data = initialValues.getAsString(FileColumns.DATA);
+ if (!TextUtils.isEmpty(data) && new File(data).exists()) {
+ triggerScan = true;
+ }
+ }
+
+ // If we're already doing this update from an internal scan, no need to
+ // kick off another no-op scan
+ if (isCallingPackageSystem()) {
+ triggerScan = false;
}
// Since the update mutation may prevent us from matching items after
@@ -6093,6 +6119,7 @@
if (useData) {
values.put(FileColumns.DATA, getAbsoluteSanitizedPath(path));
} else {
+ values.put(FileColumns.VOLUME_NAME, extractVolumeName(path));
values.put(FileColumns.RELATIVE_PATH, extractRelativePath(path));
values.put(FileColumns.DISPLAY_NAME, extractDisplayName(path));
}
@@ -6748,6 +6775,11 @@
ensureThumbnailsValid(volume, db);
return null;
});
+
+ // We just finished the database operation above, we know that
+ // it's ready to answer queries, so notify our DocumentProvider
+ // so it can answer queries without risking ANR
+ MediaDocumentsProvider.onMediaStoreReady(getContext(), volume);
});
}
return uri;
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 0696ef5..3f97464 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -182,7 +182,7 @@
protected Void doInBackground(Void... params) {
Log.d(TAG, "User allowed grant for " + uris);
Metrics.logPermissionGranted(volumeName, appInfo.uid,
- getCallingPackage(), 1);
+ getCallingPackage(), uris.size());
try {
switch (getIntent().getAction()) {
case MediaStore.CREATE_WRITE_REQUEST_CALL: {
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index a83cc13..cfc9eeb 100644
--- a/tests/src/com/android/providers/media/DatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/DatabaseHelperTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -39,7 +40,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
+import com.android.providers.media.scan.MediaScannerTest.IsolatedContext;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,22 +60,19 @@
private static final String SQLITE_MASTER_ORDER_BY = "type,name,tbl_name";
- private Context getContext() {
- return InstrumentationRegistry.getTargetContext();
- }
+ private static Context sIsolatedContext;
+ private static ContentResolver sIsolatedResolver;
@Before
- @After
- public void deleteDatabase() throws Exception {
- getContext().deleteDatabase(TEST_RECOMPUTE_DB);
- getContext().deleteDatabase(TEST_UPGRADE_DB);
- getContext().deleteDatabase(TEST_DOWNGRADE_DB);
- getContext().deleteDatabase(TEST_CLEAN_DB);
+ public void setUp() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ sIsolatedContext = new IsolatedContext(context, TAG);
+ sIsolatedResolver = sIsolatedContext.getContentResolver();
}
@Test
public void testFilterVolumeNames() throws Exception {
- try (DatabaseHelper helper = new DatabaseHelperR(getContext(), TEST_CLEAN_DB)) {
+ try (DatabaseHelper helper = new DatabaseHelperR(sIsolatedContext, TEST_CLEAN_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
{
final ContentValues values = new ContentValues();
@@ -155,7 +154,7 @@
@Test
public void testTransactions() throws Exception {
- try (DatabaseHelper helper = new DatabaseHelperR(getContext(), TEST_CLEAN_DB)) {
+ try (DatabaseHelper helper = new DatabaseHelperR(sIsolatedContext, TEST_CLEAN_DB)) {
helper.beginTransaction();
try {
helper.setTransactionSuccessful();
@@ -187,7 +186,7 @@
private void assertDowngrade(Class<? extends DatabaseHelper> before,
Class<? extends DatabaseHelper> after) throws Exception {
try (DatabaseHelper helper = before.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_DOWNGRADE_DB)) {
+ .newInstance(sIsolatedContext, TEST_DOWNGRADE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
{
final ContentValues values = new ContentValues();
@@ -206,7 +205,7 @@
// Downgrade will wipe data, but at least we don't crash
try (DatabaseHelper helper = after.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_DOWNGRADE_DB)) {
+ .newInstance(sIsolatedContext, TEST_DOWNGRADE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
try (Cursor c = db.query("files", null, null, null, null, null, null, null)) {
assertEquals(0, c.getCount());
@@ -234,7 +233,7 @@
private void assertRecompute(Class<? extends DatabaseHelper> before,
Class<? extends DatabaseHelper> after) throws Exception {
try (DatabaseHelper helper = before.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_RECOMPUTE_DB)) {
+ .newInstance(sIsolatedContext, TEST_RECOMPUTE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
{
final ContentValues values = new ContentValues();
@@ -298,7 +297,7 @@
}
try (DatabaseHelper helper = after.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_RECOMPUTE_DB)) {
+ .newInstance(sIsolatedContext, TEST_RECOMPUTE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
try (Cursor c = db.query("files", null, FileColumns.DISPLAY_NAME + "='global.jpg'",
null, null, null, null)) {
@@ -368,18 +367,18 @@
private void assertUpgrade(Class<? extends DatabaseHelper> before,
Class<? extends DatabaseHelper> after) throws Exception {
try (DatabaseHelper helper = before.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_UPGRADE_DB)) {
+ .newInstance(sIsolatedContext, TEST_UPGRADE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
}
try (DatabaseHelper helper = after.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_UPGRADE_DB)) {
+ .newInstance(sIsolatedContext, TEST_UPGRADE_DB)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
// Create a second isolated instance from scratch and assert that
// upgraded schema is identical
try (DatabaseHelper helper2 = after.getConstructor(Context.class, String.class)
- .newInstance(getContext(), TEST_CLEAN_DB)) {
+ .newInstance(sIsolatedContext, TEST_CLEAN_DB)) {
SQLiteDatabase db2 = helper2.getWritableDatabaseForTest();
try (Cursor c1 = db.query("sqlite_master",
@@ -426,7 +425,7 @@
private static class DatabaseHelperO extends DatabaseHelper {
public DatabaseHelperO(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_O,
- false, false, true, Column.class, null, null, null, null);
+ false, false, false, Column.class, null, null, null, null);
}
@Override
@@ -438,7 +437,7 @@
private static class DatabaseHelperP extends DatabaseHelper {
public DatabaseHelperP(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_P,
- false, false, true, Column.class, null, null, null, null);
+ false, false, false, Column.class, null, null, null, null);
}
@Override
@@ -450,7 +449,7 @@
private static class DatabaseHelperQ extends DatabaseHelper {
public DatabaseHelperQ(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_Q,
- false, false, true, Column.class, null, null, null, null);
+ false, false, false, Column.class, null, null, null, null);
}
@Override
@@ -462,7 +461,7 @@
private static class DatabaseHelperR extends DatabaseHelper {
public DatabaseHelperR(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_R,
- false, false, true, Column.class, null, null, null, null);
+ false, false, false, Column.class, null, null, null, null);
}
}