Android Question Open file from Uri

warwound

Expert
Licensed User
I am developing 2 apps:

App #1 will contain some bittorrent .torrent files in it's DirInternal.
App #2 is a bittorrent client that needs to open one or more of the other app's .torrent files.

In app #1 i'm creating an Intent with the Uri of the .torrent file as it's data and then broadcasting this Intent.
App #2 receives the broadcast Intent and now i need to open the .torrent file.
This is the Service in app #2 that receives the broadcast:
B4X:
Sub Service_Start (StartingIntent As Intent)
  
   If StartingIntent.Action=ACTION_DOWNLOAD_TORRENT Then
       Log("broadcast action found")
       
       Dim JavaObject1 As JavaObject = StartingIntent
       
       Dim Data As Object=JavaObject1.RunMethod("getData", Null)
       Log("Data="&Data)
       
       Dim FileUri As Uri=JavaObject1.RunMethod("getData", Null)
       Log("FileUri="&FileUri)
       
   Else
       Log("broadcast action not found")
   End If
   
End Sub
The log shows:
broadcast action found
Data=file:///data/data/b4a.example.deleteme/files/torrent-files/myfile.torrent
FileUri=(StringUri) file:///data/data/b4a.example.deleteme/files/torrent-files/myfile.torrent
FYI app 1 is a 'file provider', it's manifest contains:
B4X:
AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <files-path name="name" path="torrent-files" />
)
And the code used to create and broadcast the Intent:
B4X:
'Returns the file uri.
Sub GetFileUri (Dir As String, FileName As String) As Object
   
   If UseFileProvider = False Then
       Dim uri As JavaObject
       Return uri.InitializeStatic("android.net.Uri").RunMethod("parse", Array("file://" & File.Combine(Dir, FileName)))
   Else
       Dim f As JavaObject
       f.InitializeNewInstance("java.io.File", Array(Dir, FileName))
       Dim fp As JavaObject
       Dim context As JavaObject
       context.InitializeContext
       fp.InitializeStatic("android.support.v4.content.FileProvider")
       Return fp.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", f))
   End If
End Sub

Sub Go
   Dim Phone1 As Phone
   If Phone1.SdkVersion >= 24 Then
       UseFileProvider = True
   Else
       UseFileProvider = False
   End If
   Log($"Using FileProvider? ${UseFileProvider}"$)
   
   Dim TorrentFolder As String=File.Combine(File.DirInternal, TORRENT_FILE_FOLDERNAME)
   File.MakeDir("", TorrentFolder)
   
   File.WriteString(TorrentFolder, "myfile.torrent", "This is a text file...")
   
   Dim Intent1 As Intent
   Intent1.Initialize(ACTION_DOWNLOAD_TORRENT, "")
   Intent1.PutExtra(EXTRA_ABC, "this is an extra string")
   SetFileUriAsIntentData(Intent1, TorrentFolder, "myfile.torrent")
   Intent1.SetType("application/x-bittorrent")
   
   Dim JavaObject1 As JavaObject
   JavaObject1.InitializeContext
   JavaObject1.RunMethod("sendBroadcast", Array As Object(Intent1))
End Sub

'Replaces the intent Data field with the file uri.
'Resets the type field. Make sure to call Intent.SetType after calling this method
Sub SetFileUriAsIntentData (Intent As Intent, Dir As String, FileName As String)
   Dim jo As JavaObject = Intent
   jo.RunMethod("setData", Array(GetFileUri(Dir, FileName)))
   Intent.Flags = Bit.Or(Intent.Flags, 1) 'FLAG_GRANT_READ_URI_PERMISSION
