Install Application programmatically on Android
Asked Answered
S

18

247

Is possible to programmatically install a dynamically downloaded apk from a custom Android application.

Smitty answered 5/1, 2011 at 12:59 Comment(1)
I do not know what "dynamic loader, dependent on current user environment" means. The answer supplied by @Lie Ryan shows how you can install an APK downloaded by whatever means you choose.Balls
L
254

You can easily launch a market link or an install prompt:

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

source

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("market://details?id=com.package.name"));
startActivity(goToMarket);

source

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

Lakeesha answered 5/1, 2011 at 14:7 Comment(17)
Good answer, but don't hardcode /sdcard, since that is wrong on Android 2.2+ and other devices. Use Environment.getExternalStorageDirectory() instead.Balls
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 .Catechin
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.Lakeesha
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: https://mcmap.net/q/48969/-android-install-apk-programmatically-duplicateGordon
@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.Succinic
@cesar will you able to find anything on how to update with explicit permission of the user? The device is rooted.Shipping
@Lie Ryan does it work for the paid apps as well? If I run the device rootedDisruptive
@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 thisAby
Installing app using .apk will remove it's ownership from Play Store or not ? Referencing to this thread: LinkRunway
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?Atoll
@android developer: I can't test this, at the moment, but on targetSdkVersion >= 24, the following applies: #38200782 so you have to use FileProvider.Lakeesha
@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.Atoll
You need to save the APK to a public directory.For example: Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)Ostensive
@LieRyan Is there any way to check if the installation is successful or failed?Newcastle
Its not working in Android 9, Can anyone help here?Djebel
What works for me is added "REQUEST_INSTALL_PACKAGES" permission in AndroidManifest.xml and start an Intent with action "ACTION_INSTALL_PACKAGE"Benoite
You may try this. android.googlesource.com/platform/development/+/master/samples/…Caesarism
C
56
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.

Contingency answered 17/3, 2011 at 10:58 Comment(3)
I can't upvote this answer enough. For some reason, setting the intent data and MIME type separately causes ActivityNotFoundException in API level 17.Anguine
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.Jemima
@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/…Legatee
M
56

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.

Mara answered 15/1, 2017 at 16:32 Comment(7)
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?Abohm
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.Paste
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object referencePapst
Your library could have at last simple readme with simple instructions about usage... ;)Pascha
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.Sylvia
I used the activity.cacheDir for file and <cache-path name="name" path="." /> in xml. This way the apk is in app memory. I just added also the permission and it worked great the app self updateGarling
This must be the accepted answer, the accepted one is just a suggestion, your manifest changes makes the differencePendulum
S
33

Well, I dug 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);
Smitty answered 5/1, 2011 at 14:17 Comment(2)
android.permission.INSTALL_PACKAGES is available only for the system signed apps. So this would not help a lotStricture
@Smitty could you explain how did you import PackatUtil? And also how do you put in this lib on the app classpath?Kalikalian
S
24

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!


Sieve answered 29/1, 2019 at 15:19 Comment(10)
Thanks, the fourth step was what was missing in all of them.Keats
Indeed, the 4th point is absolutely necessary. All other answers missed it completely.Lettered
Isn't android.permission.REQUEST_INSTALL_PACKAGES available to only system apps or apps signed with system keystore or whatever?Anarchism
@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: signatureSieve
This code does not use Intent.ACTION_INSTALL_PACKAGE. So why this permission??Motte
@AlexanderDyagilev Google said that not me! They try to do better in latest android versions! If you try it will not work in some android versions, There is a lots of custom and official versions of android you can't tell which one does not need it!Sieve
In your third step, the external-path path and name are somewhat ambiguous. If my file is downloaded to storage/emulated/0/Android/data/com.mycompany.myapp/files then what should the path and name values be, exactly?Skiascope
@Skiascope Take a look at this developer.android.com/reference/androidx/core/content/… Read carefully!Sieve
this = <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> save meCovington
I get an error "There was a problem parsing the package", when it try to install. Why?Condyloma
T
23

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);
Taps answered 11/11, 2014 at 15:0 Comment(1)
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 meAbebi
S
10

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;
    }
}
Souffle answered 4/7, 2020 at 14:25 Comment(3)
I'm getting an "App not installed" dialog when the update finishes, and, obviously, it doesn't actually install the update.Ventriloquy
App Not install dialog will come in some scenario. 1. current running app and installation APK has a different signature. 2.Version should be greater than the currently running application. 3. Both apps should be signed or unsigned. one is signed another one is unsigned means this problem will come.Souffle
i was trying to update a build from the studio and not an actual apk. face palmVentriloquy
W
7

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

        }

    }

    ...

}
Worser answered 18/5, 2019 at 15:51 Comment(0)
R
5

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);
Ravid answered 22/1, 2015 at 21:30 Comment(0)
S
5

Came at this some two months ago and supposedly figured it out. Came back today and couldn't make heads or tails of it. Nothing had changed in my setup as far as I am aware, so apparently whatever past me had come up with wasn't robust enough for present me. I finally managed to get something working again, so documenting it here for future me and anyone else who might stand to benefit from yet another attempt.

