Android Question Loading a kmz file into Google Earth

Kevin

Well-Known Member
Licensed User
I wrote an app for my employer a while back and it has been working fine. I recently changed the manifest to target API 23 and added support for Runtime permissions and it continued to work as expected.

My employer recently upgraded our company-provided cell phones and we went from the Galaxy S7 to the Galaxy S9. I forget which version of Android the S7 had been on when we switched over but it had obviously been updated several times over the years we had it.

On the S9 now, we are running Android 8.0. Since changing over to the new phones though, one part of the app no longer works.

We have KMZ files that show our pipeline map overlayed in Google Earth. On the new phones, the KMZ files get copied to an external folder properly but Google Earth fails to load them.

I ran across the file provider tutorial and tried getting it to work that way but had no luck. I'm not even sure if that is where the problem lies or not.

Is anyone familiar with how to properly open a KMZ file with Google Earth on the latest version of Android?

B4X:
Sub CopyKMZ (sFN As String) As Boolean
    Dim sFolder As String = File.DirDefaultExternal 'RTPerm.GetSafeDirDefaultExternal ("")
    
    If File.ExternalReadable = True Then
        If File.Exists (sFolder, sFN) = True Then Return True ' Already there
    End If
    
    If File.ExternalWritable = True Then
        Try
            File.Copy (File.DirAssets, sFN, sFolder, sFN)
            Return True
                Catch
                    ToastMessageShow ("Failed to copy " & Chr(34) & sFN & Chr(34) & " to public folder.  Is the phone's storage full?", False)
                    Return False
        End Try
            Else ' Not writable
        ToastMessageShow ("Failed to copy " & Chr(34) & sFN & Chr(34) & " to public folder.  App needs permission to access external folder.", True)
                Return False
    End If
End Sub

Sub ShowAssetsinGE (iArea As Int)

    Dim sAreaFilename As String
    Select Case iArea
        Case 0
            sAreaFilename = "Hammond.kmz"
        Case 1
            sAreaFilename = "WoodRiver.kmz"
        Case 2
            sAreaFilename = "Glenpool.kmz"
        Case 3
            sAreaFilename = "Greenville.kmz"
        Case 4
            sAreaFilename = "Arlington.kmz"
        Case 5
            sAreaFilename = "Houston.kmz"
        Case 6
            sAreaFilename = "PortArthur.kmz"
        Case 7
            Msgbox ("There are no pipeline assets associated with the Tulsa area.", Application.LabelName)
            Return
    End Select
        
    RTPerm.CheckAndRequest (RTPerm.PERMISSION_WRITE_EXTERNAL_STORAGE)
    Wait for Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result Then
        If CopyKMZ (sAreaFilename) = False Then
            Msgbox ("Unable to load the pipeline map into Google Earth because the file could not be copied." & CRLF & CRLF & "Is your device low on space?", "Oops!")
            Return
        End If
        If File.ExternalReadable = False Then
            Msgbox ("Unable to load the pipeline map into Google Earth because the file could not be accessed.", "Oops!")
            Return
        End If
        'Should be good regarding external storage
        Dim iIntent As Intent, sPackage As String
        Dim sFolder As String = File.DirDefaultExternal 'RTPerm.GetSafeDirDefaultExternal ("")
        iIntent.Initialize(iIntent.ACTION_VIEW, "file://" &  sFolder & "/" & sAreaFilename)
        sPackage = "com.google.earth/.EarthActivity"
        iIntent.SetComponent(sPackage)
        Try
            ToastMessageShow ("Loading assets into Google Earth." & CRLF & "Please wait...", True):Sleep(0)
            StartActivity(iIntent)
                Catch
                If Msgbox2 ("Failed to start the Google Earth app." & CRLF & CRLF & "Would you like to try installing it now?", "Uh-oh!", "Yes", "No", "", Null) = DialogResponse.POSITIVE Then InstallApp (0)
        End Try
            Else
            Msgbox ("In order for this app to display pipeline assets in Google Earth, you must grant it permission to access files on your device so that the asset files can be copied to a storage location accessible by other apps.", "Permission required!")
    End If
End Sub
 

DonManfred

Expert
Licensed User
I ran across the file provider tutorial and tried getting it to work that way but had no luck
you still did not understand how it work.
iIntent.Initialize(iIntent.ACTION_VIEW, "file://" & sFolder & "/" & sAreaFilename)
replace the file:... part with the uri from the fileprovider

