By Alkersan


2011-01-05 12:59:03 8 Comments

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

15 comments

@Hadi Note 2019-01-29 15:19:10

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!


@Amin Keshavarzian 2019-05-25 13:45:39

Thanks, the fourth step was what was missing in all of them.

@whiteagle 2019-08-26 12:05:58

Indeed, the 4th point is absolutely necessary. All other answers missed it completely.

@Pavitra 2019-11-05 12:52:15

Isn't android.permission.REQUEST_INSTALL_PACKAGES available to only system apps or apps signed with system keystore or whatever?

@Hadi Note 2019-11-06 07:28:04

@Pavitra 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. Protection level: signature

@amiron 2019-05-18 15:51:53

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();
            }

        }

    }

    ...

}

@Lie Ryan 2011-01-05 14:07:33

You can easily launch a play store link or an install prompt:

Intent promptInstall = new Intent(Intent.ACTION_VIEW)
    .setDataAndType(Uri.parse("content:///path/to/your.apk"), 
                    "application/vnd.android.package-archive");
startActivity(promptInstall); 

or

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("https://play.google.com/store/apps/details?id=com.package.name"));
startActivity(goToMarket);

However, you cannot install .apks without user's explicit permission; not unless the device and your program is rooted.

@CommonsWare 2011-01-05 14:22:35

Good answer, but don't hardcode /sdcard, since that is wrong on Android 2.2+ and other devices. Use Environment.getExternalStorageDirectory() instead.

@dharam 2011-12-26 11:33:42

it can only possible for me when m trying to install it form the SD card but when m going to put it on asset folder its give me parsing error !! , may be its not allowed from project directory .

@Lie Ryan 2011-12-26 11:42:06

The /asset/ directory only exist in your development machine, when the application is compiled to an APK, the /asset/ directory no longer exists since all the assets are zipped inside the APK. If you wish to install from your /asset/ directory, you'll need to extract that into another folder first.

@Juuso Ohtonen 2013-01-04 12:40:37

Beware! setType() "automatically clears any data that was previously set (for example by setData(Uri))." developer.android.com/reference/android/content/… To be on the safe side, use setDataAndType() instead: stackoverflow.com/a/4969421/1097104

@Sharanabasu Angadi 2013-03-14 10:28:06

@LieRyan . Nice to see your answer. I have the rooted device with custom ROM. I want to install themes dynamically without asking user to press install button. Can I do that.

@jeevs 2013-07-09 10:38:45

@cesar will you able to find anything on how to update with explicit permission of the user? The device is rooted.

@Siddharthan Asokan 2013-09-18 22:49:11

@Lie Ryan does it work for the paid apps as well? If I run the device rooted

@AndroidOptimist 2014-03-04 06:23:11

@LieRyan i have a simple question. I downloaded my apk from net and going to update the downloaded apk. Now i would like to know how would i synchronize both, means the progress bar needs to display both the download and upload activity together. Help me in this

@sdream 2016-02-09 14:10:30

Installing app using .apk will remove it's ownership from Play Store or not ? Referencing to this thread: Link

@android developer 2016-11-25 23:48:01

This doesn't seem to work anymore when targetSdk is 25. it gives an exception: "android.os.FileUriExposedException: ...apk exposed beyond app through Intent.getData() ...". How come?

@Lie Ryan 2016-11-26 10:04:43

@android developer: I can't test this, at the moment, but on targetSdkVersion >= 24, the following applies: stackoverflow.com/questions/38200282/… so you have to use FileProvider.

@android developer 2016-11-26 17:45:08

@LieRyan Can you please update the answer to include this? I've failed to use this method, not just for opening APK files, but also for sharing symlinked files.

@Iman Marashi 2016-12-02 15:08:49

You need to save the APK to a public directory.For example: Environment.getExternalStoragePublicDirectory(Environment.DI‌​RECTORY_DOWNLOADS)

@user7691120 2018-06-07 01:25:15

@LieRyan Is there any way to check if the installation is successful or failed?

@Cheerag 2019-06-05 05:57:56

Its not working in Android 9, Can anyone help here?

@MrVasilev 2019-09-04 08:39:02

What works for me is added "REQUEST_INSTALL_PACKAGES" permission in AndroidManifest.xml and start an Intent with action "ACTION_INSTALL_PACKAGE"

