1

TL;DR: I get the following error when I try to read a file (Uri) in a service I get the following error:

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider

What I am trying to do:

a) read an audio file at specific time

b) have the option to stop the Media Player using the app interface (stopService())

c) make sure that even if the app is closed, the audio still gets played


How I am trying to do it:

  1. Select an audio file using ACTION_GET_CONTENT in Main Activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
     { result ->if (result.resultCode == RESULT_OK) {
                    val data: Intent? = result.data
                    fileUri = data?.data!!
                }
            }
    
    val myIintent = Intent()
                    .setType("audio/*")
                    .setAction(Intent.ACTION_GET_CONTENT)

    resultLauncher.launch(myIntent)
  1. Set an alarm using a pending intent AlarmManager, and pass the Uri (as a string) using extras to the AlarmManager

     val alarmIntent = Intent(context,AlarmSetter::class.java)
     alarmIntent.action = "SET_ALARM"
     alarmIntent.putExtra("audio_path",fileUri.toString())
    
     alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
     var pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0)
    
     alarmManager.setAndAllowWhileIdle(
         AlarmManager.RTC_WAKEUP,
         calendar.timeInMillis,
         pendingIntent)
    
  2. Call the service in the receiver and pass the Uri to the service in the intent override fun onReceive(context: Context?, intent: Intent?) {

     val filePath = intent?.getStringExtra("audio_path")
     val musicIntent = Intent(context, MusicService::class.java)
     musicIntent.putExtra("audio_path",filePath)
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
     context?.startForegroundService(musicIntent)}
     else{
         context?.startService(musicIntent)
     }
    
  3. Read the file in the service

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

       ...
       notification()
       ...

       val mp = MediaPlayer.create(this, Uri.parse(intent?.getStringExtra("audio_path")))

       ...

Problem:

When the App is still running, the service sends the notification is sent and the audio file is read by the media player.

However, when the app is killed/exited, the notification is sent but the file can't be read because the service does not have permission to open it.

nayriz
  • 353
  • 1
  • 17

2 Answers2

0

Replace your putExtra() calls with setData() calls and add addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) to each of those outbound Intent objects. And, replace your getStringExtra() calls with getData() calls.

This will pass your Uri as an actual Uri in the "data" facet of the Intent, and it will pass along the read permission as you traverse process and component boundaries.

See this blog post for more.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • This is the correct answer, however it seems like you ALSO need to add the FLAG_GRANT_PERSISTABLE_URI_PERMISSION flag. – nayriz Oct 28 '21 at 13:30
  • Also, it seems like you only need to set permission flags in the receiver for the intent to the service, you don't need to set them in the (pending) intent from the Activity to the receiver. – nayriz Oct 28 '21 at 13:39
  • @nayriz: "you ALSO need to add the FLAG_GRANT_PERSISTABLE_URI_PERMISSION flag" -- AFAIK that would only be necessary if you are calling `takePersistableUriPermission()` in the recipient, rather than in the provider of the `Uri`. Personally, if I want persistable permission, I try to request that first thing before doing much else with the `Uri`. The `PendingIntent` might automatically be adding those flags on some versions of Android, but you should make sure that you test that on all versions that you support. – CommonsWare Oct 28 '21 at 13:42
  • Interesting. I experimented with it and for me it doesn't work if it's not there the first time I use the app. – nayriz Oct 28 '21 at 13:46
  • WARNING: the solution seems to work in SDK 26 but not in SDK 25. When adding the flags, startService crashes in the receiver. – nayriz Oct 29 '21 at 04:31
0

Based on CommonsWare's answer, and one of his other answers, this is what worked for me:

In step 1) set the intent action to ACTION_OPEN_DOCUMENT instead of ACTION_GET_CONTENT, and call takePersistableUriPermission()

val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
         { result ->if (result.resultCode == RESULT_OK) {
                        val data: Intent? = result.data
                        fileUri = data?.data!!
                        this.contentResolver.takePersistableUriPermission(
    filePath, FLAG_GRANT_READ_URI_PERMISSION);
                    }
                }
    
val myIintent = Intent()
                .setType("audio/*")
                .setAction(Intent.ACTION_OPEN_DOCUMENT)
    
resultLauncher.launch(myIntent)

In step 2) instead of

alarmIntent.putExtra("audio_path",fileUri.toString())

do

alarmIntent.data = fileUri

In step 3) do:

val fileUri = intent?.data
val musicIntent = Intent(context, MusicService::class.java)

musicIntent.data = fileUri
nayriz
  • 353
  • 1
  • 17