End Sub
(It's just a modified version of the FileProvider example).


How can app #2 open the .torrent file in app #1's DirInternal?
Stripping "file://" from the uri string value and trying to access "/data/data/b4a.example.deleteme/files/torrent-files/myfile.torrent" fails with a permission error (as to be expected).
 

warwound

Expert
Licensed User
Hmm it works but only when the file i share is located on external storage.
When i try to share the Uri of a file located at File.DirInternal i get an exception:
java.io.FileNotFoundException: /data/user/0/b4a.example.broadcastintent/files/shared/my_torrent.torrent: open failed: EACCES (Permission denied)
I've attached two projects: BroadcastIntent and ReceiveIntent.
Compile and install both projects, click 'Broadcast' button in BroadcastIntent and the log shows that ReceiveIntent can open and read the contents of my shared file:
DownloadService_Start
broadcast action found
this is my torrent file, hello world!!
** Service (downloadservice) Destroy **
From BroadcastIntent project, FileProvider module:
B4X:
Public Sub Initialize
   Dim p As Phone
   If p.SdkVersion >= 24 Or File.ExternalWritable = False Then
       UseFileProvider = True
       SharedFolder = File.Combine(File.DirInternal, "shared")
   Else
       UseFileProvider = False
       SharedFolder = Starter.RuntimePermissions1.GetSafeDirDefaultExternal("shared")
   End If
   
   If Not(File.Exists("", SharedFolder)) Then
       File.MakeDir("", SharedFolder)
   End If
   
   Log($"Using FileProvider? ${UseFileProvider}"$)
   Log($"SharedFolder: ${SharedFolder}"$)
End Sub
Note that I am developing for (custom) tablets that run android API 19 or API 23 so 'UseFileProvider' is always False.

Now if i change the shared folder location to DirInternal:
B4X:
Public Sub Initialize
   Dim p As Phone
   If p.SdkVersion >= 24 Or File.ExternalWritable = False Then
       UseFileProvider = True
       SharedFolder = File.Combine(File.DirInternal, "shared")
   Else
       UseFileProvider = False
       SharedFolder = File.Combine(File.DirInternal, "shared") ' change shared folder location
   End If
   
   If Not(File.Exists("", SharedFolder)) Then
       File.MakeDir("", SharedFolder)
   End If
   
   Log($"Using FileProvider? ${UseFileProvider}"$)
   Log($"SharedFolder: ${SharedFolder}"$)
End Sub
ReceiveIntent no longer has permission to open and read the shared file:
DownloadService_Start
broadcast action found
downloadservice_service_start (java line: 172)
java.io.FileNotFoundException: /data/user/0/b4a.example.broadcastintent/files/shared/my_torrent.torrent: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:452)
at java.io.FileInputStream.<init>(FileInputStream.java:76)
at anywheresoftware.b4a.objects.streams.File.OpenInput(File.java:214)
at anywheresoftware.b4a.objects.streams.File.ReadString(File.java:276)
at b4a.example.receiveintent.downloadservice._service_start(downloadservice.java:172)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:176)
at b4a.example.receiveintent.downloadservice.handleStart(downloadservice.java:100)
at b4a.example.receiveintent.downloadservice.access$000(downloadservice.java:8)
at b4a.example.receiveintent.downloadservice$1.run(downloadservice.java:71)
at anywheresoftware.b4a.objects.ServiceHelper$StarterHelper.onStartCommand(ServiceHelper.java:235)
at b4a.example.receiveintent.downloadservice.onStartCommand(downloadservice.java:69)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3010)
at android.app.ActivityThread.-wrap17(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1442)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:742)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:632)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Posix.open(Native Method)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
at libcore.io.IoBridge.open(IoBridge.java:438)
... 21 more
Must i adjust the path defined in provider_paths.xml?
Or does this not apply as UseFileProvider is always False?
 

Attachments

warwound

Expert
Licensed User
The file must be copied to the fileprovider path and this path should be used to share the file.
My project requires that all .torrent files (files which will be shared) be stored on DirInternal.
External storage is not an option as my .torrent will be downloaded to the root of external storage (ie /mnt/sdcard), so cannot overwrite itself.

The solution must be to change the 'shared path' defined in provider_paths.xml?
But remember that my app is for custom tablets that run API 19 or API 23, so none of the FileProvider stuff actually gets used...
 

Erel

Administrator
Staff member
Licensed User
To improve compatibility, FileProvider is only really used on Android 7+ devices or when the external storage is not available.

If you want to share files from File.DirInternal on older versions then change the Initialize sub of FileProvider to:
B4X:
Public Sub Initialize
       UseFileProvider = True
       SharedFolder = File.Combine(File.DirInternal, "shared")
       File.MakeDir("", SharedFolder)
   Log($"Using FileProvider? ${UseFileProvider}"$)
End Sub
Note that the received Uri will not be a file uri. You need to load it like this:
B4X:
File.ReadString("ContentDir", UriHere)
 

warwound

Expert
Licensed User
What is the output of Log(FileUri) in Service_Start of DownloadService (on an Android 7+ device)?
I just installed both BroadcastIntent and ReceiveIntent on my Galaxy S7 running Android 8.0.0 and ReceiveIntent does not even receive the broadcast intent!
 

warwound

Expert
Licensed User
That tutorial doesn't look like it'll help.
It looks like a way to let other apps request files but not a way to 'push files' to another app.

I've modified my projects.
My FileProvider is now:
B4X:
Sub Class_Globals
   Public SharedFolder As String
End Sub

Public Sub Initialize
   SharedFolder = File.Combine(File.DirInternal, "shared")
   File.MakeDir("", SharedFolder)
End Sub

Public Sub GetFileUri (FileName As String) As Object
   Dim FileToShare As JavaObject
   FileToShare.InitializeNewInstance("java.io.File", Array(SharedFolder, FileName))
   Dim fp As JavaObject
   Dim context As JavaObject
   context.InitializeContext
   fp.InitializeStatic("android.support.v4.content.FileProvider")
   Return fp.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", FileToShare))
End Sub

Public Sub SetFileUriAsIntentData (Intent1 As Intent, FileName As String, MimeType As String)
   Dim jo As JavaObject = Intent1
   jo.RunMethod("setDataAndType", Array As Object(GetFileUri(FileName), MimeType))
   Intent1.Flags = Bit.Or(Intent1.Flags, 1) 'FLAG_GRANT_READ_URI_PERMISSION
