Android Question [Solved - Use FileProvider on File.DirRootExternal] Help needed opening KML file

agraham

Expert
Licensed User
Longtime User
I am trying to transfer a program that I wrote in the DroidScript IDE, that uses JavaScript, to B4A as ultimately B4A should give me more capability although at the cost of more complexity.

In B4A I can successfully write a kml file to the Download folder 'File.DirRootExternal & "/Download/' and when I click on it in Ghost Commander I get a choice of the installed apps that can open it. I want the user to be able to select from Google Earth or the AlpineQuest mapping app and both are shown from Ghost Commander and both can open the file when I select it.

In DroidScript, which is fully updated to deal withe ramifications of SAF and API 26, I can get the same choice of apps to pop up and successfully select by invoking
B4X:
app.OpenFile(filepath,"application/vnd.google-earth.kml+xml", "Choose a progam");

I have tried totally unsuccessfully to replicate this in B4A using
B4X:
Dim intent1 As Intent
intent1.Initialize(intent1.ACTION_VIEW, "file://" & File.Combine(path,KMLname & ".kml"))
intent1.SetType("application/vnd.google-earth.kml+xml")
StartActivity(intent1)
I never really got the hang of intents and this just gives me a FileUriExposedException error.

I put this together from Erel's FileProvider class example although I don't understand the manifest entry
CreateResource(xml, provider_paths, <files-path name="name" path="shared" />)
B4X:
Dim path As String = File.DirRootExternal & "/Download/"
Dim intent1 As Intent
intent1.Initialize(intent1.ACTION_VIEW, Starter.Provider.GetFileUri(File.Combine(path,KMLname & ".kml")))
intent1.SetType("application/vnd.google-earth.kml+xml")
StartActivity(intent1)
which shows the popup I expected but when I select Google Earth it opens then closes immediately and when I select AlpineQuest it gives me the error

SecurityException(Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{...}(pid=32279, uid=10099) that is not exported from UID 10152)[/code]

Yet tapping on that same file in Ghost Commander will open it successfully in both Google Earth and AlpineQuest. Any ideas?
 
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

agraham

Expert
Licensed User
Longtime User
After several hours of hacking the FileProvider I have a solution that works perfectly for Google Earth and works but throws a briefly displayed non-fatal error in AlpineQuest. This is probably an AlpineQuest problem as I hadn't noticed but opening the same file in Ghost Commander briefly shows the error as well.

First you need to tell FileProvider that you want to share the external storage. Although the documentation is not explicit on this, seeming to imply that you can only specify folders individually it appears that you once you have shared a folder all its files and sub-folders are shared as well. So to share the entire external storage you need only to specify the root of the external storage. You need to add this to the manifest, it can't be done programmatically.

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,
   <external-path name="root" path="" />
)

To use it you need to set Provider.SharedFolder to be the same as the one in the manifest. Then you can generate a content URI specifying the sub-folder and file you want to open, set the intent type and most importantly set the flags to allow read permission.
B4X:
    Starter.Provider.SharedFolder = File.DirRootExternal
   Dim intent1 As Intent
   intent1.Initialize(intent1.ACTION_VIEW, Starter.Provider.GetFileUri("Download/" & KMLname & ".kml"))
   intent1.SetType("application/vnd.google-earth.kml+xml")
   intent1.Flags = 1 'FLAG_GRANT_READ_URI_PERMISSION
   StartActivity(intent1)
Voila!

Tomorrow's question is how to do the same for images on an external SD card.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
same for images on an external SD card
it is more complicated.

Starting from Android 4.4 it is no longer possible to directly access external storages.
The only way to access these storages is through the Storage Access Framework (SAF), which is a quite complex and under-documented framework.

The ExternalStorage class makes it simpler to work with SAF.

https://www.b4x.com/android/forum/threads/externalstorage-access-sd-cards-and-usb-sticks.90238/
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
I can't immediately see how to translate a path and filename into an ExternalFile object
if you know the path you can use Listfiles on the storage to get ExternalFile Objects.
B4X:
    For Each f As ExternalFile In Storage.ListFiles(folder)
        If f.IsFolder Then
            ListView1.AddSingleLine2($"[${f.Name}]"$, f)
        Else If IsImageFile(f.Name) Then
            Dim cs As CSBuilder
            cs.Initialize.Append(f.Name).Append(" ").Color(0xFF00EEFF).Append("(click)").PopAll
            ListView1.AddSingleLine2(cs, f)
        Else
            ListView1.AddSingleLine2(f.Name, f)
        End If
    Next
 
Upvote 0
Top