3

I have developed an app that is running with system permissions (it is built into the image of AOSP) and has the INSTALL_PACKAGES permission.

My goal is to install a APK silently in the background without user interaction. I read different approaches on stackoverflow, but most of them were either outdated or simply didn't work.

For example if I try to use the PackageManager (either via reflection or via running as a shell command from within my app) I get the following error and that's where I got stuck currently.

11-20 19:06:10.999  4965  4965 D AndroidRuntime: >>>>>> START 

com.android.internal.os.RuntimeInit uid 10039 <<<<<<
11-20 19:06:11.199  4965  4965 D AndroidRuntime: Calling main entry com.android.commands.pm.Pm
11-20 19:06:11.268  4965  4965 E Pm      : Error
11-20 19:06:11.268  4965  4965 E Pm      : java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.AppOpsManager.checkPackage(int, java.lang.String)' on a null object reference
11-20 19:06:11.268  4965  4965 E Pm      :  at android.os.Parcel.readException(Parcel.java:2010)
11-20 19:06:11.268  4965  4965 E Pm      :  at android.os.Parcel.readException(Parcel.java:1950)
11-20 19:06:11.268  4965  4965 E Pm      :  at android.content.pm.IPackageInstaller$Stub$Proxy.createSession(IPackageInstaller.java:254)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.commands.pm.Pm.doCreateSession(Pm.java:607)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.commands.pm.Pm.runInstall(Pm.java:431)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.commands.pm.Pm.run(Pm.java:150)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.commands.pm.Pm.main(Pm.java:107)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
11-20 19:06:11.268  4965  4965 E Pm      :  at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:285)
11-20 19:06:11.270  4965  4965 I app_process: System.exit called, status: 1
11-20 19:06:11.270  4965  4965 I AndroidRuntime: VM exiting with result code 1.

Can anybody tell me why this error is occurring or alternatively other solutions or the "modern" way to install APKS silently with INSTALL_PACKAGES?

Any help appreciated.

EDIT:

To the guy who marked this as a duplicate of nullpointer exception - how do you make the connection that would be a duplicate? I am specifically asking for the reasons behind it and not about the nullpointer exception itself. Your duplicate suggestion makes no sense at all.

EDIT 2:

My code that tries to install the APK (copied from the package installer app of AOSP sources):

public boolean install(Context context) {

    Log.d(TAG, "Start Installation");

    String packageName = "com.spotify.music";
    String apkPath = Environment.getExternalStorageDirectory().toString() + "/APKs/spotify.apk";

    PackageInstaller.Session session;
    int sessionId;
    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    params.setAppPackageName(packageName);

    try {
        sessionId = context.getPackageManager().getPackageInstaller().createSession(params);
        session = context.getPackageManager().getPackageInstaller().openSession(sessionId);
    } catch (IOException e) {
        Log.w(TAG, "Error creating/opening the installing session", e);
        return false;
    }

    try {
        File file = new File(apkPath);

        try (InputStream in = new FileInputStream(file)) {
            long sizeBytes = file.length();
            try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
                byte[] buffer = new byte[1024 * 1024];
                while (true) {

                    int numRead = in.read(buffer);

                    if (numRead == -1) {
                        session.fsync(out);
                        break;
                    }

                    out.write(buffer, 0, numRead);
                    if (sizeBytes > 0) {
                        float fraction = ((float) numRead / (float) sizeBytes);
                        Log.d(TAG, "Install progress: " + fraction);
                    }
                }
            }
        }

        Log.d(TAG, "Install finished");
        session.commit(PendingIntent.getBroadcast(context, sessionId, new Intent("android.intent.action.MAIN"), 0).getIntentSender());

        return true;
    } catch (IOException | SecurityException e) {
        Log.e(TAG, "Could not write package", e);

        session.close();

        return false;
    }
}
phoebus
  • 1,280
  • 1
  • 16
  • 36
  • Can you share the code you are using to initiate the install? – satur9nine Jun 04 '18 at 20:18
  • @satur9nine I used the same code as the package installer app in the AOSP source code, meaning getting a PackageInstaller object and opening a session – phoebus Jun 05 '18 at 21:56

1 Answers1

1

We use the following code:

Uri apkUri = Uri.fromFile(apkFile);
IPackageInstallObserver observer = null;
String installerPackageName = "MyInstaller";
getPackageManager().installPackage(apkUri, observer, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);

It is deprecated but still works even on Android 8.1.

satur9nine
  • 13,927
  • 5
  • 80
  • 123
  • will try it out and come back to you - thank you! – phoebus Jun 04 '18 at 20:32
  • do I have to follow a specific gradle setup to do this? My Android Studio does not resolve the installPackage() method – phoebus Jun 05 '18 at 22:13
  • If you are building an android app with system privileges to be bundled with a system image it must be signed with the platform key and almost certainly built along with the rest of the android platform via Android.mk make files and not with gradle. This gives you access to `@hide` methods. Accessing `@hide` methods outside of the platform build system is difficult (but not impossible). – satur9nine Jun 06 '18 at 04:06