Android Question Open file from Uri

Discussion in 'Android Questions' started by warwound, May 2, 2019.

  1. warwound

    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:
    Code:
    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:
    FYI app 1 is a 'file provider', it's manifest contains:
    Code:
    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:
    Code:
    'Returns the file uri.
    Sub GetFileUri (Dir As String, FileName As StringAs 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).
     
  2. Erel

    Erel Administrator Staff Member Licensed User

    You need to add and request the storage permission. This is required for file uris.
     
  3. warwound

    warwound Expert Licensed User

    Perfect, everything now works as desired.
     
  4. warwound

    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:
    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:
    From BroadcastIntent project, FileProvider module:
    Code:
    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:
    Code:
    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:
    Must i adjust the path defined in provider_paths.xml?
    Or does this not apply as UseFileProvider is always False?
     

    Attached Files:

  5. DonManfred

    DonManfred Expert Licensed User

    The point is probably that appB can not access DirInternal of AppA.
    The file must be copied to the fileprovider path and this path should be used to share the file.

    I´ll check your examples later today (@work as of now)
     
  6. Erel

    Erel Administrator Staff Member Licensed User

    Note that you don't need to have an Activity_PermissionResult sub. It is simple to use Wait For.

    Don't modify anything. What is the output of Log(FileUri) in Service_Start of DownloadService (on an Android 7+ device)?
     
  7. warwound

    warwound Expert Licensed User

    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...
     
  8. Erel

    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:
    Code:
    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:
    Code:
    File.ReadString("ContentDir", UriHere)
     
  9. warwound

    warwound Expert Licensed User

    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!
     
  10. Erel

    Erel Administrator Staff Member Licensed User

  11. warwound

    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:
    Code:
    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 StringAs 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:
    Thats the project that listens for the broadcast intent:
    Code:
    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:
    The exception is the same on API 19 and API 23.
     
  12. warwound

    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:
    Code:
    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:
    Code:
    Public Sub GetFileUri (FileName As StringAs 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!!!
     
    DonManfred and José J. Aguilar like this.
  13. Erel

    Erel Administrator Staff Member Licensed User

    It could be simpler.

    Try it with:
    Code:
    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
     
  14. warwound

    warwound Expert Licensed User

    Excellent - this code works perfectly.
    I've attached the 2 working projects just in case anyone is interested.
    Thumbs up to the forum!
     

    Attached Files:

    Last edited: May 7, 2019
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice