Android - Install APK silently with PackageInstaller and PackageInstaller.Session
Asked Answered
C

2

6

Looking into the sources of Android (AOSP), the installPackage is labled deprecated and inspecting the PackageManager application of Android, it uses the PackageInstaller class to create a PackageInstaller.Session instance to perform the installation of an APK.

I am trying to do the same in my application. I am signed with the system key and I did include the INSTALL_PACKAGES permission in the manifest.

Here's my code:

    val packageName = "com.spotify.music"
    val inputStream = File(filesDir, "spotify.apk").inputStream()

    // ...

    val packageInstaller = context.packageManager.packageInstaller
    val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
    params.setAppPackageName(packageName)

    val sessionId = packageInstaller.createSession(params)
    val session = packageInstaller.openSession(sessionId)
    val out = session.openWrite("COSU", 0, -1)
    inputStream.copyTo(out)
    session.fsync(out)
    inputStream.close()
    out.close()
    session.commit(null)

However, I am getting the following weird null pointer exception:

Error while installing: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.AppOpsManager.checkPackage(int, java.lang.String)' on a null object reference

Does anybody have a clue what's going wrong here?

Chamomile answered 27/7, 2018 at 17:34 Comment(1)
Looking at the source of PackageInstallerService it grabs a reference to mContext.getSystemService(AppOpsManager.class) at system start up. But in your case it's null. You said you have the system signature. Are you building a custom ROM? Did you remove/disable app ops service? Is the system fully ready at the time you're attempting the install? What version of Android is this?Vanzandt
P
2

You can't pass null to session.commit(). See the doc at Android Developers. What you need is an IntentSender, which you get from a PendingIntent which in turn should contain an intent that your app can receive (can be in either of an Activity, a Service or a BroadcastReceiver).

When the session is committed the result will be included as extras in the received intent.

Polymath answered 25/9, 2018 at 15:0 Comment(0)
K
1

The following snippet should work for you (I did not test it, but you are more then welcome to debug it and edit any necessary changes).

Method parameters:

  • context - Your application's Context
  • installSessionId - A String to identify the install session
  • packageName - The package name you wish to install (e.g. com.my.package)
  • apkStream - An input stream that holds the APK file data.

Here is the code:

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class PackageInstaller {

    public static void installPackage(Context context, String installSessionId,
                                      String packageName,
                                      InputStream apkStream) throws IOException {
        PackageManager packageManger = context.getPackageManager();
        android.content.pm.PackageInstaller packageInstaller = 
                packageManger.getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        android.content.pm.PackageInstaller.Session session = null;
        try {
            int sessionId = packageInstaller.createSession(params);
            session = packageInstaller.openSession(sessionId);
            OutputStream out = session.openWrite(installSessionId, 0, -1);
            byte buffer[] = new byte[1024];
            int length;
            int count = 0;
            while ((length = apkStream.read(buffer)) != -1) {
                out.write(buffer, 0, length);
                count += length;
            }
            session.fsync(out);
            out.close();

            Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);

            session.commit(PendingIntent.getBroadcast(context, sessionId,
                    intent, PendingIntent.FLAG_UPDATE_CURRENT).getIntentSender());
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }
}
Keeter answered 6/8, 2018 at 10:32 Comment(1)
not working the codesGleeful

© 2022 - 2024 — McMap. All rights reserved.