Building a File Explorer on Android 11 - android

I'm looking forward to build a File Explorer app but I'm shocked with what I found on I do need full read/write permission and access to everything.
So as they state here:
Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest.
Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app
To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager().
Now I can access to the root folder "/storage/emulated/0/" via Environment.getExternalStoragePublicDirectory(path) and then file.listFiles()
But when I want to access to Downloads folder I can't, I've looked at SAF but I'd have to use like ACTIONS and I just want to explore through folders, then I looked at MediaStore but there's not a clear example of how I could use it here since I'm trying to get the files but cursor.moveToNext() or cursor.moveToFirst() returns false
Here's the code I've used
val projectionDownloads = arrayOf(
MediaStore.Downloads._ID,
MediaStore.Downloads.DISPLAY_NAME,
)
val selectionDownloads = ""
val selectionArgsDownloads = emptyArray<String>()
val sortOrderDownloads = "${MediaStore.Downloads.DISPLAY_NAME} ASC"
context.applicationContext.contentResolver.query(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
projectionDownloads,
selectionDownloads,
selectionArgsDownloads,
sortOrderDownloads
)?.use { cursor ->
Log.i("SeeMedia", "we got cursor! $cursor")
val idColumn = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
val nameColumn = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
while (cursor.moveToNext()) { //Here return false
Log.i("SeeMedia", "Move to next!")
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
Log.i("SeeMedia", "ID = $id AND NAME= $name")
}
}
My app contains the 2 permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
There are 3 questions that I'm wondering after this:
Question 1: Do I need other permissions for the purpose of reading files?
Question 2: What am I missing on this MediaStore query? Why it doesn't provide me the files of download?
Question 3: What if I want to query on other unknown files/directories, for example a folder inside Downloads which there is another folder and so on? "Downloads/Folder1/Folder2" I guess I should use MediaStore.Files, right? So how is the URI for accessing those files?
EDIT 1:
SDK 29 = Android 10
SDK 30 = Android 11
What I want is to query are just files, no image, no audio just files whatever it comes, so for example the path "/" which is root will return a list of Directories:
-Ringtones
-Music
-DCIM
-Download
-Documents
That's good it works with the Environment.getExternalStorageDirectory and the permission MANAGE_EXTERNAL_STORAGE.
But then I want to access to one of those directories and it's empty, for example "DCIM" (which I know it contains some pictures) and <= 28 it retrieves me the pictures with:
Environment.getExternalStorageDirectory()/DCIM
But in >= 29 it returns an empty, how could I get access to those files with the classic methods or MediaStore?
If I'm wrong in something just tell me because right now my head is filled with a lot of mixed stuff since I've watched a lot of videos and documentation and questions and I'm just trying to understand some critical points.
Here's a simple code that I finally could query the root "/" with MediaStore in case you want to see it, but don't know how to query other paths and retrieve me their files:
val projection = arrayOf(
MediaStore.Files.FileColumns.DISPLAY_NAME
)
context.applicationContext.contentResolver.query(
MediaStore.Files.getContentUri("external"),
projection,
null,
null,
null
)?.use { cursor ->
Log.i("SeeMedia", "we got cursor! $cursor")
val nameColumn =
cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
while(cursor.moveToNext()){
Log.i("SeeMedia", "Move to next!")
val name = cursor.getString(nameColumn)
Log.i("SeeMedia", "Name = $name")
}
}

Answering some of your questions:
Do I need other permissions for the purpose of reading files?
No, as it's stated here, you've got all the possible file-related permissions.
What am I missing on this MediaStore query? Why it doesn't provide me the files of download?
Download is a kind of special case. Here at the very bottom of the section it says
In particular, if your app wants to access a file within the
MediaStore.Downloads collection that your app didn't create, you must
use the Storage Access Framework.
And even using SAF you will not be able to access all files in Download directory. So you can access a single file picked by user, but you cannot list the content of the directory.
Generally, as it's stated here, having the MANAGE_EXTERNAL_STORAGE permission granted, you can access all non-restricted files using both MediaStore and file paths. Both ways should give the same result.

Related

Android list all files in drectory from SCHEME_CONTENT uri