Copy file
B4X:
    File.Copy(File.DirAssets, sFN, Starter.shared, sFN) ' copy your file here to thepath fileprovider is using
B4X:
iIntent.Initialize(iIntent.ACTION_VIEW, CreateFileProviderUri(Starter.shared, sFN))
B4X:
Sub CreateFileProviderUri (Dir As String, FileName As String) As Object
    Dim FileProvider As JavaObject
    Dim context As JavaObject
    context.InitializeContext
    FileProvider.InitializeStatic("android.support.v4.content.FileProvider")
    Dim f As JavaObject
    f.InitializeNewInstance("java.io.File", Array(Dir, FileName))
    Return FileProvider.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", f))
End Sub
 

Kevin

Well-Known Member
Licensed User
I should have clarified that the code I posted was without the File Provider change. Not sure if it was even the correct path, I elected to post what used to work, hoping someone could suggest what needs to be changed.

Below is using the File Provider option. It results in Google Earth crashing.

B4X:
Sub CopyKMZ (sFN As String) As Boolean
    Dim sFolder As String = RTPerm.GetSafeDirDefaultExternal ("maps") 'File.DirDefaultExternal
    Log ("Copy KMZ to folder: " & sFolder)
    
    If File.ExternalReadable = True Then
        If File.Exists (sFolder, sFN) = True Then Return True ' Already there
    End If
    
    If File.ExternalWritable = True Then
        Try
            File.Copy (File.DirAssets, sFN, sFolder, sFN)
            Return True
                Catch
                    ToastMessageShow ("Failed to copy " & Chr(34) & sFN & Chr(34) & " to public folder.  Is the phone's storage full?", False)
                    Return False
        End Try
            Else ' Not writable
        ToastMessageShow ("Failed to copy " & Chr(34) & sFN & Chr(34) & " to public folder.  App needs permission to access external folder.", True)
                Return False
    End If
End Sub

Sub ShowAssetsinGE (iArea As Int)

    Dim sAreaFilename As String
    Select Case iArea
        Case 0
            sAreaFilename = "hammond.kmz"
        Case 1
            sAreaFilename = "woodriver.kmz"
        Case 2
            sAreaFilename = "glenpool.kmz"
        Case 3
            sAreaFilename = "greenville.kmz"
        Case 4
            sAreaFilename = "arlington.kmz"
        Case 5
            sAreaFilename = "houston.kmz"
        Case 6
            sAreaFilename = "portarthur.kmz"
        Case 7
            Msgbox ("There are no pipeline assets associated with the Tulsa area.", Application.LabelName)
            Return
    End Select
        
'    RTPerm.CheckAndRequest (RTPerm.PERMISSION_WRITE_EXTERNAL_STORAGE)
'    Wait for Activity_PermissionResult (Permission As String, Result As Boolean)
'    If Result Then
        If CopyKMZ (sAreaFilename) = False Then
            Msgbox ("Unable to load the pipeline map into Google Earth because the file could not be copied." & CRLF & CRLF & "Is your device low on space?", "Oops!")
            Return
        End If
        If File.ExternalReadable = False Then
            Msgbox ("Unable to load the pipeline map into Google Earth because the file could not be accessed.", "Oops!")
            Return
        End If
        'Should be good regarding external storage
        Dim iIntent As Intent, sPackage As String
        Dim sFolder As String = RTPerm.GetSafeDirDefaultExternal ("maps") 'File.DirDefaultExternal
'        iIntent.Initialize(iIntent.ACTION_VIEW, "file://" &  sFolder & "/" & sAreaFilename)

'        iIntent.Initialize(iIntent.ACTION_VIEW, "")
'        iIntent.SetType("text/plain") 'it is not related to the file itself.
'        iIntent.PutExtra("android.intent.extra.STREAM",  CreateFileProviderUri(sFolder, sAreaFilename))
'        sPackage = "com.google.earth/.EarthActivity"
'        iIntent.SetComponent(sPackage)
        
    iIntent.Initialize(iIntent.ACTION_VIEW, CreateFileProviderUri(sFolder, sAreaFilename))

        Try
            'ToastMessageShow ("Loading assets into Google Earth." & CRLF & "Please wait...", True):Sleep(0)
            StartActivity(iIntent)
                Catch
                If Msgbox2 ("Failed to start the Google Earth app." & CRLF & CRLF & "Would you like to try installing it now?", "Uh-oh!", "Yes", "No", "", Null) = DialogResponse.POSITIVE Then InstallApp (0)
        End Try