This attempt represents a direct Xamarin C# translation of the original Android Java Install APK - Session API example. It could probably use some additional work, but it's at least a start. I have it running on an Android 9 device, though I have the project targetting Android 11.

InstallApkSessionApi.cs

namespace LauncherDemo.Droid
{
    using System;
    using System.IO;

    using Android.App;
    using Android.Content;
    using Android.Content.PM;
    using Android.OS;
    using Android.Widget;

    [Activity(Label = "InstallApkSessionApi", LaunchMode = LaunchMode.SingleTop)]
    public class InstallApkSessionApi : Activity
    {
        private static readonly string PACKAGE_INSTALLED_ACTION =
                "com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            this.SetContentView(Resource.Layout.install_apk_session_api);

            // Watch for button clicks.
            Button button = this.FindViewById<Button>(Resource.Id.install);
            button.Click += this.Button_Click;
        }

        private void Button_Click(object sender, EventArgs e)
        {
            PackageInstaller.Session session = null;
            try
            {
                PackageInstaller packageInstaller = this.PackageManager.PackageInstaller;
                PackageInstaller.SessionParams @params = new PackageInstaller.SessionParams(
                        PackageInstallMode.FullInstall);
                int sessionId = packageInstaller.CreateSession(@params);
                session = packageInstaller.OpenSession(sessionId);
                this.AddApkToInstallSession("HelloActivity.apk", session);

                // Create an install status receiver.
                Context context = this;
                Intent intent = new Intent(context, typeof(InstallApkSessionApi));
                intent.SetAction(PACKAGE_INSTALLED_ACTION);
                PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, 0);
                IntentSender statusReceiver = pendingIntent.IntentSender;

                // Commit the session (this will start the installation workflow).
                session.Commit(statusReceiver);
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException("Couldn't install package", ex);
            }
            catch
            {
                if (session != null)
                {
                    session.Abandon();
                }

                throw;
            }
        }

        
        private void AddApkToInstallSession(string assetName, PackageInstaller.Session session)
        {
            // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
            // if the disk is almost full.
            using Stream packageInSession = session.OpenWrite("package", 0, -1);
            using Stream @is = this.Assets.Open(assetName);
            byte[] buffer = new byte[16384];
            int n;
            while ((n = @is.Read(buffer)) > 0)
            {
                packageInSession.Write(buffer, 0, n);
            }
        }

        // Note: this Activity must run in singleTop launchMode for it to be able to receive the intent
        // in onNewIntent().
        protected override void OnNewIntent(Intent intent)
        {
            Bundle extras = intent.Extras;
            if (PACKAGE_INSTALLED_ACTION.Equals(intent.Action))
            {
                PackageInstallStatus status = (PackageInstallStatus)extras.GetInt(PackageInstaller.ExtraStatus);
                string message = extras.GetString(PackageInstaller.ExtraStatusMessage);
                switch (status)
                {
                    case PackageInstallStatus.PendingUserAction:
                        // This test app isn't privileged, so the user has to confirm the install.
                        Intent confirmIntent = (Intent) extras.Get(Intent.ExtraIntent);
                        this.StartActivity(confirmIntent);
                        break;
                    case PackageInstallStatus.Success:
                        Toast.MakeText(this, "Install succeeded!", ToastLength.Short).Show();
                        break;
                    case PackageInstallStatus.Failure:
                    case PackageInstallStatus.FailureAborted:
                    case PackageInstallStatus.FailureBlocked:
                    case PackageInstallStatus.FailureConflict:
                    case PackageInstallStatus.FailureIncompatible:
                    case PackageInstallStatus.FailureInvalid:
                    case PackageInstallStatus.FailureStorage:
                        Toast.MakeText(this, "Install failed! " + status + ", " + message,
                                ToastLength.Short).Show();
                        break;
                    default:
                        Toast.MakeText(this, "Unrecognized status received from installer: " + status,
                                ToastLength.Short).Show();
                        break;
                }
            }
        }
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.launcherdemo" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
    <application android:label="LauncherDemo.Android" android:theme="@style/MainTheme" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</manifest>

What I particularly like about this approach is that it does not require any special work in the manifest - no broadcast receivers, file providers, etc. Granted, this takes as its source some APK in the app's assets, whereas a more useful system will probably use some given APK path. I imagine that will introduce a certain level of additional complexity. In addition, I never ran into any issues here (at least as far as I could tell) with the Xamarin GC closing streams before Android was done with them. Nor did I have any issues with the APK not being parsed. I made sure to use a signed APK (the one generated by Visual Studio when deploying to the device worked just fine), and again I did not run into any file access permission issues simply due to using an APK from the app's assets in this example.

One thing that some of the other answers here provided was the idea of making the sideloading permission grant more streamlined. The answer by Yabaze Cool provided this feature:

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

When I tested my translation, I uninstalled both the launcher demo and the app it installed. Not providing the check to canRequestPackageInstalls made it to where I had to manually press an additional Settings button to take me to the same dialog as does the ACTION_MANAGE_UNKNOWN_APP_SOURCES intent above. So adding this logic can help to somewhat simplify the installation process for the user.