I want the get a list of files in same directory of a given single file from uri e.g [picture #1.1]
content://org.owncloud.documents/document/79
and then select file [picture #1.2]
when I select requested fields
val filePath = arrayOf(MediaStore.Images.Media.DATA, DISPLAY_NAME, RELATIVE_PATH)
val c: Cursor = context.contentResolver.query(uri!!, filePath, null, null, null)!!
c.moveToFirst()
I receive [picture #1.3]
within this directory e.g content://org.owncloud.documents/document/79
Solution 1 :
Query it, but something like https://stackoverflow.com/a/65070765/1079990 doesn't work.
I guess I've grab directory from it and then query all files.
This is the main question !
Solution 2 :
Select the directory directly with eg.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, 123)
But here I can't access ownCloud, how to be able to select this as well ?
First if ACTION_OPEN_DOCUMENT_TREE cannot give you the tree then what you want will be impossible.
Second if you used ACTION_OPEN_DOCUMENT you got an uri for a file. And permission to read and or write it.
Even if you could manage to build up an uri for the directory the file was in (quite possible if you choosed a file from device storage) you would not have permission to read it.

Document files (word, pdf, excel,...) are no longer returned from MediaStore.Files.getContentUri("external")

This is what I did and it works in sdk 29, it will return all files regardless of type.
val uri = MediaStore.Files.getContentUri("external")
val projection = arrayOf(
MediaStore.Images.Media.DATA,
MediaStore.Files.FileColumns.MIME_TYPE,
)
val cursor = this.contentResolver.query(
uri,
projection,
null,
null,
MediaStore.Images.Media.DATE_ADDED + " DESC"
)
But after forced to raise targetSdk to at least 30 (Playstore required), it only returns image/video/audio files, is there any way to solve this problem, or is there any other way to read all files (or at least document files)?
I found the problem, since Android 11 we distinguish between read media files and all files, so the app needs to be allowed to read all files
see workaround here:
How to access permission allow management of all file in android studio
After allow this permission, the above code will work!
This should be related to Android.permissions (READ/WRITE_EXTERNAL_STORAGE)

java.lang.IllegalArgumentException Volume external_primary not found in Android 10 devices

Crash : java.lang.IllegalArgumentException Volume external_primary not found
When querying for tracks from media store, I am getting this crash in some Android 10 devices (Most of them are from Xiaomi Mi A2 Lite, Motorola, HMD Global Nokia).
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
Should I be passing a different URI for Android 10 devices ?. (However it is working fine in most of the devices )
Stack trace :
Caused by java.lang.IllegalArgumentException: Volume external_primary not found
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:170)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.query(ContentProviderProxy.java:423)
at android.content.ContentResolver.query(ContentResolver.java:946)
at android.content.ContentResolver.query(ContentResolver.java:881)
at android.content.ContentResolver.query(ContentResolver.java:837)
at com.example.musicplayer.CursorFactory.getAllSongsCursor(CursorFactory.java:164)
Edit : Based on this issue reported, Suspect it could be an issue with sdcard in those devices with Android 10 OS.
Post Android 10, API 28, there are some changes when accessing the media content from other sources.
Google mentioned it in this link https://developer.android.com/training/data-storage/shared/media#storage-volume
You can get more information on how to resolve this issue with more information given by google with the below code:
// Add a specific media item.
ContentResolver resolver = getApplicationContext()
.getContentResolver();
// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
Uri audioCollection = MediaStore.Audio.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL_PRIMARY);
// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
"My Song.mp3");
// Keeps a handle to the new song's URI in case we need to modify it
// later.
Uri myFavoriteSongUri = resolver
.insert(audioCollection, newSongDetails);
Hope this works for you!
Google added a thing called Scoped Storage which changes the File operation a bit. so you should use VOLUME_EXTERNAL/VOLUME_EXTERNAL_PRIMARY instead of EXTERNAL_CONTENT_URI
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
Going with VOLUME_EXTERNAL/VOLUME_EXTERNAL_PRIMARY is the right direction but there is a really rare case that the user stores their audio file on SD card but the phone doesn't recognize it which leads to the crash. This is a device error and there is nothing you can do about it.
From the doc of https://developer.android.com/reference/android/provider/MediaStore#VOLUME_EXTERNAL_PRIMARY
you could use getExternalVolumeNames to check is there any external storage available before making the query.
https://developer.android.com/reference/android/provider/MediaStore#getExternalVolumeNames(android.content.Context)
You need to ask read permission for Android Q and above to access files outside of your application scope.
As per documentation mention as below and here is the documention link
If scoped storage is enabled, the collection shows only the photos, videos, and audio files that your app has created. Most developers won't need to use MediaStore.Files to view media files from other apps, but if you have a specific requirement to do so, you can declare the READ_EXTERNAL_STORAGE permission. It's recommended, however, that you use the MediaStore APIs to open files that your app hasn't created.
Here is the common code to ask permission for all version,
Declare the request launcher object
private val requestReadResult = registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
if (result) {
queryData()
} else {
AlertDialog.Builder(this)
.setMessage(R.string.app_permission_required)
.setPositiveButton(R.string.exit) { _, _ -> finish() }.setCancelable(false).create().show()
}
}
Check permission granted or not before query
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
requestReadResult.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
return
}
queryData()
Now check for the Android SDK version and as per that initialize your Uri
val uri= if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
else
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// Here you will getting all the Images from device.
val mediaCursor = contentResolver.query(uri, null, null, null, null)
As per your media requirement change MediaStore.yourmediatype and use it.
Thanks

