Programmatically install an apk in Android 7 / api24
Asked Answered
M

9

16

I am trying to get my app to automatically install an apk. This works fine for api<24. But for 24, it is failing. Android has implemented extra security:

For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app. If an intent containing a file URI leaves your app, the app fails with a FileUriExposedException exception.

So I tried this:

    Uri myuri;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
        myuri = Uri.parse("file://"+outapk);
    } else {
        File o = new File(outapk);
        myuri = FileProvider.getUriForFile(con, con.getApplicationContext().getPackageName() + ".provider", o);
    }
    Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(myuri,"application/vnd.android.package-archive");
    con.startActivity(promptInstall);

but get a fatal exception:

com.android.packageinstaller "Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{b42ee8a 6826:com.android.packageinstaller/u0a15} (pid=6826, uid=10015) that is not exported from uid 10066". 

I have export=true in my manifest.

The problem seems to be that packageinstaller cannot use a content:// uri.

Are there any ways to allow an app to progammatically install an apk with api24?

Mizzen answered 4/1, 2017 at 23:1 Comment(0)
E
16

Are there any ways to allow an app to progammatically install an apk with api24?

Add addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) to your promptInstall setup, to grant read access to the content.

I have export=true in my manifest.

Not on your FileProvider, as that would cause your app to crash.

The problem seems to be that packageinstaller cannot use a content:// uri.

No, the problem is that you did not grant permission to the package installer to read from that Uri. Had the package installer been unable to use a content scheme, you would have gotten an ActivityNotFoundException.

Note, though, that it is only with Android 7.0 that the package installer starts supporting content. Earlier versions of Android have to use file.

Egbert answered 5/1, 2017 at 12:36 Comment(6)
Awesome. That did it. Thanks!Mizzen
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) worked for me too.Alexalexa
Hey CommonsWare. we have to use if...else for N and lower apis? there is no other way to do that?Kozhikode
@David: On older devices, use Uri.fromFile(). On newer devices (Android 7.0+), use FileProvider.Egbert
"I have export=true in my manifest." "Not on your FileProvider, as that would cause your app to crash." Can you explain that? Why would having export=true on the FileProvider cause the app to crash? Do you mean because it should be exported instead of export?Hadwyn
@LarsH: No, I mean that, last I checked, FileProvider would throw an exception if you tried exporting it (see these lines).Egbert
P
13

For Oreo, Add permission in AndroidManifast (Otherwise it just silently fails)

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

now add to you'r Manifest

  <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.provider" 
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

in xml directory add...

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." /></paths>

then use these codes where you want.

File directory = Environment.getExternalStoragePublicDirectory("myapp_folder"); 

 File file = new File(directory, "myapp.apk"); // assume refers to "sdcard/myapp_folder/myapp.apk"


    Uri fileUri = Uri.fromFile(file); //for Build.VERSION.SDK_INT <= 24

    if (Build.VERSION.SDK_INT >= 24) {

        fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
    }
    Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
    intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
    intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
    intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //dont forget add this line
    context.startActivity(intent);
}
Pattison answered 1/12, 2018 at 21:29 Comment(3)
After adding this code, is there something else I need to add? Coz I am getting app not installed. :(Gram
what is the exactly error you faced on it? can u go to this intent that mentioned on top ? =>[ Intent intent = new Intent(Intent.ACTION_VIEW, fileUri) ] and choose cancel or install app? if u are inside an activity change context.startActivity(intent) ; to startActivity(intent);Pattison
Perfect, it works for me!, i needed to add this permission to the manifest. <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>Pearlypearman
D
4

For Oreo, Add permission in AndroidManifast

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
Dietrich answered 24/8, 2018 at 9:29 Comment(0)
C
2

Here is the solution I have found

val newFile = File(dirPath, "$fileNameWithoutExtn.apk")
                        var fileUri = Uri.fromFile(newFile)
                        //use the fileProvider to get the downloaded from sdcard
                        if (Build.VERSION.SDK_INT >= 24) {
                            fileUri = FileProvider.getUriForFile(this@SettingAcitivity, applicationContext.packageName + ".provider", newFile)
                         val intent=Intent(Intent.ACTION_VIEW)
                            intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
                            intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                            startActivity(intent)
                        }else{
                            newFile.setReadable(true, false)
                            val intent = Intent(Intent.ACTION_VIEW)
                            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
                            intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                            startActivity(intent)
                        }

and write in manifest

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>

and also set the permission

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

and in xml folder paths will be

<paths>
    <external-path
        name="external_files"
        path="." />
</paths>
Columbus answered 4/3, 2019 at 9:39 Comment(1)
I don't know why, but only your code worked for me, thanks!Glyph
D
0

Add file in res/xml -> provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

Add this code in AndroidManifest.xml

     <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.example.provider" <-- change this with your package name
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>

run this code for install your app or open

    public void installApk(String file) {
        Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider",new File(file));
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        context.startActivity(intent);
    }
Dimarco answered 2/7, 2018 at 4:30 Comment(0)
I
0

simply do the following steps:

1- add the following permission to manifest:

 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

2- Add provider to manifest (as child as application tag):

 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="tatcomputer.ir.libraryapp.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths"/>
    </provider>

3- Add paths.xml to xml directory:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="." />
<external-files-path name="external_files" path="." />
<external-path name="external_files" path="." />
<cache-path name="cached_files" path="." />
<external-cache-path name="cached_files" path="." />
</paths>

4- show install apk page using following code (note that in my case apk is in root of my phone named tmp.apk:

       String root = Environment.getExternalStorageDirectory().toString();

        Uri fileUri24 = FileProvider.getUriForFile(App.applicationContext, BuildConfig.APPLICATION_ID + ".provider", new File(root + "/tmp.apk"));
        Uri fileUri = Uri.fromFile(new File(root + "/tmp.apk"));

        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= 24)
            intent.setDataAndType(fileUri24, "application/vnd.android.package-archive");
        else intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        App.applicationContext.startActivity(intent);
Ikeikebana answered 3/3, 2019 at 8:3 Comment(0)
S
0

Intall Apk automatically

Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent1.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" +filename)), "application/vnd.android.package-archive");
            intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Saskatchewan answered 3/8, 2019 at 12:4 Comment(0)
I
0

In my case on android 8.0 problem was in

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

More detailed how to get this permission Exception 'open failed: EACCES (Permission denied)' on Android

Ifill answered 30/8, 2019 at 14:39 Comment(0)
J
0

Update in 2022

You can't use internal storage to install an APK. You have to use External Storage instead.

And this is for people looking at how to install step-by-step APK on a remote Android device. Also, the tutorial has a NodeJS Server implementation.

enter image description here

Remote install for Android app using APK

Jellybean answered 10/6, 2022 at 7:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.