@Abbas.moosavi 2017-11-22 14:26:03

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);

@Ali Nem 2017-01-15 16:32:54

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.

@Hara Hara Mahadevaki 2017-03-30 14:17:25

I followed this method. However, it says file corrupted when I press install button. Im able to install the same file by transferring through bluetooth. why so?

@Sridhar S 2018-03-09 07:00:55

HI When you get file corrupted error, what is the mode of transportation you gave to your app to bring the APK, if you are downloading from server means, check to flush the streams at the copying file from the server? Since installing the APK which was transferred by Bluetooth working means that is the problem I guess.

@Makvin 2019-01-09 05:37:12

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.cont‌​ent.pm.PackageManage‌​r, java.lang.String)' on a null object reference

@Renetik 2019-05-31 08:09:32

Your library could have at last simple readme with simple instructions about usage... ;)

@sehrob 2019-07-17 18:50:48

Thank you so much, after a week of struggle with this answer I solved my problem! @SridharS, I know it's been a long time ago, as I see, but if you interested, on line 5, you should add .authorityStr after context.getPackageName() then it should work.

@Jacob S.P. 2019-11-10 02:53:37

@sehrob do you know why he added .authorityStr to begin with?

@Phuoc-Loc Truong 2015-05-25 14:27:04

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);

@Clocker 2016-09-19 16:26:55

You don't need that system-only permission to launch the Package Installer activity

@Joolah 2015-03-14 16:12:06

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);

@Hartok 2015-01-22 21:30:52

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);

@user1604240 2014-11-11 15:00:11

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);

@Jai 2018-04-10 10:28:30

I'm getting the same parser error! I don't know how to resolve it? I set file.setReadable(true, false) but it's not working for me

@Alkersan 2011-01-05 14:17:45

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);

@Hubert Liberacki 2014-08-28 00:11:05

android.permission.INSTALL_PACKAGES is available only for the system signed apps. So this would not help a lot

@sarahara 2014-03-24 14:27:13

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

@infinite_loop_ 2015-01-05 15:49:36

there's some problems with the website. I can't get the api_key and i can't continue registration.

@sarahara 2015-01-11 06:58:30

Hi @infinite_loop_ you can find the API key within your user account section: updatenode.com/profile/view_keys

@qix 2014-04-25 11:43:19

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);

@Loïc Faure-Lacroix 2011-01-05 13:21:22

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.

@Alkersan 2011-01-05 13:35:18

As I see, this solution is closest to truth :). But it not appropriate for my case. I need dynamic loader, dependent on current user environment, and going to market - not a good solution. But anyway, thank you.

@IronBlossom 2013-06-27 19:25:40

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

@Horaceman 2011-03-17 10:58:14

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.

@Brent M. Spell 2013-04-02 15:38:00

I can't upvote this answer enough. For some reason, setting the intent data and MIME type separately causes ActivityNotFoundException in API level 17.

@Tam 2013-04-12 02:28:45

I just up-voted this too. This form was the only friggin one I could get to work. Still years later.. what's this freakin bug? Hours wasted. I'm using Eclipse(Helios), BTW.

@Bogdan Alexandru 2014-06-02 10:19:18

@BrentM.Spell and others: look into the documentation, you will see that whenever you set only data OR type, the other one is automatically voided, e.g.: setData() would cause the type parameter to be removed. You MUST use setDataAndType() if you want to give values for both. Here: developer.android.com/reference/android/content/…

Related Questions

Sponsored Content

39 Answered Questions

[SOLVED] How to get screen dimensions as pixels in Android

97 Answered Questions

12 Answered Questions

30 Answered Questions

33 Answered Questions

[SOLVED] How do you install an APK file in the Android emulator?

11 Answered Questions

[SOLVED] Proper use cases for Android UserManager.isUserAGoat()?

9 Answered Questions

[SOLVED] How do I discover memory usage of my application in Android?

45 Answered Questions

[SOLVED] Android SDK installation doesn't find JDK

106 Answered Questions

[SOLVED] R cannot be resolved - Android error

8 Answered Questions

[SOLVED] Android error: Failed to install *.apk on device *: timeout

Sponsored Content