API 29 Mediastore Access

My app creates playlists in the android mediastore. All is well for api's including 28 however, api 29 seems to require additional permissions.
Inserting a new playlist name and id works without issue. When it comes to inserting track id and play order, an access permission exception is thrown.
In verifying the Uri, i found that when resolver.insert for API 29 the exception error is:
java.lang.SecurityException: myapp_name has no access to content://media/external_primary/audio/media/146
The code:
Uri exturi = MediaStore.Audio.Playlists.Members.getContentUri("external", playlist_id);
// exturi : content://media/external/audio/playlists/227/members
// values : audio_id=146 play_order=0
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, play_order);
values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audio_id);
try {
resolver.insert(exturi, values);
} catch (Exception e) {
e.printStackTrace();
}
Strange thing is that although inserting a new playlist into Mediastore works but adding tracks (track_id, play order) gives an access permission error
How to resolve this exception error for API 29?
Update Feb 2021:
a small step forward, I am pretty sure I need to get Documenturi for the original uri but still gives me the access error. So the issue does not lie with accessing the tracks but with the uri itself.
doc_uri = MediaStore.getDocumentUri(context,playlist_members_uri);
java.lang.SecurityException: com.flyingdutchman.newplaylistmanager has no access to content://media/external/audio/playlists/130/members
I think this is an Android 10 bug, so I've filed a report here: https://issuetracker.google.com/issues/147619577 (includes instructions for an emulator test case to reproduce it if that interests you). Please consider starring it to let the Android team know that it affects you.
From what I can tell, it only affects files on 'external' storage, like sdcards mounted on /storage/XXXX-XXXX
In the meantime, the only fix that some of my users were able to successfully apply is to move their music files to the internal storage (reboot and wait for the media scan to finish to be sure that MediaStore is up-to-date).
in my further research for the answer, I came across this;
All about the media database (Mediastore) with android 11
Create playlist with uri "MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI", and the date row in external.db for the playlist is:
_id
_display_name
volume_name
308
New playlist.m3u
external_primary
The playlist's volume name is "external_primary".
2.
Music file is under flash card
Music file's id in external.db is 278
The volume name of flash card is "1EDD-DDE0"
When add this music file to playlist, got below exception:
Exception message: java.lang.SecurityException: has no access to content://media/external_primary/audio/media/278
If I create playlist with uri MediaStore.Audio.Playlists.getContentUri("1edd-dde0"), then music can be successfully added to the playlist.
It seems that the reason is the mismatch of volume name between playlist and the music file to be added. Only when playlist's volume name is same to music file's, inserting operation can be complete.
Update for android 11.
Worth noting that the media database has moved from
/data/data/com.android.providers.media
to
/data/data/com.google.android.providers.media.module
also the structures have changes significantly
and
I came across the same issue. As the MediaProvider changes to Google's MediaProvider, the Scoped Storage feature is activated. When you try to modify a playlist file, but it's not created by your app (or it did be created by your app, but after OTA to new Android version, which changes to use Google's MediaProvider, it scans your playlist file and put a record to its database, but leaves the owner_package_name colume empty, it's a new colume, the old MediaProvider database has no owner_package_name colume, so no one could tell this playlist file was created by you), you will get a SecurityException says you have no access to this file.
You can check if the playlist file was owned by your app before doing the 'insert' operation:
Uri uri = MediaStore.Audio.Playlists.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
String[] projection = new String[] {
MediaStore.Audio.Playlists._ID,
MediaStore.Audio.Playlists.NAME,
MediaStore.Audio.Playlists.OWNER_PACKAGE_NAME,
};
String where = MediaStore.Audio.Playlists._ID + "=?";
String[] args = new String[] {playlistFileId};
Cursor cursor = resolver.query(uri, projection, where, args, null);
if (cursor != null) {
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
String ownerPkg = cursor.getString(
cursor.getColumnIndex(MediaStore.Audio.Playlists.OWNER_PACKAGE_NAME));
// print ownerPkg here
}
}
If the owner package name of this playlist file is empty or other app's package name, that you probably have no access to write this playlist file due to the scoped storage feature limit.
According to this document, we can consider using MediaStore.createWriteRequest() method to prompt user to grant write permission to playlist file for our own app, but this request only available to certain kind of files, like images, audios, videos etc, but not for some other kinds like playlist files which ends in .m3u suffix.
Also, according to this, when you try to operate some image or audio files that's not created by your app in public storage, you will get a RecoverableSecurityException and you can use this exception to prompt user to get user consent to modify the file, but for playlist kind files, you will just get SecurityException instead of RecoverableSecurityException.
So the result is, you may never be able to access to that playlist file again, you can not modify it, and you can not delete it too. My solution is just create a new playlist file, so it's owned by my app, now I finally have full access to it. You may need to migrate your old playlist data to the new one.
AND FINALLY I FIND THIS
MediaStore.Audio.Playlists
This class was deprecated in API level 31.
Android playlists are now deprecated. We (Google) will keep the current functionality for compatibility resons, but we will no longer take feature request. We do not advise adding new usages of Android Playlists. M3U files can be used as an alternative.
In conclusion, no longer a relevant post
I have implemented the SAF so do not use scopedStorage and have access once the user accepts.
The fact that I can insert new playlist entries clearly shows access to MediaStore, I can also delete these. However trying to add tracks to these playlists does not work for api29. Inserting/deleting a new playlist does not involve any files located on internal or external sdcards as it is simply adding values.
the permissions for both internal and external sdcard:
2020-07-12 14:39:04.435 11858-11858/com.flyingdutchman.newplaylistmanager E/onCreate:: uriPermission: UriPermission {uri=content://com.android.externalstorage.documents/tree/17F5-240A%3A, modeFlags=3, persistedTime=1594551961263}
2020-07-12 14:39:04.435 11858-11858/com.flyingdutchman.newplaylistmanager E/onCreate:: uriPermission: UriPermission {uri=content://com.android.externalstorage.documents/tree/primary%3A, modeFlags=3, persistedTime=1594551926876}
The question now becomes
How do I ensure saf permissions are recognised by the resolver.insert method when inserting/modify tracks into the Media database
Update May 2020
Stepping through the resolver code with debug F7
Scenario 1 results in permission error (incorrect MediaStore.VOLUME_EXTERNAL).
playlist_uri = MediaStore.Audio.Playlists.getContentUri(MediaStore.VOLUME_EXTERNAL);
playlist_members_uri = MediaStore.Audio.Playlists.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
.buildUpon()
.appendEncodedPath(Long.toString(playlist_id))
.appendEncodedPath("members")
.build();
acquireProvider(mContext, auth); = media
Uri createdRow = provider.insert(mPackageName, mAttributionTag, url, values, extras); = null
mPackageName=com.flyingdutchman.newplaylistmanager
mAttributionTag=null
values[0] = 206
values[1]=69
values[2]=1
extras=null
DatabaseUtils.java
public static final void readExceptionFromParcel(Parcel reply) {
int code = reply.readExceptionCode();
if (code == 0) return;
String msg = reply.readString();
DatabaseUtils.readExceptionFromParcel(reply, msg, code);
}
msg = com.flyingdutchman.newplaylistmanager has no access to content://media/external_primary/audio/playlists/206
Scenario 2 results in NO permission error BUT no tracks added to audio_playlists_map table.
playlist_uri = MediaStore.Audio.Playlists.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
url=content://media/external_primary/audio/playlists/206/members

Media scanner for secondary storage on Android Q

With the newer Android Q many things changed, especially with scoped storage and gradual deprecation of file:/// URIs. The problem is the lack of documentation on how to handle media files correctly on Android Q devices.
I have a media file (audio) management application and I could not find yet a reliable way to tell to the OS that I performed a change to a file so that it can update its MediaStore record.
Option #1: MediaScannerService
MediaScannerConnection.scanFile(context, new String[]{ filePath }, new String[]{"audio/*"}, new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(String s, Uri uri) {
}
});
Works with file:// URIs from primary storage
Not works with file:// URIs from secondary storage (such as removable storage)
Not works with any content:// URI
Option #2: broadcast
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
Not working at all
Soon deprecated
Option #3: manual MediaStore insertion
AudioFileContentValues are some column values from MediaStore.Audio.AudioColumns.
Old method based on file:// URI:
Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
MediaStore.Audio.Media.getContentUriForPath is deprecated
Still not working
Newer method based on what I could put together from documentation:
Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);
Where correctVolume would be external from primary storage, while it would be something like 0000-0000 for secondary storage, depending on where the file is located.
Insertion returns a content URI such as content://media/external/audio/media/125 but then no record is persisted inside MediaStore for files located in primary storage
Insertion fails with no URI returned and no record in MediaStore
These are more or less all the methods available in previous Android versions but none of them now allow me to notify the system that I changed some audio file metadata and to get Android to update MediaStore records. Event though option #1 is partially working, this could never be a valuable solution because it's clearly not supporting content URIs.
Is there any reliable way to trigger media scan on Android Q, despite where the file is located? We shouldn't even care about file location, according to Google, since we will soon only use content URIs. MediaStore has always been a little frustrating in my opinion, but now the situation is pretty worse.
I'm also currently struggling with that.
I think what you want to do you cannot do any longer once you are on Android Q, because you are not allowed to access the Music directory on Q. You are only allowed to create and access files in directories you created. You did not create the music directory.
Now every change to the Media has to happen threw the MediaStore. So you insert your Music file beforehand and then get an outputstream from the MediaStore to write to it. All the changes on Q on Media should be done threw the MediaStore hence you informing the MediaStore of changes cannot even occur anymore, because you never directly access the File.
This has one giant caviat in that all the new things in MediaStore that make that possible do not exist in older versions of Android. So I do currently believe that you will need to implement everything twice, sadly. At least if you want to actively influences where your music is saved to that is.
Those two MediaStore columns are new in Q and do not exist before Q, witch you'll probably need to use in Q
MediaStore.Audio.Media.RELATIVE_PATH with that you can influence the path where it's saved. So I put "Music/MyAppName/MyLibraryName" there and that will end up saving "song.mp3" into "Music/MyAppName/MyLibraryName/song.mp3"
MediaStore.Audio.Media.IS_PENDING this you should be setting to 1 while the song is still being written and then afterwards you can update it to 0.
I've also now started to implement things twice with if checks for Android versions. It's annoying. I don't want to do it. But it seems like that's the only way.
I'm just gonna put a bit of code here on how I managed inserting music on Android.Q and below. It's not perfect. I have to specify the MIME type for Q, because flacs would now become .flac.mp3 somehow, because it does not quite seem to get that.
So, anyways this is a part that I have updated already to work with Q and before, it downloads a Music file from a music player on my NAS. The app is written in kotlin, not sure if that's a problem for you.
override fun execute(library : Library, remoteApi: RemoteApi, ctx: Context) : Boolean {
var success = false
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues().apply {
put(MediaStore.Audio.Media.RELATIVE_PATH, library.rootFolderRelativePath)
put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename())
put(MediaStore.Audio.Media.IS_PENDING, 1)
}
val collection = MediaStore.Audio.Media
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uri = ctx.contentResolver.insert(collection, values)
ctx.contentResolver.openOutputStream(uri!!).use {
success = remoteApi.downloadMusic(remoteLibraryEntry, it!!)
}
if(success) {
values.clear()
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
ctx.contentResolver.update(uri, values, null, null)
} else {
ctx.contentResolver.delete(uri, null, null)
}
} else {
val file = File("${library.rootFolderPublicDirectory}/${remoteLibraryEntry.getFilename()}")
if(file.exists()) file.delete()
success = remoteApi.downloadMusic(remoteLibraryEntry, file.outputStream())
if (success) {
MediaScannerConnection.scanFile(ctx, arrayOf(file.path), arrayOf("audio/*")) { _, uri ->
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
}
}
}
return success
}
And the MediaStoreHelper Method being this here
fun getSongId(uri : Uri) : Long {
val cursor = resolver.query(uri, arrayOf(Media._ID), null, null, null)
return if(cursor != null && cursor.moveToNext()) {
val idIndex = cursor.getColumnIndex(Media._ID)
val id = cursor.getLong(idIndex)
cursor.close()
id
} else {
cursor?.close()
-1
}
}
One thing when you do not specify the MIME type it seems to assume mp3 is the MIME type. So .flac files would get saved as name.flac.mp3, because it adds the mp3 file type if there is none and it thinks it's a mp3. It does not add another .mp3 for mp3 files. I don't currently have the MIME type anywhere... so I'm gonna go ahead and do this now, I guess.
There is also a helpful google IO talk about scoped/shared storage https://youtu.be/3EtBw5s9iRY
That probably won't answer all of your questions. It sure enough didn't for me. But it was a helpful start to have a rough idea what they even did change to begin with.
For deleting and updating files its kinda the same on Q if you call delete on a mediastore entry, the file will be deleted. Before, Q you have to manually delete the file also. But if you do that on Q your app will crash. So again you have to check wether or not youre on Q or an older version of android and take appropriate actions.

Categories

Resources