Android Question Programmatically Installing an APK Using API 24 or Higher

Mahares

Well Known Member
Licensed User
@Erel’s example in post #1 of this thread: https://www.b4x.com/android/forum/threads/version-safe-apk-installation.87667/ works well if I am using the storage of the apk file to install as; File.DirDefaultExternal when the phone API is 24, OS 7. But I really, I mean REALLY want the apk to be on File.DirRootExternal, but of course because of permission, the app cannot be installed with an error of:
File.copy(File.DirAssets, ApkName, Starter.Shared
java.io.FileNotFoundException: /storage/emulated/0/2nd_app.apk (Permission denied)


Is there any way to overcome this limitation and have the apk of the app to be installed reside on DirRootExternal. I prefer to always use RootExternal (easier to manage). I do not like to deal with DefaultExternal folders.
The below code only yields: /storage/emulated/0/Android/data/b4a.example3/files :
B4X:
For Each f As String  In Starter.rp.GetAllSafeDirsExternal("")
       Log(f)
Next
 

Mahares

Well Known Member
Licensed User
Now, I am getting this error on the below 2nd sub:
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
/storage/emulated/0/Android/data/b4a.example3/files
** Activity (main) Resume **
dir:/storage/emulated/0
file name:2nd_app.apk
main_createfileprovideruri (B4A line: 126)
Return FileProvider.RunMethod("getUriForFile", Ar
java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:131)
at b4a.example3.main._createfileprovideruri(main.java:756)
at b4a.example3.main$ResumableSub_SendInstallIntent.resume(main.java:876)
at anywheresoftware.b4a.BA.checkAndRunWaitForEvent(BA.java:240)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:180)
at anywheresoftware.b4a.BA$2.run(BA.java:360)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/2nd_app.apk
at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:711)
at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
... 14 more
B4X:
Private Sub SendInstallIntent
    Dim ApkName As String = "2nd_app.apk"
    File.copy(File.DirAssets, ApkName, File.DirRootExternal, ApkName)   
    Dim i As Intent
    If phone.SdkVersion >= 24 Then   
        Starter.rp.CheckAndRequest(Starter.rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
        If Result Then
            i.Initialize("android.intent.action.INSTALL_PACKAGE", CreateFileProviderUri(File.DirRootExternal, ApkName))
            i.Flags = Bit.Or(i.Flags, 1) 'FLAG_GRANT_READ_URI_PERMISSION
        End If
    Else
        i.Initialize(i.ACTION_VIEW, "file://" & File.Combine(File.DirRootExternal, ApkName))
        i.SetType("application/vnd.android.package-archive")
    End If
    StartActivity(i)
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))   'ERROR HERE
End Sub
 

Mahares

Well Known Member
Licensed User
It will not work with the root folder. FileProvider doesn't support it.
I created a subfolder of the root and tried it and still does not work. What am supposed to do in B4A language to get this working. I could not understand how to handle the link @Erel sent me to in the previous post to make a File.DirRootExternal subfolder work.
 

DonManfred

Expert
Licensed User
I created a subfolder of the root and tried it and still does not work.
Expected if you dont set a path for the Fileprovider in your manifest.

I guess you need to edit (or create another entry in the resource)

B4X:
CreateResource(xml, provider_paths,
   <external-files-path name="name" path="" />
)
 

Mahares

Well Known Member
Licensed User
CreateResource(xml, provider_paths,
<external-files-
path name="name" path="" />
)
I edited the manifest to show this:
B4X:
CreateResource(xml, provider_paths,
   <external-files-path name="PPPapk" path="/" />
PPPapk is the folder that contains the app to install programmatically. But got this error:
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/PPPapk/2nd_app.apk
 

DonManfred

Expert
Licensed User
hmmm... You did considered the notes from Erel

B4X:
CreateResource(xml, provider_paths,
   <external-files-path name="name" path="" />
)
Note that the path is empty as it is relative to RuntimePermission.GetSafeDirDefaultExternal (which is the same as File.DirDefaultExternal but doesn't require the permission).
So i guess you should copy the apk to
B4X:
dim path as string =RuntimePermission.GetSafeDirDefaultExternal("PPPapk")
and then maybe the manifest from your will work. Maybe need a change...

But i´m positive it will work with this path instead of an folder in the real ROOT of the external sd.
 

Mahares

Well Known Member
Licensed User
But i´m positive it will work with this path instead of an folder in the real ROOT of the external sd.
I got it working with File.DefaultExternal, without any problems. But, I wanted to use RootExternal, as I explained in my thread posts. If it is not possible, than, it is a shame. I prefer dealing with RootExternal rather than DefaultExternal. I thought maybe you can force File.RootExternal or one of its subfolders. I thought using Post#2 by @Erel may solve the problem, but it does not.
 

DonManfred

Expert
Licensed User

Mahares

Well Known Member
Licensed User
As I wrote: It will not work with the root folder. FileProvider doesn't support it.
OK, bear with me for a second. What does then this below excerpt telling me from this link you sent me to in post#4:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html#SpecifyFiles

<external-path name="name" path="path" />
Represents files in the root of the external storage area. The root path of this subdirectory is the same as the value returned byEnvironment.getExternalStorageDirectory().
<external-files-path name="name" path="path" />
Represents files in the root of your app's external storage area. The root path of this subdirectory is the same as the value returned byContext#getExternalFilesDir(String) Context.getExternalFilesDir(null).

Are you telling me that File.DirRootExternal is OFF limit and there is no workaround. If so, then is the only way to install a list of apk files programmatically is to save them to File.DirAssets and then copy them to DirInternal or DirDefaultExternal before installing them. Please explain it so I can avoid any followup posts.
 

Erel

Administrator
Staff member
Licensed User
You are correct!
It is possible.

1. Change the manifest editor to:
B4X:
CreateResource(xml, provider_paths,
   <external-path name="name" path="" />
)
2. Remove the version based external storage permission in the manifest.
3. Set Starter.shared to File.DirRootExternal
4. Add this check and request to CheckInstalledRequirements:
B4X:
Starter.rp.CheckAndRequest(Starter.rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
   Wait For Activity_PermissionResult (Permission As String, PResult As Boolean)
   If PResult = False Then
       MsgboxAsync("No permission to access external storage", "")
       Return False
   End If
 

Jmu5667

Well-Known Member
Licensed User
OK, bear with me for a second. What does then this below excerpt telling me from this link you sent me to in post#4:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html#SpecifyFiles

<external-path name="name" path="path" />
Represents files in the root of the external storage area. The root path of this subdirectory is the same as the value returned byEnvironment.getExternalStorageDirectory().
<external-files-path name="name" path="path" />
Represents files in the root of your app's external storage area. The root path of this subdirectory is the same as the value returned byContext#getExternalFilesDir(String) Context.getExternalFilesDir(null).

Are you telling me that File.DirRootExternal is OFF limit and there is no workaround. If so, then is the only way to install a list of apk files programmatically is to save them to File.DirAssets and then copy them to DirInternal or DirDefaultExternal before installing them. Please explain it so I can avoid any followup posts.
Hello

Did you resolve this issue and successfully install APK's ?

Regards

John
 
Top