Merge cherrypicks of ['googleplex-android-review.googlesource.com/32875067', 'googleplex-android-review.googlesource.com/34151218'] into udc-platform-release.
Change-Id: I345c791fbee31763ec3706ed1ff7074193e7ac01
diff --git a/src/com/android/providers/media/AccessChecker.java b/src/com/android/providers/media/AccessChecker.java
index adde626..6ed2dc9 100644
--- a/src/com/android/providers/media/AccessChecker.java
+++ b/src/com/android/providers/media/AccessChecker.java
@@ -55,10 +55,8 @@
import static com.android.providers.media.LocalUriMatcher.VIDEO_THUMBNAILS;
import static com.android.providers.media.LocalUriMatcher.VIDEO_THUMBNAILS_ID;
import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
-import static com.android.providers.media.MediaProvider.INCLUDED_DEFAULT_DIRECTORIES;
import static com.android.providers.media.util.DatabaseUtils.bindSelection;
-import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.MediaColumns;
@@ -69,6 +67,8 @@
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
/**
* Class responsible for performing all access checks (read/write access states for calling package)
@@ -232,7 +232,7 @@
@NonNull
public static String getWhereForConstrainedAccess(
@NonNull LocalCallingIdentity callingIdentity, int uriType,
- boolean forWrite, @NonNull Bundle extras) {
+ boolean forWrite, Optional<List<String>> includedDefaultDirectoriesOptional) {
switch (uriType) {
case AUDIO_MEDIA_ID:
case AUDIO_MEDIA: {
@@ -326,9 +326,12 @@
// Allow access to file in directories. This si particularly used only for
// SystemGallery use-case
- final String defaultDirectorySql = getWhereForDefaultDirectoryMatch(extras);
- if (defaultDirectorySql != null) {
- options.add(defaultDirectorySql);
+ if (includedDefaultDirectoriesOptional.isPresent()) {
+ final String defaultDirectorySql = getWhereForDefaultDirectoryMatch(
+ includedDefaultDirectoriesOptional.get());
+ if (defaultDirectorySql != null) {
+ options.add(defaultDirectorySql);
+ }
}
return TextUtils.join(" OR ", options);
@@ -387,12 +390,11 @@
* @see MediaProvider#INCLUDED_DEFAULT_DIRECTORIES
*/
@Nullable
- private static String getWhereForDefaultDirectoryMatch(@NonNull Bundle extras) {
- final ArrayList<String> includedDefaultDirs = extras.getStringArrayList(
- INCLUDED_DEFAULT_DIRECTORIES);
+ private static String getWhereForDefaultDirectoryMatch(
+ List<String> includedDefaultDirectories) {
final ArrayList<String> options = new ArrayList<>();
- if (includedDefaultDirs != null) {
- for (String defaultDir : includedDefaultDirs) {
+ if (includedDefaultDirectories != null) {
+ for (String defaultDir : includedDefaultDirectories) {
options.add(FileColumns.RELATIVE_PATH + " LIKE '" + defaultDir + "/%'");
}
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 623d229..ed25b73 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -408,15 +408,6 @@
private static final String FILE_DATABASE_UUID = ".database_uuid";
/**
- * Specify what default directories the caller gets full access to. By default, the caller
- * shouldn't get full access to any default dirs.
- * But for example, we do an exception for System Gallery apps and allow them full access to:
- * DCIM, Pictures, Movies.
- */
- static final String INCLUDED_DEFAULT_DIRECTORIES =
- "android:included-default-directories";
-
- /**
* Value indicating that operations should include database rows matching the criteria defined
* by this key only when calling package has write permission to the database row or column is
* {@column MediaColumns#IS_PENDING} and is set by FUSE.
@@ -2605,7 +2596,8 @@
}
final String writeAccessCheckSql = getWhereForConstrainedAccess(mCallingIdentity.get(),
- uriType, /* forWrite */ true, Bundle.EMPTY);
+ uriType, /* forWrite */ true, /* includedDefaultDirectoriesOptional */
+ Optional.empty());
final String matchWritableRowsClause = String.format("%s=0 OR (%s=1 AND (%s OR %s))",
column, column, MATCH_PENDING_FROM_FUSE, writeAccessCheckSql);
@@ -2764,7 +2756,8 @@
final String[] selectionArgs = new String[] {path};
final SQLiteQueryBuilder qbForQuery =
- getQueryBuilder(TYPE_QUERY, match, uri, Bundle.EMPTY, null);
+ getQueryBuilder(TYPE_QUERY, match, uri, Bundle.EMPTY,
+ null, /* includedDefaultDirectoriesOptional */ Optional.empty());
try (Cursor c = qbForQuery.query(helper, new String[] {FileColumns.OWNER_PACKAGE_NAME},
selection, selectionArgs, null, null, null, null, null)) {
if (!c.moveToFirst()) {
@@ -2781,7 +2774,8 @@
}
final SQLiteQueryBuilder qbForUpdate =
- getQueryBuilder(TYPE_UPDATE, match, uri, Bundle.EMPTY, null);
+ getQueryBuilder(TYPE_UPDATE, match, uri, Bundle.EMPTY, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
ContentValues values = new ContentValues();
values.put(FileColumns.OWNER_PACKAGE_NAME, "null");
return qbForUpdate.update(helper, values, selection, selectionArgs) == 1;
@@ -2789,14 +2783,15 @@
private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
@NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values) {
- return updateDatabaseForFuseRename(helper, oldPath, newPath, values, Bundle.EMPTY);
+ return updateDatabaseForFuseRename(helper, oldPath, newPath, values, Bundle.EMPTY,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
}
private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
@NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values,
- @NonNull Bundle qbExtras) {
+ @NonNull Bundle qbExtras, Optional<List<String>> includedDefaultDirectoriesOptional) {
return updateDatabaseForFuseRename(helper, oldPath, newPath, values, qbExtras,
- FileUtils.getContentUriForPath(oldPath));
+ FileUtils.getContentUriForPath(oldPath), includedDefaultDirectoriesOptional);
}
/**
@@ -2804,10 +2799,12 @@
*/
private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
@NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values,
- @NonNull Bundle qbExtras, Uri uriOldPath) {
+ @NonNull Bundle qbExtras, Uri uriOldPath,
+ Optional<List<String>> includedDefaultDirectoriesOptional) {
boolean allowHidden = isCallingPackageAllowedHidden();
final SQLiteQueryBuilder qbForUpdate = getQueryBuilder(TYPE_UPDATE,
- matchUri(uriOldPath, allowHidden), uriOldPath, qbExtras, null);
+ matchUri(uriOldPath, allowHidden), uriOldPath, qbExtras, null,
+ includedDefaultDirectoriesOptional);
// uriOldPath may use Files uri which doesn't allow modifying AudioColumns. Include
// AudioColumns projection map if we are modifying any audio columns while renaming
@@ -2835,7 +2832,8 @@
}
if (retryUpdateWithReplace) {
- if (deleteForFuseRename(helper, oldPath, newPath, qbExtras, selection, allowHidden)) {
+ if (deleteForFuseRename(helper, oldPath, newPath, qbExtras, selection, allowHidden,
+ includedDefaultDirectoriesOptional)) {
Log.i(TAG, "Retrying database update after deleting conflicting entry");
count = qbForUpdate.update(helper, values, selection, new String[]{oldPath});
} else {
@@ -2846,13 +2844,15 @@
}
private boolean deleteForFuseRename(DatabaseHelper helper, String oldPath,
- String newPath, Bundle qbExtras, String selection, boolean allowHidden) {
+ String newPath, Bundle qbExtras, String selection, boolean allowHidden,
+ Optional<List<String>> includedDefaultDirectoriesOptional) {
// We are replacing file in newPath with file in oldPath. If calling package has
// write permission for newPath, delete existing database entry and retry update.
final Uri uriNewPath = FileUtils.getContentUriForPath(oldPath);
final SQLiteQueryBuilder qbForDelete = getQueryBuilder(TYPE_DELETE,
- matchUri(uriNewPath, allowHidden), uriNewPath, qbExtras, null);
- if (qbForDelete.delete(helper, selection, new String[] {newPath}) == 1) {
+ matchUri(uriNewPath, allowHidden), uriNewPath, qbExtras, null,
+ includedDefaultDirectoriesOptional);
+ if (qbForDelete.delete(helper, selection, new String[]{newPath}) == 1) {
return true;
}
// Check if delete can be done using other URI grants
@@ -2980,7 +2980,7 @@
final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE,
matchUri(uriOldPath, isCallingPackageAllowedHidden()), uriOldPath, Bundle.EMPTY,
- null);
+ null, /* includedDefaultDirectoriesOptional */ Optional.empty());
final DatabaseHelper helper;
try {
helper = getDatabaseForUri(uriOldPath);
@@ -3072,9 +3072,6 @@
helper.beginTransaction();
try {
- final Bundle qbExtras = new Bundle();
- qbExtras.putStringArrayList(INCLUDED_DEFAULT_DIRECTORIES,
- getIncludedDefaultDirectories());
final boolean wasHidden = FileUtils.shouldDirBeHidden(new File(oldPath));
final boolean isHidden = FileUtils.shouldDirBeHidden(new File(newPath));
for (String filePath : fileList) {
@@ -3083,7 +3080,7 @@
if(!updateDatabaseForFuseRename(helper, oldPath + "/" + filePath, newFilePath,
getContentValuesForFuseRename(newFilePath, mimeType, wasHidden, isHidden,
/* isSameMimeType */ true),
- qbExtras)) {
+ new Bundle(), Optional.of(getIncludedDefaultDirectories()))) {
Log.e(TAG, "Calling package doesn't have write permission to rename file.");
return OsConstants.EPERM;
}
@@ -3207,7 +3204,7 @@
return false;
}
return updateDatabaseForFuseRename(helper, oldPath, newPath, contentValues, Bundle.EMPTY,
- oldPathGrantedUri);
+ oldPathGrantedUri, /* includedDefaultDirectoriesOptional */ Optional.empty());
}
/**
@@ -3384,7 +3381,8 @@
type = TYPE_QUERY;
}
- final SQLiteQueryBuilder qb = getQueryBuilder(type, table, uri, Bundle.EMPTY, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(type, table, uri, Bundle.EMPTY,
+ null, /* includedDefaultDirectoriesOptional */ Optional.empty());
try (Cursor c = qb.query(helper,
new String[] { BaseColumns._ID }, null, null, null, null, null, null, null)) {
if (c.getCount() == 1) {
@@ -3473,9 +3471,6 @@
PulledMetrics.logVolumeAccessViaMediaProvider(getCallingUidOrSelf(), volumeName);
queryArgs = (queryArgs != null) ? queryArgs : new Bundle();
- // INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
- queryArgs.remove(INCLUDED_DEFAULT_DIRECTORIES);
-
final ArraySet<String> honoredArgs = new ArraySet<>();
DatabaseUtils.resolveQueryArgs(queryArgs, honoredArgs::add, this::ensureCustomCollator);
@@ -3532,7 +3527,7 @@
final DatabaseHelper helper = getDatabaseForUri(uri);
final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, table, uri, queryArgs,
- honoredArgs::add);
+ honoredArgs::add, /* includedDefaultDirectoriesOptional */ Optional.empty());
// Allowing hidden column _user_id for this query to support Cloned Profile use case.
if (table == FILES) {
qb.allowColumn(FileColumns._USER_ID);
@@ -4918,7 +4913,7 @@
// row irrespective of is_download=1.
final Uri uri = FileUtils.getContentUriForPath(path);
SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, matchUri(uri, allowHidden), uri,
- extras, null);
+ extras, null, /* includedDefaultDirectoriesOptional */ Optional.empty());
// We won't be able to update columns that are not part of projection map of Files table. We
// have already checked strict columns in previous insert operation which failed with
@@ -4996,9 +4991,6 @@
// REDACTED_URI_BUNDLE_KEY extra should only be set inside MediaProvider.
extras.remove(QUERY_ARG_REDACTED_URI);
- // INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
- extras.remove(INCLUDED_DEFAULT_DIRECTORIES);
-
final boolean allowHidden = isCallingPackageAllowedHidden();
final int match = matchUri(uri, allowHidden);
@@ -5147,7 +5139,8 @@
long rowId = -1;
Uri newUri = null;
- final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_INSERT, match, uri, extras, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_INSERT, match, uri, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
switch (match) {
case IMAGES_MEDIA: {
@@ -5419,7 +5412,8 @@
// We already handle the required permission checks for the app before we get here
final LocalCallingIdentity token = clearLocalCallingIdentity();
try {
- return getQueryBuilder(type, match, uri, extras, honored);
+ return getQueryBuilder(type, match, uri, extras, honored,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
} finally {
restoreLocalCallingIdentity(token);
}
@@ -5441,17 +5435,20 @@
* </ul>
*/
private @NonNull SQLiteQueryBuilder getQueryBuilder(int type, int match,
- @NonNull Uri uri, @NonNull Bundle extras, @Nullable Consumer<String> honored) {
+ @NonNull Uri uri, @NonNull Bundle extras, @Nullable Consumer<String> honored,
+ Optional<List<String>> includedDefaultDirectoriesOptional) {
Trace.beginSection("MP.getQueryBuilder");
try {
- return getQueryBuilderInternal(type, match, uri, extras, honored);
+ return getQueryBuilderInternal(type, match, uri, extras, honored,
+ includedDefaultDirectoriesOptional);
} finally {
Trace.endSection();
}
}
private @NonNull SQLiteQueryBuilder getQueryBuilderInternal(int type, int match,
- @NonNull Uri uri, @NonNull Bundle extras, @Nullable Consumer<String> honored) {
+ @NonNull Uri uri, @NonNull Bundle extras, @Nullable Consumer<String> honored,
+ Optional<List<String>> includedDefaultDirectoriesOptional) {
final boolean forWrite;
switch (type) {
case TYPE_QUERY: forWrite = false; break;
@@ -5521,7 +5518,8 @@
// to commit to this as an API.
final boolean includeAllVolumes = shouldIncludeRecentlyUnmountedVolumes(uri, extras);
- appendAccessCheckQuery(qb, forWrite, uri, match, extras, volumeName);
+ appendAccessCheckQuery(qb, forWrite, uri, match, extras, volumeName,
+ includedDefaultDirectoriesOptional);
switch (match) {
case IMAGES_MEDIA_ID:
@@ -5919,7 +5917,8 @@
}
private void appendAccessCheckQuery(@NonNull SQLiteQueryBuilder qb, boolean forWrite,
- @NonNull Uri uri, int uriType, @NonNull Bundle extras, @NonNull String volumeName) {
+ @NonNull Uri uri, int uriType, @NonNull Bundle extras, @NonNull String volumeName,
+ Optional<List<String>> includedDefaultDirectoriesOptional) {
Objects.requireNonNull(extras);
final Uri redactedUri = extras.getParcelable(QUERY_ARG_REDACTED_URI);
@@ -5952,7 +5951,7 @@
// Allow access to files which are owned by the caller. Or allow access to files based on
// legacy or any other special access permissions.
options.add(getWhereForConstrainedAccess(mCallingIdentity.get(), uriType, forWrite,
- extras));
+ includedDefaultDirectoriesOptional));
appendWhereStandalone(qb, TextUtils.join(" OR ", options));
}
@@ -6045,9 +6044,6 @@
return 0;
}
- // INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
- extras.remove(INCLUDED_DEFAULT_DIRECTORIES);
-
uri = safeUncanonicalize(uri);
final boolean allowHidden = isCallingPackageAllowedHidden();
final int match = matchUri(uri, allowHidden);
@@ -6125,7 +6121,8 @@
}
}
- final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_DELETE, match, uri, extras, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_DELETE, match, uri, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
{
// Give callers interacting with a specific media item a chance to
@@ -6254,7 +6251,8 @@
// 2. delete file row from the db
final boolean allowHidden = isCallingPackageAllowedHidden();
final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_DELETE,
- matchUri(uriGranted, allowHidden), uriGranted, extras, null);
+ matchUri(uriGranted, allowHidden), uriGranted, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
int count = qb.delete(helper, BaseColumns._ID + "=" + id, null);
if (isDownload == 1) {
@@ -7281,8 +7279,6 @@
// Related items are only considered for new media creation, and they
// can't be leveraged to move existing content into blocked locations
extras.remove(QUERY_ARG_RELATED_URI);
- // INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
- extras.remove(INCLUDED_DEFAULT_DIRECTORIES);
final String userWhere = extras.getString(QUERY_ARG_SQL_SELECTION);
final String[] userWhereArgs = extras.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS);
@@ -7356,7 +7352,8 @@
}
}
- final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, match, uri, extras, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, match, uri, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
// Give callers interacting with a specific media item a chance to
// escalate access if they don't already have it
@@ -7875,7 +7872,8 @@
extras.putInt(QUERY_ARG_MATCH_PENDING, MATCH_INCLUDE);
extras.putInt(QUERY_ARG_MATCH_TRASHED, MATCH_INCLUDE);
final SQLiteQueryBuilder qbForReplace = getQueryBuilder(TYPE_DELETE,
- matchUri(uri, allowHidden), uri, extras, null);
+ matchUri(uri, allowHidden), uri, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
final long rowId = getIdIfPathOwnedByPackages(qbForReplace, helper, path,
mCallingIdentity.get().getSharedPackagesAsString());
@@ -8171,7 +8169,8 @@
try {
helper = getDatabaseForUri(membersUri);
qb = getQueryBuilder(TYPE_DELETE, AUDIO_PLAYLISTS_ID_MEMBERS,
- membersUri, queryArgs, null);
+ membersUri, queryArgs, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
} catch (VolumeNotFoundException ignored) {
return new int[0];
}
@@ -10286,7 +10285,8 @@
// First, check to see if caller has direct write access
if (forWrite) {
- final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, table, uri, extras, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, table, uri, extras,
+ null, /* includedDefaultDirectoriesOptional */ Optional.empty());
qb.allowColumn(SQLiteQueryBuilder.ROWID_COLUMN);
try (Cursor c = qb.query(helper, new String[] { SQLiteQueryBuilder.ROWID_COLUMN },
selection, selectionArgs, null, null, null, null, null)) {
@@ -10310,7 +10310,8 @@
}
// Second, check to see if caller has direct read access
- final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, table, uri, extras, null);
+ final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, table, uri, extras, null,
+ /* includedDefaultDirectoriesOptional */ Optional.empty());
qb.allowColumn(SQLiteQueryBuilder.ROWID_COLUMN);
try (Cursor c = qb.query(helper, new String[] { SQLiteQueryBuilder.ROWID_COLUMN },
selection, selectionArgs, null, null, null, null, null)) {
diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
index 15cf150..d2ec58c 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -31,6 +31,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
@@ -821,10 +822,30 @@
}
private Cursor queryMediaIdForAppsInternal(@NonNull SQLiteQueryBuilder qb,
- @NonNull String[] projection, @NonNull String[] selectionArgs) {
- return qb.query(mDatabase, getMediaStoreProjectionLocked(projection),
- /* selection */ null, selectionArgs, /* groupBy */ null, /* having */ null,
- /* orderBy */ null, /* limitStr */ null);
+ @NonNull String[] columns, @NonNull String[] selectionArgs) {
+ final Cursor cursor =
+ qb.query(mDatabase, getMediaStoreProjectionLocked(columns),
+ /* selection */ null, selectionArgs, /* groupBy */ null, /* having */ null,
+ /* orderBy */ null, /* limitStr */ null);
+
+ if (columns == null || columns.length == 0 || cursor.getColumnCount() == columns.length) {
+ return cursor;
+ } else {
+ // An unknown column was encountered. Populate it will null for backwards compatibility.
+ final MatrixCursor result = new MatrixCursor(columns);
+ if (cursor.moveToFirst()) {
+ do {
+ final ContentValues contentValues = new ContentValues();
+ DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
+ final MatrixCursor.RowBuilder rowBuilder = result.newRow();
+ for (String column : columns) {
+ rowBuilder.add(column, contentValues.get(column));
+ }
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return result;
+ }
}
/**
@@ -938,53 +959,50 @@
}
private String[] getMediaStoreProjectionLocked(String[] columns) {
- final String[] projection = new String[columns.length];
+ final List<String> projection = new ArrayList<>();
- for (int i = 0; i < projection.length; i++) {
+ for (int i = 0; i < columns.length; i++) {
switch (columns[i]) {
case PickerMediaColumns.DATA:
- projection[i] = getProjectionDataLocked(PickerMediaColumns.DATA);
+ projection.add(getProjectionDataLocked(PickerMediaColumns.DATA));
break;
case PickerMediaColumns.DISPLAY_NAME:
- projection[i] =
- getProjectionSimple(
- getDisplayNameSql(), PickerMediaColumns.DISPLAY_NAME);
+ projection.add(getProjectionSimple(
+ getDisplayNameSql(), PickerMediaColumns.DISPLAY_NAME));
break;
case PickerMediaColumns.MIME_TYPE:
- projection[i] =
- getProjectionSimple(KEY_MIME_TYPE, PickerMediaColumns.MIME_TYPE);
+ projection.add(getProjectionSimple(
+ KEY_MIME_TYPE, PickerMediaColumns.MIME_TYPE));
break;
case PickerMediaColumns.DATE_TAKEN:
- projection[i] =
- getProjectionSimple(KEY_DATE_TAKEN_MS, PickerMediaColumns.DATE_TAKEN);
+ projection.add(getProjectionSimple(
+ KEY_DATE_TAKEN_MS, PickerMediaColumns.DATE_TAKEN));
break;
case PickerMediaColumns.SIZE:
- projection[i] = getProjectionSimple(KEY_SIZE_BYTES, PickerMediaColumns.SIZE);
+ projection.add(getProjectionSimple(KEY_SIZE_BYTES, PickerMediaColumns.SIZE));
break;
case PickerMediaColumns.DURATION_MILLIS:
- projection[i] =
- getProjectionSimple(
- KEY_DURATION_MS, PickerMediaColumns.DURATION_MILLIS);
+ projection.add(getProjectionSimple(
+ KEY_DURATION_MS, PickerMediaColumns.DURATION_MILLIS));
break;
case PickerMediaColumns.HEIGHT:
- projection[i] = getProjectionSimple(KEY_HEIGHT, PickerMediaColumns.HEIGHT);
+ projection.add(getProjectionSimple(KEY_HEIGHT, PickerMediaColumns.HEIGHT));
break;
case PickerMediaColumns.WIDTH:
- projection[i] = getProjectionSimple(KEY_WIDTH, PickerMediaColumns.WIDTH);
+ projection.add(getProjectionSimple(KEY_WIDTH, PickerMediaColumns.WIDTH));
break;
case PickerMediaColumns.ORIENTATION:
- projection[i] =
- getProjectionSimple(KEY_ORIENTATION, PickerMediaColumns.ORIENTATION);
+ projection.add(getProjectionSimple(
+ KEY_ORIENTATION, PickerMediaColumns.ORIENTATION));
break;
default:
- projection[i] = getProjectionSimple("NULL", columns[i]);
// Ignore unsupported columns; we do not throw error here to support
- // backward compatibility
+ // backward compatibility for ACTION_GET_CONTENT takeover.
Log.w(TAG, "Unexpected Picker column: " + columns[i]);
}
}
- return projection;
+ return projection.toArray(new String[0]);
}
private String getProjectionAuthorityLocked() {
diff --git a/tests/src/com/android/providers/media/AccessCheckerTest.java b/tests/src/com/android/providers/media/AccessCheckerTest.java
index 49a870e..0145bcd 100644
--- a/tests/src/com/android/providers/media/AccessCheckerTest.java
+++ b/tests/src/com/android/providers/media/AccessCheckerTest.java
@@ -47,7 +47,8 @@
import static org.junit.Assert.assertThrows;
-import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
import android.system.Os;
import android.text.TextUtils;
@@ -59,6 +60,8 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
@RunWith(AndroidJUnit4.class)
public class AccessCheckerTest {
@@ -314,22 +317,27 @@
// App with no permissions only has access to owned files
assertWithMessage("Expected owned access SQL for Audio collection")
- .that(getWhereForConstrainedAccess(hasNoPerms, AUDIO_MEDIA, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasNoPerms, AUDIO_MEDIA, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasNoPerms)
+ " OR is_ringtone=1 OR is_alarm=1 OR is_notification=1");
assertWithMessage("Expected owned access SQL for Video collection")
- .that(getWhereForConstrainedAccess(hasNoPerms, VIDEO_MEDIA, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasNoPerms, VIDEO_MEDIA, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasNoPerms));
assertWithMessage("Expected owned access SQL for Images collection")
- .that(getWhereForConstrainedAccess(hasNoPerms, IMAGES_MEDIA, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasNoPerms, IMAGES_MEDIA, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasNoPerms));
// App with no permissions only has access to owned files
assertWithMessage("Expected owned access SQL for Downloads collection")
- .that(getWhereForConstrainedAccess(hasNoPerms, DOWNLOADS, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasNoPerms, DOWNLOADS, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasNoPerms));
assertWithMessage("Expected owned access SQL for FILES collection")
- .that(getWhereForConstrainedAccess(hasNoPerms, FILES, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasNoPerms, FILES, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasNoPerms));
}
@@ -344,12 +352,27 @@
// App with READ_EXTERNAL_STORAGE or READ_MEDIA_* permission has access to only owned
// non-media files or media files.
assertWithMessage("Expected owned access SQL for Downloads collection")
- .that(getWhereForConstrainedAccess(hasReadMedia, DOWNLOADS, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasReadMedia, DOWNLOADS, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasReadMedia));
assertWithMessage("Expected owned access SQL for FILES collection")
- .that(getWhereForConstrainedAccess(hasReadMedia, FILES, false, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasReadMedia, FILES, false,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(
getWhereForOwnerPackageMatch(hasReadMedia) + " OR " + getFilesAccessSql());
+ assertWithMessage("Expected owned access SQL for FILES collection")
+ .that(getWhereForConstrainedAccess(hasReadMedia, FILES, false,
+ /* includedDefaultDirectoriesOptional */Optional.of(
+ List.of(Environment.DIRECTORY_DCIM, Environment.DIRECTORY_PICTURES,
+ Environment.DIRECTORY_MOVIES))))
+ .isEqualTo(
+ getWhereForOwnerPackageMatch(hasReadMedia) + " OR "
+ + getFilesAccessSql() + " OR "
+ + MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE 'DCIM/%'"
+ + " OR " + MediaStore.Files.FileColumns.RELATIVE_PATH
+ + " LIKE 'Pictures/%'"
+ + " OR " + MediaStore.Files.FileColumns.RELATIVE_PATH
+ + " LIKE 'Movies/%'");
}
@Test
@@ -359,22 +382,27 @@
// App with no permissions only has access to owned files.
assertWithMessage("Expected owned access SQL for Audio collection")
- .that(getWhereForConstrainedAccess(noPerms, AUDIO_MEDIA, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(noPerms, AUDIO_MEDIA, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(noPerms)
+ " OR is_ringtone=1 OR is_alarm=1 OR is_notification=1");
assertWithMessage("Expected owned access SQL for Video collection")
- .that(getWhereForConstrainedAccess(noPerms, VIDEO_MEDIA, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(noPerms, VIDEO_MEDIA, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(noPerms));
assertWithMessage("Expected owned access SQL for Images collection")
- .that(getWhereForConstrainedAccess(noPerms, IMAGES_MEDIA, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(noPerms, IMAGES_MEDIA, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(noPerms));
// App with no permissions only has access to owned files
assertWithMessage("Expected owned access SQL for Downloads collection")
- .that(getWhereForConstrainedAccess(noPerms, DOWNLOADS, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(noPerms, DOWNLOADS, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(noPerms));
assertWithMessage("Expected owned access SQL for FILES collection")
- .that(getWhereForConstrainedAccess(noPerms, FILES, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(noPerms, FILES, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(noPerms));
}
@@ -389,10 +417,12 @@
// App with write permission to media files has access write access to media files and owned
// files.
assertWithMessage("Expected owned access SQL for Downloads collection")
- .that(getWhereForConstrainedAccess(hasReadPerms, DOWNLOADS, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasReadPerms, DOWNLOADS, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasReadPerms));
assertWithMessage("Expected owned access SQL for FILES collection")
- .that(getWhereForConstrainedAccess(hasReadPerms, FILES, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasReadPerms, FILES, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasReadPerms) + " OR "
+ getFilesAccessSql());
}
@@ -409,11 +439,13 @@
// Legacy app with WRITE_EXTERNAL_STORAGE permission has access to non-media files as well.
// However, they don't have global write access to secondary volume.
assertWithMessage("Expected where clause SQL for Downloads collection to be")
- .that(getWhereForConstrainedAccess(hasLegacyWrite, DOWNLOADS, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasLegacyWrite, DOWNLOADS, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasLegacyWrite) + " OR "
+ AccessChecker.getWhereForExternalPrimaryMatch());
assertWithMessage("Expected where clause SQL for FILES collection to be")
- .that(getWhereForConstrainedAccess(hasLegacyWrite, FILES, true, Bundle.EMPTY))
+ .that(getWhereForConstrainedAccess(hasLegacyWrite, FILES, true,
+ /* includedDefaultDirectoriesOptional */ Optional.empty()))
.isEqualTo(getWhereForOwnerPackageMatch(hasLegacyWrite) + " OR "
+ AccessChecker.getWhereForExternalPrimaryMatch() + " OR "
+ getFilesAccessSql());
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
index f38afe8..cfa8168 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
@@ -22,6 +22,7 @@
import static com.android.providers.media.util.MimeUtils.getExtensionFromMimeType;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
@@ -985,20 +986,32 @@
}
// Assert invalid projection column
- final String invalidColumn = "testInvalidColumn";
- final String[] invalidProjection = new String[] {
+ final String invalidColumn = "test invalid column";
+ final String[] invalidProjection = new String[]{
PickerMediaColumns.DATE_TAKEN,
invalidColumn
};
try (Cursor cr = mFacade.queryMediaIdForApps(CLOUD_PROVIDER, CLOUD_ID,
invalidProjection)) {
- assertThat(cr.getCount()).isEqualTo(1);
+ assertWithMessage(
+ "Unexpected number of rows when asserting invalid projection column with "
+ + "cloud provider.")
+ .that(cr.getCount()).isEqualTo(1);
+ assertWithMessage("Unexpected number of columns in cursor")
+ .that(cr.getColumnCount())
+ .isEqualTo(2);
cr.moveToFirst();
- assertThat(cr.getLong(cr.getColumnIndex(invalidColumn)))
+ assertWithMessage("Unexpected value of the invalidColumn with cloud provider.")
+ .that(cr.getLong(cr.getColumnIndexOrThrow(invalidColumn)))
.isEqualTo(0);
- assertThat(cr.getLong(cr.getColumnIndex(PickerMediaColumns.DATE_TAKEN)))
+ assertWithMessage("Unexpected value of the invalidColumn with cloud provider.")
+ .that(cr.getString(cr.getColumnIndexOrThrow(invalidColumn)))
+ .isEqualTo(null);
+ assertWithMessage(
+ "Unexpected value of PickerMediaColumns.DATE_TAKEN with cloud provider.")
+ .that(cr.getLong(cr.getColumnIndexOrThrow(PickerMediaColumns.DATE_TAKEN)))
.isEqualTo(DATE_TAKEN_MS);
}
}