[android] Install Application programmatically on Android

I'm interested in knowing if it is possible to programmatically install a dynamically downloaded apk from a custom Android application.

This question is related to android apk

The answer is


File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

I had the same problem and after several attempts, it worked out for me this way. I don't know why, but setting data and type separately screwed up my intent.


This can help others a lot!

First:

private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";

private void install() {
    File file = new File(APP_DIR + fileName);

    if (file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String type = "application/vnd.android.package-archive";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
            intent.setDataAndType(downloadedApk, type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        getContext().startActivity(intent);
    } else {
        Toast.makeText(getContext(), "?File not found!", Toast.LENGTH_SHORT).show();
    }
}

Second: For android 7 and above you should define a provider in manifest like below!

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

Third: Define path.xml in res/xml folder like below! I'm using this path for internal storage if you want to change it to something else there is a few way! You can go to this link: FileProvider

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

Forth: You should add this permission in manifest:

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

Allows an application to request installing packages. Apps targeting APIs greater than 25 must hold this permission in order to use Intent.ACTION_INSTALL_PACKAGE.

Please make sure the provider authorities are the same!



Just an extension, if anyone need a library then this might help. Thanks to Raghav


Do not forget to request permissions:

android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
android.Manifest.permission.READ_EXTERNAL_STORAGE

Add in AndroidManifest.xml the provider and permission:

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

Create XML file provider 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"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
</paths>

Use below example code:

   public class InstallManagerApk extends AppCompatActivity {

    static final String NAME_APK_FILE = "some.apk";
    public static final int REQUEST_INSTALL = 0;

     @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // required permission:
        // android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
        // android.Manifest.permission.READ_EXTERNAL_STORAGE

        installApk();

    }

    ...

    /**
     * Install APK File
     */
    private void installApk() {

        try {

            File filePath = Environment.getExternalStorageDirectory();// path to file apk
            File file = new File(filePath, LoadManagerApkFile.NAME_APK_FILE);

            Uri uri = getApkUri( file.getPath() ); // get Uri for  each SDK Android

            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            intent.setData( uri );
            intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK );
            intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
            intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getApplicationInfo().packageName);

            if ( getPackageManager().queryIntentActivities(intent, 0 ) != null ) {// checked on start Activity

                startActivityForResult(intent, REQUEST_INSTALL);

            } else {
                throw new Exception("don`t start Activity.");
            }

        } catch ( Exception e ) {

            Log.i(TAG + ":InstallApk", "Failed installl APK file", e);
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG)
                .show();

        }

    }

    /**
     * Returns a Uri pointing to the APK to install.
     */
    private Uri getApkUri(String path) {

        // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
        // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
        // recommended.
        boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;

        String tempFilename = "tmp.apk";
        byte[] buffer = new byte[16384];
        int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
        try (InputStream is = new FileInputStream(new File(path));
             FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {

            int n;
            while ((n = is.read(buffer)) >= 0) {
                fout.write(buffer, 0, n);
            }

        } catch (IOException e) {
            Log.i(TAG + ":getApkUri", "Failed to write temporary APK file", e);
        }

        if (useFileProvider) {

            File toInstall = new File(this.getFilesDir(), tempFilename);
            return FileProvider.getUriForFile(this,  BuildConfig.APPLICATION_ID, toInstall);

        } else {

            return Uri.fromFile(getFileStreamPath(tempFilename));

        }

    }

    /**
     * Listener event on installation APK file
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQUEST_INSTALL) {

            if (resultCode == Activity.RESULT_OK) {
                Toast.makeText(this,"Install succeeded!", Toast.LENGTH_SHORT).show();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                Toast.makeText(this,"Install canceled!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this,"Install Failed!", Toast.LENGTH_SHORT).show();
            }

        }

    }

    ...

}

The solutions provided to this question are all applicable to targetSdkVersion s of 23 and below. For Android N, i.e. API level 24, and above, however, they do not work and crash with the following Exception:

android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()

This is due to the fact that starting from Android 24, the Uri for addressing the downloaded file has changed. For instance, an installation file named appName.apk stored on the primary external filesystem of the app with package name com.example.test would be as

file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk

for API 23 and below, whereas something like

content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk

for API 24 and above.

More details on this can be found here and I am not going to go through it.

To answer the question for targetSdkVersion of 24 and above, one has to follow these steps: Add the following to the AndroidManifest.xml:

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

2. Add the following paths.xml file to the xml folder on res in src, main:

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

The pathName is that shown in the exemplary content uri example above and pathValue is the actual path on the system. It would be a good idea to put a "." (without quotes) for pathValue in the above if you do not want to add any extra subdirectory.

  1. Write the following code to install the apk with the name appName.apk on the primary external filesystem:

    File directory = context.getExternalFilesDir(null);
    File file = new File(directory, fileName);
    Uri fileUri = Uri.fromFile(file);
    if (Build.VERSION.SDK_INT >= 24) {
        fileUri = FileProvider.getUriForFile(context, context.getPackageName(),
                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_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intent);
    activity.finish();
    

No permission is also necessary when writing to your own app's private directory on the external filesystem.

I have written an AutoUpdate library here in which I have used the above.


Another solution that doesn't not require to hard-code the receiving app and that is therefore safer:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
startActivity(intent);

In Android Oreo and above version we have to approach different methods to install apk programatically.

 private void installApkProgramatically() {


    try {
        File path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);

        File file = new File(path, filename);

        Uri uri;

        if (file.exists()) {

            Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

                if (!activity.getPackageManager().canRequestPackageInstalls()) {
                    startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
                } else {
                    Uri fileUri = FileProvider.getUriForFile(activity.getBaseContext(), activity.getApplicationContext().getPackageName() + ".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_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    startActivity(intent);
                    alertDialog.dismiss();
                }

            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

                Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                uri = FileProvider.getUriForFile(activity.getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file);
                activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                intent1.setDataAndType(uri,
                        "application/*");
                intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent1.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                startActivity(intent1);

            } else {
                Intent intent = new Intent(Intent.ACTION_VIEW);

                uri = Uri.fromFile(file);

                intent.setDataAndType(uri,
                        "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        } else {

            Log.i(TAG, " file " + file.getPath() + " does not exist");
        }
    } catch (Exception e) {

        Log.i(TAG, "" + e.getMessage());

    }
}

In Oreo and above version we need unknown resource installation permission. so in activity result u have to check the result for the permission

    @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {

        case Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE:
            switch (resultCode) {
                case Activity.RESULT_OK:
                    installApkProgramatically();

                    break;
                case Activity.RESULT_CANCELED:
                    //unknown resouce installation cancelled

                    break;
            }
            break;
    }
}

First add the following line to AndroidManifest.xml :

<uses-permission android:name="android.permission.INSTALL_PACKAGES"
    tools:ignore="ProtectedPermissions" />

Then use the following code to install apk:

File sdCard = Environment.getExternalStorageDirectory();
            String fileStr = sdCard.getAbsolutePath() + "/MyApp";// + "app-release.apk";
            File file = new File(fileStr, "TaghvimShamsi.apk");
            Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
            startActivity(promptInstall);

Well, I digged deeper, and found sources of PackageInstaller application from Android Source.

https://github.com/android/platform_packages_apps_packageinstaller

From manifest I found that it require permission:

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

And the actual process of installation occurs after confirmation

Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
   newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);

Try this - Write on Manifest:

uses-permission android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions"

Write the Code:

File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
File file = new File(fileStr, "app-release.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");

startActivity(promptInstall);

Yes it's possible. But for that you need the phone to install unverified sources. For example, slideMe does that. I think the best thing you can do is to check if the application is present and send an intent for the Android Market. you should use something the url scheme for android Market.

market://details?id=package.name

I don't know exactly how to start the activity but if you start an activity with that kind of url. It should open the android market and give you the choice to install the apps.


try this

String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
MainActivity.this.startActivity(intent);

UpdateNode provides an API for Android to install APK packages from inside another App.

You can just define your Update online and integrate the API into your App - that's it.
Currently the API is in Beta state, but you can already do some tests yourself.

Beside that, UpdateNode offers also displaying messages though the system - pretty useful if you want to tell something important to your users.

I am part of the client dev team and am using at least the message functionality for my own Android App.

See here a description how to integrate the API


It's worth noting that if you use the DownloadManager to kick off your download, be sure to save it to an external location e.g. setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. The intent with a package-archive type doesn't appear to like the content: scheme used with downloads to an internal location, but does like file:. (Trying to wrap the internal path into a File object and then getting the path doesn't work either, even though it results in a file: url, as the app won't parse the apk; looks like it must be external.)

Example:

int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
        .setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(promptInstall);

I just want to share the fact that my apk file was saved to my app "Data" directory and that I needed to change the permissions on the apk file to be world readable in order to allow it to be installed that way, otherwise the system was throwing "Parse error: There is a Problem Parsing the Package"; so using solution from @Horaceman that makes:

File file = new File(dir, "App.apk");
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);