Superpatriot answered 29/10, 2021 at 19:53 Comment(1)
And naturally within minutes of posting this I find my previous working solution, pretty much identical to the above... Forgive me SO, I have hoarded my knowledge, I am chastened...Superpatriot
R
4

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);
Richie answered 25/4, 2014 at 11:43 Comment(0)
R
3

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.

Rehnberg answered 5/1, 2011 at 13:21 Comment(1)
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.Smitty
N
2

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

Neely answered 27/6, 2013 at 19:25 Comment(0)
F
2

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);
Fourthly answered 14/3, 2015 at 16:12 Comment(0)
S
0

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

Sweptback answered 24/3, 2014 at 14:27 Comment(2)
there's some problems with the website. I can't get the api_key and i can't continue registration.Underwing
Hi @Underwing you can find the API key within your user account section: updatenode.com/profile/view_keysSweptback
N
0

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);
Nominee answered 25/5, 2015 at 14:27 Comment(1)
You don't need that system-only permission to launch the Package Installer activityConvexity
U
0

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);
Unquestionable answered 22/11, 2017 at 14:26 Comment(0)
T
0

Based on answer @Uroš Podkrižnik.

Installing the application through the APK may differ for various versions of android (API Levels 21-30):

private var uri: Uri? = null
private var manager: DownloadManager? = null
private var file: File? = null
private var request: DownloadManager.Request? = null

private val REQUEST_WRITE_PERMISSION = 786
private val REQUEST_INSTALL_PACKAGE = 1234

private var receiver: BroadcastReceiver? = null
private var installIntent: Intent? = null

...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val externalStorageDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
    } else {
        @Suppress("DEPRECATION")
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    }
    val destination = "$externalStorageDir/Application.apk"
    uri = Uri.parse("file://$destination")

    file = File(destination)
    file?.let { if (it.exists()) it.delete() }
    
    request = DownloadManager.Request(Uri.parse("https://path_to_file/application.apk"))
    request?.let {
        it.setDescription("Update App")
        it.setTitle("Application")
        it.setDestinationUri(uri)
    }
    manager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

    // for level android api >= 23 needs permission to write to external storage
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(context!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            // here you can display the loading diagram
            registerReceiver()
        } else {
            // request for permission to write to external storage
            requestPermissions(
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                REQUEST_WRITE_PERMISSION
            )
        }
    } else {
        // here you can display the loading diagram
        registerReceiver()
    }
}

Create and register receiver:

private val onDownloadComplete = object : BroadcastReceiver() {
    // install app when apk is loaded
    override fun onReceive(ctxt: Context, intent: Intent) {
        val mimeType = "application/vnd.android.package-archive"
        receiver = this
        try {
            installIntent = Intent(Intent.ACTION_VIEW)
            installIntent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP

            // for android api >= 24 requires FileProvider
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                installIntent?.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

                val fileProviderURI = FileProvider.getUriForFile(
                    context!!,
                    context!!.applicationContext.packageName + ".provider",
                    file!!)

                installIntent?.setDataAndType(fileProviderURI, mimeType)

                // for android api >= 26 requires permission to install from APK in settings
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
                        installFromAPK()
                    } else goToSecuritySettings()
                } else installFromAPK()
            } else {
                // for android api < 24 used file:// instead content://
                // (no need to use FileProvider)
                installIntent?.setDataAndType(uri, mimeType)
                installFromAPK()
            }
        } catch (e: Exception) {
            // view error message
        }
    }
}

private fun registerReceiver() {
    manager!!.enqueue(request)
    context?.registerReceiver(
        onDownloadComplete,
        IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
    )
}

private fun installFromAPK() {
    try {
        startActivity(installIntent)
        context?.unregisterReceiver(receiver)
        activity?.finish()
    } catch (e: Exception) {
        // view error message
    }
}

// go to settings for get permission install from APK
private fun goToSecuritySettings() {
    val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
        Uri.parse(String.format(
            "package:%s",
            context!!.applicationContext.packageName
        ))
    )
    try {
        startActivityForResult(intent, REQUEST_INSTALL_PACKAGE)
    } catch (e: Exception) {
        // view error message
    }
}

Intercept the result of the permission request WRITE_EXTERNAL_STORAGE:

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_WRITE_PERMISSION
            && grantResults.isNotEmpty()
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        try {
            // here you can display the loading diagram
            registerReceiver()
        } catch (e: Exception) {
            // view error message
        }
    }
}

Intercept the result of user selection in security settings:

@RequiresApi(api = Build.VERSION_CODES.O)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_INSTALL_PACKAGE
            && resultCode == AppCompatActivity.RESULT_OK) {
        if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
            installFromAPK()
        }
    } else {
        // view error message
    }
}

Add to your manifest:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application...>
    ...
    <provider
        android:name="androidx.core.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>
    ...
</application>

Add provider_paths.xml file to res/xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

For android API level = 30, return from security settings does not work, so used installation via browser:

try {
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = Uri.parse("https://path_to_file/application.apk")
    startActivity(intent)
    activity?.finish()
} catch (e: ActivityNotFoundException) { }
Tenerife answered 17/1, 2022 at 6:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.