End Sub
And the manifest for the project that broadcasts the Intent:
AddApplicationText(
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="$PACKAGE$.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
)
CreateResource(xml, provider_paths,
<files-path name="name" path="shared" />
)
Thats the project that listens for the broadcast intent:
B4X:
Sub Service_Start (StartingIntent As Intent)
   Log("DownloadService_Start")
   If StartingIntent.Action=ACTION_DOWNLOAD_TORRENT Then
       Log("broadcast action found")
       
       Dim JavaObject1 As JavaObject = StartingIntent
       
       Dim ContentUri As Uri=JavaObject1.RunMethod("getData", Null)
       Log("ContentUri="&ContentUri)
       Dim JavaObject2 As JavaObject=ContentUri
       Dim Path As String=JavaObject2.RunMethod("getPath", Null)
       Log("Path="&Path)
       
       Log(File.ReadString("ContentDir", ContentUri))
       
   Else
       Log("broadcast action not found")
   End If
   
   Service.StopAutomaticForeground 'Call this when the background task completes (if there is one)
   StopService(Me)
End Sub
I now get a new exception:
DownloadService_Start
broadcast action found
ContentUri=(HierarchicalUri) content://b4a.example.broadcastintent.provider/name/my_torrent.torrent
Path=/name/my_torrent.torrent
downloadservice_service_start (java line: 176)
java.io.FileNotFoundException: No content provider: (HierarchicalUri) content://b4a.example.broadcastintent.provider/name/my_torrent.torrent
The exception is the same on API 19 and API 23.
 

warwound

Expert
Licensed User
I have found a solution.
I used JavaObject and the native android ContentResolver to get an InputStream from my 'shared content Uri'.
My 'broadcast receiver' code:
B4X:
Sub Service_Start (StartingIntent As Intent)
   Log("DownloadService_Start")
   If StartingIntent.Action=ACTION_DOWNLOAD_TORRENT Then
       Log("broadcast action found")
       
       Dim JavaObject1 As JavaObject = StartingIntent
       
       Dim ContentUri As Uri=JavaObject1.RunMethod("getData", Null)
       Log("ContentUri="&ContentUri)
       Dim JavaObject2 As JavaObject=ContentUri
       Dim Path As String=JavaObject2.RunMethod("getPath", Null)
       Log("Path="&Path)
       
       Dim Context As JavaObject
       Context.InitializeContext
       Dim ContentResolver As JavaObject=Context.RunMethod("getContentResolver", Null)
       Dim InputStream1 As InputStream=ContentResolver.RunMethod("openInputStream", Array As Object(ContentUri))
       
       Dim Bytes(InputStream1.BytesAvailable) As Byte
       InputStream1.ReadBytes(Bytes, 0, InputStream1.BytesAvailable)
       
       Log(BYTE_CONVERTER.StringFromBytes(Bytes, "UTF-8"))
   Else
       Log("broadcast action not found")
   End If
   
   Service.StopAutomaticForeground 'Call this when the background task completes (if there is one)
   StopService(Me)
End Sub
A little tweak was required on the 'broadcast sender' code:
B4X:
Public Sub GetFileUri (FileName As String) As Object
   Dim FileToShare As JavaObject
   FileToShare.InitializeNewInstance("java.io.File", Array(SharedFolder, FileName))
   
   Dim FileUri As Object=fp.RunMethod("getUriForFile", Array(Context, Application.PackageName & ".provider", FileToShare))
   
   Context.RunMethod("grantUriPermission", Array As Object("b4a.example.receiveintent", FileUri, FLAG_GRANT_READ_URI_PERMISSION))
   
   Return FileUri
End Sub
'b4a.example.receiveintent' is my broadcast receiving project and the grantUriPermission method must be called to explicitly allow the project access to the shared Uri.

What a lot of work to simply share a file!!!
 

Erel

Administrator
Staff member
Licensed User
It could be simpler.

Try it with:
B4X:
Sub Service_Start (StartingIntent As Intent)
   Log("DownloadService_Start")
   If StartingIntent.Action=ACTION_DOWNLOAD_TORRENT Then
       Log(File.ReadString("ContentDir", StartingIntent.GetData))
   Else
       Log("broadcast action not found")
   End If
   Service.StopAutomaticForeground 'Call this when the background task completes (if there is one)
   StopService(Me)
End Sub
 

warwound

Expert
Licensed User
It could be simpler.

Try it with:
B4X:
Sub Service_Start (StartingIntent As Intent)
   Log("DownloadService_Start")
   If StartingIntent.Action=ACTION_DOWNLOAD_TORRENT Then
       Log(File.ReadString("ContentDir", StartingIntent.GetData))
   Else
       Log("broadcast action not found")
   End If
   Service.StopAutomaticForeground 'Call this when the background task completes (if there is one)
   StopService(Me)
End Sub
Excellent - this code works perfectly.
I've attached the 2 working projects just in case anyone is interested.
Thumbs up to the forum!
 

Attachments

Last edited:
Top