'            Else
'            Msgbox ("In order for this app to display pipeline assets in Google Earth, you must grant it permission to access files on your device so that the asset files can be copied to a storage location accessible by other apps.", "Permission required!")
'    End If
End Sub

Sub CreateFileProviderUri (Dir As String, FileName As String) As Object
    Dim FileProvider As JavaObject
    Dim context As JavaObject
    context.InitializeContext
    FileProvider.InitializeStatic("android.support.v4.content.FileProvider")
    Dim f As JavaObject
    f.InitializeNewInstance("java.io.File", Array(Dir, FileName))
    Return FileProvider.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", f))
End Sub
Manifest:
B4X:
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: http://www.basic4ppc.com/forum/showthread.php?p=78136
' For target, limit to 23 otherwise there doesn't seem to be a way to pass the KMZ files to GE!
AddManifestText(
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
'End of default text.

SetApplicationAttribute(android:hardwareAccelerated, "true")
'SetActivityAttribute(Main, android:theme, "@style/MyFastScrollerTheme")

'New for sharing KML with Google Earth
AddManifestText(<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18" />
)

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-files-path name="name" path="maps" />
)
 

Kevin

Well-Known Member
Licensed User
Well, I'm at a loss here. I don't know why using the File Provider URI crashes the Earth app but it does.

Using the original method (the code in my original post) used to work fine, but something must have changed in Earth. When I pass the file that way, it shows up in "My Places" in the Earth app but the map overlay is not displayed. Even opening the KMZ file from a file explorer app launches Earth but with the same behavior - nothing happens, but it is listed in "My Places". Toggling visibility makes absolutely no difference at all. It wouldn't be the first time that Google broke something that used to work fine.

For all I know, the Earth app crashing when using the File Provider URI may be a bug in Earth. Using the file sharing example that Erel posted works fine in that I can "share" the KMZ file with other apps (such as Email) but Earth isn't listed as a possible app to "share" the file to.
 

Kevin

Well-Known Member
Licensed User
I tried that but it made no difference. They are definitely KMZs though anyway, as they are compressed.
 

Marcob

Member
Licensed User
I fixed my Google Earth crash issue by granting the read URI permission with:

B4X:
iIntent.Flags = 1 'set FLAG_GRANT_READ_URI_PERMISSION
before StartActivity(iIntent)

I hope that helps.
 

agraham

Expert
Licensed User
This is how I pass a KML file to AlpineQuest and Google Earth
B4X:
    Dim kml As String = "<?xml version='1.0' encoding='UTF-8'?>" & CRLF
   kml = kml & "<kml xmlns='http://www.opengis.net/kml/2.2'>" & CRLF
   kml = kml & "  <Document>" & CRLF
   kml = kml & "    <name>" & KMLname & "</name>" & CRLF
   kml = kml & "    <Placemark>" & CRLF
   kml = kml & "      <name>" & KMLname & "</name>" & CRLF
   kml = kml & "      <description>" & KMLgridRef & "</description>" & CRLF
   kml = kml & "      <Point>" & CRLF
   kml = kml & "        <coordinates>" & wgs84longitude & "," & wgs84latitude & ",0 </coordinates>" & CRLF
   kml = kml &  "      </Point>" & CRLF
   kml = kml & "    </Placemark>" & CRLF
   kml = kml & "  </Document>" & CRLF
   kml = kml & "</kml>"
   ' AlpineQuest doesn't seem to like spaces in filenames when opened by FileProvider and throws a permission exception
   Dim Filename As String = KMLname.Replace(" ", "_") & ".kml"
   File.WriteString(path, Filename, kml)

   Provider.SharedFolder = File.DirRootExternal
   Dim intent1 As Intent
   intent1.Initialize(intent1.ACTION_VIEW, Provider.GetFileUri("Download/" & Filename))
   intent1.SetType("application/vnd.google-earth.kml+xml")
   intent1.Flags = 1 'FLAG_GRANT_READ_URI_PERMISSION
   StartActivity(intent1)
 
Top