summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin F. Haggerty <haggertk@lineageos.org>2021-03-02 06:03:07 -0700
committerKevin F. Haggerty <haggertk@lineageos.org>2021-03-02 06:03:07 -0700
commit812423083f9c6cb8ab3f6da0803e54ef2dadf613 (patch)
tree368b0200445fa62c7ffe491fddcb08ef12edffd6
parentf1bbe5e8e2a2732ab3d0c90919522070b64f6d0b (diff)
parent7a7ce311e5181020e5afb0c10351f91ecd857ff6 (diff)
Merge tag 'android-11.0.0_r32' into staging/lineage-18.1_merge-android-11.0.0_r32
Android 11.0.0 Release 32 (RQ2A.210305.006) * tag 'android-11.0.0_r32': Import translations. DO NOT MERGE ANYWHERE Handle 64-bit and end-of-file box lengths in IsoInterface Ensure default folders and Camera folders are always visible Import translations. DO NOT MERGE ANYWHERE Gracefully handle content uris without id in checkUriPermission Import translations. DO NOT MERGE ANYWHERE Don't allow renaming top level directory to another top level directory Support Playlist#Members uri in LegacMediaProvider Change-Id: Ie68fd6837bd823e5cc92bcedbba96ea21b0670e9
-rw-r--r--res/values-as/strings.xml2
-rw-r--r--res/values-bn/strings.xml2
-rw-r--r--res/values-gu/strings.xml2
-rw-r--r--res/values-kn/strings.xml2
-rw-r--r--res/values-ml/strings.xml2
-rw-r--r--res/values-mr/strings.xml2
-rw-r--r--res/values-or/strings.xml2
-rw-r--r--res/values-ta/strings.xml2
-rw-r--r--src/com/android/providers/media/MediaProvider.java65
-rw-r--r--src/com/android/providers/media/util/FileUtils.java55
-rw-r--r--tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java68
11 files changed, 162 insertions, 42 deletions
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index be897a13..35449a87 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"মিডিয়া সঞ্চয়াগাৰ"</string>
<string name="artist_label" msgid="8105600993099120273">"শিল্পী"</string>
<string name="unknown" msgid="2059049215682829375">"অজ্ঞাত"</string>
- <string name="root_images" msgid="5861633549189045666">"প্ৰতিচ্ছবিসমূহ"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"ভিডিঅ\'সমূহ"</string>
<string name="root_audio" msgid="3505830755201326018">"অডিঅ’"</string>
<string name="root_documents" msgid="3829103301363849237">"নথিপত্র"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 552556eb..4b02f685 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"মিডিয়া স্টোরেজ"</string>
<string name="artist_label" msgid="8105600993099120273">"শিল্পী"</string>
<string name="unknown" msgid="2059049215682829375">"অজানা"</string>
- <string name="root_images" msgid="5861633549189045666">"ছবি"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"ভিডিও"</string>
<string name="root_audio" msgid="3505830755201326018">"অডিও"</string>
<string name="root_documents" msgid="3829103301363849237">"ডকুমেন্ট"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index e7c56191..d7cc37d3 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"મીડિયા સ્ટોરેજ"</string>
<string name="artist_label" msgid="8105600993099120273">"કલાકાર"</string>
<string name="unknown" msgid="2059049215682829375">"અજાણ"</string>
- <string name="root_images" msgid="5861633549189045666">"છબીઓ"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"વીડિયો"</string>
<string name="root_audio" msgid="3505830755201326018">"ઑડિયો"</string>
<string name="root_documents" msgid="3829103301363849237">"દસ્તાવેજો"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index a39e5746..99c17889 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"ಮಾಧ್ಯಮ ಸಂಗ್ರಹಣೆ"</string>
<string name="artist_label" msgid="8105600993099120273">"ಕಲಾವಿದರು"</string>
<string name="unknown" msgid="2059049215682829375">"ಅಪರಿಚಿತ"</string>
- <string name="root_images" msgid="5861633549189045666">"ಚಿತ್ರಗಳು"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"ವೀಡಿಯೊಗಳು"</string>
<string name="root_audio" msgid="3505830755201326018">"ಆಡಿಯೊ"</string>
<string name="root_documents" msgid="3829103301363849237">"ಡಾಕ್ಯುಮೆಂಟ್‌ಗಳು"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 05d8624a..d505748d 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"മീഡിയ സ്റ്റോറേജ്"</string>
<string name="artist_label" msgid="8105600993099120273">"ആർട്ടിസ്‌റ്റ്"</string>
<string name="unknown" msgid="2059049215682829375">"അജ്ഞാതം"</string>
- <string name="root_images" msgid="5861633549189045666">"ചിത്രങ്ങൾ"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"വീഡിയോകൾ"</string>
<string name="root_audio" msgid="3505830755201326018">"ഓഡിയോ"</string>
<string name="root_documents" msgid="3829103301363849237">"ഡോക്യുമെന്റുകൾ"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 9a6b24ec..b110be31 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"मीडिया स्टोरेज"</string>
<string name="artist_label" msgid="8105600993099120273">"कलाकार"</string>
<string name="unknown" msgid="2059049215682829375">"अज्ञात"</string>
- <string name="root_images" msgid="5861633549189045666">"इमेज"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"व्हिडिओ"</string>
<string name="root_audio" msgid="3505830755201326018">"ऑडिओ"</string>
<string name="root_documents" msgid="3829103301363849237">"दस्तऐवज"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 86491aff..5ea6ea3e 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"ମିଡିଆ ଷ୍ଟୋରେଜ୍"</string>
<string name="artist_label" msgid="8105600993099120273">"କଳାକାର"</string>
<string name="unknown" msgid="2059049215682829375">"ଅଜଣା"</string>
- <string name="root_images" msgid="5861633549189045666">"ଇମେଜ୍‌"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"ଭିଡିଓ"</string>
<string name="root_audio" msgid="3505830755201326018">"ଅଡିଓ"</string>
<string name="root_documents" msgid="3829103301363849237">"ଡକ୍ୟୁମେଣ୍ଟଗୁଡ଼ିକ"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index a98d7011..f13588a2 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -21,7 +21,7 @@
<string name="app_label" msgid="9035307001052716210">"மீடியா சேமிப்பிடம்"</string>
<string name="artist_label" msgid="8105600993099120273">"கலைஞர்"</string>
<string name="unknown" msgid="2059049215682829375">"அறியாதது"</string>
- <string name="root_images" msgid="5861633549189045666">"படங்கள்"</string>
+ <string name="root_images" msgid="5861633549189045666">"Images"</string>
<string name="root_videos" msgid="8792703517064649453">"வீடியோக்கள்"</string>
<string name="root_audio" msgid="3505830755201326018">"ஆடியோ"</string>
<string name="root_documents" msgid="3829103301363849237">"ஆவணங்கள்"</string>
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 772ee64e..626bbb8e 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -54,6 +54,7 @@ import static com.android.providers.media.LocalCallingIdentity.PERMISSION_WRITE_
import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND;
import static com.android.providers.media.scan.MediaScanner.REASON_IDLE;
import static com.android.providers.media.util.DatabaseUtils.bindList;
+import static com.android.providers.media.util.FileUtils.DEFAULT_FOLDER_NAMES;
import static com.android.providers.media.util.FileUtils.PATTERN_PENDING_FILEPATH_FOR_SQL;
import static com.android.providers.media.util.FileUtils.extractDisplayName;
import static com.android.providers.media.util.FileUtils.extractFileName;
@@ -749,29 +750,6 @@ public class MediaProvider extends ContentProvider {
}
}
- private static final String[] sDefaultFolderNames = {
- Environment.DIRECTORY_MUSIC,
- Environment.DIRECTORY_PODCASTS,
- Environment.DIRECTORY_RINGTONES,
- Environment.DIRECTORY_ALARMS,
- Environment.DIRECTORY_NOTIFICATIONS,
- Environment.DIRECTORY_PICTURES,
- Environment.DIRECTORY_MOVIES,
- Environment.DIRECTORY_DOWNLOADS,
- Environment.DIRECTORY_DCIM,
- Environment.DIRECTORY_AUDIOBOOKS,
- Environment.DIRECTORY_DOCUMENTS,
- };
-
- private static boolean isDefaultDirectoryName(@Nullable String dirName) {
- for (String defaultDirName : sDefaultFolderNames) {
- if (defaultDirName.equals(dirName)) {
- return true;
- }
- }
- return false;
- }
-
/**
* Ensure that default folders are created on mounted primary storage
* devices. We only do this once per volume so we don't annoy the user if
@@ -796,7 +774,7 @@ public class MediaProvider extends ContentProvider {
final SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getContext());
if (prefs.getInt(key, 0) == 0) {
- for (String folderName : sDefaultFolderNames) {
+ for (String folderName : DEFAULT_FOLDER_NAMES) {
final File folder = new File(vol.getDirectory(), folderName);
if (!folder.exists()) {
folder.mkdirs();
@@ -2074,17 +2052,19 @@ public class MediaProvider extends ContentProvider {
// Rename not allowed on paths that can't be translated to RELATIVE_PATH.
Log.e(TAG, errorMessage + "Invalid path.");
return OsConstants.EPERM;
- } else if (oldRelativePath.length == 1 && TextUtils.isEmpty(oldRelativePath[0])) {
+ }
+ if (oldRelativePath.length == 1 && TextUtils.isEmpty(oldRelativePath[0])) {
// Allow rename of files/folders other than default directories.
final String displayName = extractDisplayName(oldPath);
- for (String defaultFolder : sDefaultFolderNames) {
+ for (String defaultFolder : DEFAULT_FOLDER_NAMES) {
if (displayName.equals(defaultFolder)) {
Log.e(TAG, errorMessage + oldPath + " is a default folder."
+ " Renaming a default folder is not allowed.");
return OsConstants.EPERM;
}
}
- } else if (newRelativePath.length == 1 && TextUtils.isEmpty(newRelativePath[0])) {
+ }
+ if (newRelativePath.length == 1 && TextUtils.isEmpty(newRelativePath[0])) {
Log.e(TAG, errorMessage + newPath + " is in root folder."
+ " Renaming a file/directory to root folder is not allowed");
return OsConstants.EPERM;
@@ -2147,10 +2127,13 @@ public class MediaProvider extends ContentProvider {
}
final int type;
+ final boolean forWrite;
if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
type = TYPE_UPDATE;
+ forWrite = true;
} else {
type = TYPE_QUERY;
+ forWrite = false;
}
final SQLiteQueryBuilder qb = getQueryBuilder(type, table, uri, Bundle.EMPTY, null);
@@ -2160,6 +2143,24 @@ public class MediaProvider extends ContentProvider {
return PackageManager.PERMISSION_GRANTED;
}
}
+
+ try {
+ if (ContentUris.parseId(uri) != -1) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ } catch (NumberFormatException ignored) { }
+
+ // If the uri is a valid content uri and doesn't have a valid ID at the end of the uri,
+ // (i.e., uri is uri of the table not of the item/row), and app doesn't request prefix
+ // grant, we are willing to grant this uri permission since this doesn't grant them any
+ // extra access. This grant will only grant permissions on given uri, it will not grant
+ // access to db rows of the corresponding table.
+ if ((modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) == 0) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ // For prefix grant on the uri with content uri without id, we don't allow apps to
+ // grant access as they might end up granting access to all files.
} finally {
restoreLocalCallingIdentity(token);
}
@@ -3031,7 +3032,11 @@ public class MediaProvider extends ContentProvider {
if (isCallingPackageSelf() && values.containsKey(FileColumns.MEDIA_TYPE)) {
// Leave FileColumns.MEDIA_TYPE untouched if the caller is ModernMediaScanner and
// FileColumns.MEDIA_TYPE is already populated.
- } else if (path != null && shouldFileBeHidden(new File(path))) {
+ } else if (isFuseThread() && path != null && shouldFileBeHidden(new File(path))) {
+ // We should only mark MEDIA_TYPE as MEDIA_TYPE_NONE for Fuse Thread.
+ // MediaProvider#insert() returns the uri by appending the "rowId" to the given
+ // uri, hence to ensure the correct working of the returned uri, we shouldn't
+ // change the MEDIA_TYPE in insert operation and let scan change it for us.
values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
} else {
values.put(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
@@ -6929,7 +6934,7 @@ public class MediaProvider extends ContentProvider {
if (isTopLevelDir) {
// We allow creating the default top level directories only, all other operations on
// top level directories are not allowed.
- if (forCreate && isDefaultDirectoryName(extractDisplayName(path))) {
+ if (forCreate && FileUtils.isDefaultDirectoryName(extractDisplayName(path))) {
return 0;
}
Log.e(TAG,
@@ -6994,7 +6999,7 @@ public class MediaProvider extends ContentProvider {
final boolean isTopLevelDir =
relativePath.length == 1 && TextUtils.isEmpty(relativePath[0]);
if (isTopLevelDir) {
- if (isDefaultDirectoryName(extractDisplayName(path))) {
+ if (FileUtils.isDefaultDirectoryName(extractDisplayName(path))) {
return 0;
} else {
Log.e(TAG,
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index 4515c6f3..47c5bc14 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -892,6 +892,21 @@ public class FileUtils {
public static final Pattern PATTERN_DATA_OR_OBB_PATH = Pattern.compile(
"(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb)/?$");
+ @VisibleForTesting
+ public static final String[] DEFAULT_FOLDER_NAMES = {
+ Environment.DIRECTORY_MUSIC,
+ Environment.DIRECTORY_PODCASTS,
+ Environment.DIRECTORY_RINGTONES,
+ Environment.DIRECTORY_ALARMS,
+ Environment.DIRECTORY_NOTIFICATIONS,
+ Environment.DIRECTORY_PICTURES,
+ Environment.DIRECTORY_MOVIES,
+ Environment.DIRECTORY_DOWNLOADS,
+ Environment.DIRECTORY_DCIM,
+ Environment.DIRECTORY_DOCUMENTS,
+ Environment.DIRECTORY_AUDIOBOOKS,
+ };
+
/**
* Regex that matches paths for {@link MediaColumns#RELATIVE_PATH}; it
* captures both top-level paths and sandboxed paths.
@@ -905,6 +920,9 @@ public class FileUtils {
private static final Pattern PATTERN_VOLUME_NAME = Pattern.compile(
"(?i)^/storage/([^/]+)");
+ private static final String CAMERA_RELATIVE_PATH =
+ String.format("%s/%s/", Environment.DIRECTORY_DCIM, "Camera");
+
private static @Nullable String normalizeUuid(@Nullable String fsUuid) {
return fsUuid != null ? fsUuid.toLowerCase(Locale.ROOT) : null;
}
@@ -1017,6 +1035,15 @@ public class FileUtils {
return relativePathSegments.length > 0 ? relativePathSegments[0] : null;
}
+ public static boolean isDefaultDirectoryName(@Nullable String dirName) {
+ for (String defaultDirName : DEFAULT_FOLDER_NAMES) {
+ if (defaultDirName.equalsIgnoreCase(dirName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Compute the value of {@link MediaColumns#DATE_EXPIRES} based on other
* columns being modified by this operation.
@@ -1214,12 +1241,32 @@ public class FileUtils {
}
final File nomedia = new File(dir, ".nomedia");
+
// check for .nomedia presence
- if (nomedia.exists()) {
- Logging.logPersistent("Observed non-standard " + nomedia);
- return true;
+ if (!nomedia.exists()) {
+ return false;
}
- return false;
+
+ // Handle top-level default directories. These directories should always be visible,
+ // regardless of .nomedia presence.
+ final String[] relativePath = sanitizePath(extractRelativePath(dir.getAbsolutePath()));
+ final boolean isTopLevelDir =
+ relativePath.length == 1 && TextUtils.isEmpty(relativePath[0]);
+ if (isTopLevelDir && isDefaultDirectoryName(name)) {
+ nomedia.delete();
+ return false;
+ }
+
+ // DCIM/Camera should always be visible regardless of .nomedia presence.
+ if (CAMERA_RELATIVE_PATH.equalsIgnoreCase(
+ extractRelativePathForDirectory(dir.getAbsolutePath()))) {
+ nomedia.delete();
+ return false;
+ }
+
+ // .nomedia is present which makes this directory as hidden directory
+ Logging.logPersistent("Observed non-standard " + nomedia);
+ return true;
}
/**
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index ec6bad1d..f7bb434b 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -45,6 +45,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.app.UiAutomation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -59,6 +60,7 @@ import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.MediaColumns;
+import android.util.Log;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
@@ -68,20 +70,26 @@ import com.android.providers.media.R;
import com.android.providers.media.scan.MediaScannerTest.IsolatedContext;
import com.android.providers.media.util.FileUtils;
+import com.google.common.io.ByteStreams;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
import java.util.Optional;
@RunWith(AndroidJUnit4.class)
public class ModernMediaScannerTest {
// TODO: scan directory-vs-files and confirm identical results
+ private static final String TAG = "ModernMediaScannerTest";
private File mDir;
private Context mIsolatedContext;
@@ -402,6 +410,41 @@ public class ModernMediaScannerTest {
}
}
+ private void assertVisibleFolder(File dir) throws Exception {
+ final File nomediaFile = new File(dir, ".nomedia");
+
+ if (!nomediaFile.getParentFile().exists()) {
+ assertTrue(nomediaFile.getParentFile().mkdirs());
+ }
+ try {
+ if (!nomediaFile.exists()) {
+ executeShellCommand("touch " + nomediaFile.getAbsolutePath());
+ assertTrue(nomediaFile.exists());
+ }
+ assertShouldScanPathAndIsPathHidden(true, false, dir);
+ } finally {
+ executeShellCommand("rm " + nomediaFile.getAbsolutePath());
+ }
+ }
+
+ /**
+ * b/168830497: Test that default folders and Camera folder are always visible
+ */
+ @Test
+ public void testVisibleDefaultFolders() throws Exception {
+ final File root = new File("storage/emulated/0");
+
+ // Top level directories should always be visible
+ for (String dirName : FileUtils.DEFAULT_FOLDER_NAMES) {
+ final File defaultFolder = new File(root, dirName);
+ assertVisibleFolder(defaultFolder);
+ }
+
+ // DCIM/Camera should always be visible
+ final File cameraDir = new File(root, Environment.DIRECTORY_DCIM + "/" + "Camera");
+ assertVisibleFolder(cameraDir);
+ }
+
private static void assertShouldScanDirectory(File file) {
assertTrue(file.getAbsolutePath(), shouldScanDirectory(file));
}
@@ -881,4 +924,29 @@ public class ModernMediaScannerTest {
assertTrue(isFileAlbumArt(file));
}
}
+
+ /**
+ * Executes a shell command.
+ */
+ public static String executeShellCommand(String command) throws IOException {
+ int attempt = 0;
+ while (attempt++ < 5) {
+ try {
+ return executeShellCommandInternal(command);
+ } catch (InterruptedIOException e) {
+ // Hmm, we had trouble executing the shell command; the best we
+ // can do is try again a few more times
+ Log.v(TAG, "Trouble executing " + command + "; trying again", e);
+ }
+ }
+ throw new IOException("Failed to execute " + command);
+ }
+
+ private static String executeShellCommandInternal(String cmd) throws IOException {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try (FileInputStream output = new FileInputStream(
+ uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
+ return new String(ByteStreams.toByteArray(output));
+ }
+ }
}