How to install/update/remove APK using “PackageInstaller” class in Android L?

回眸只為那壹抹淺笑 提交于 2019-11-28 04:10:19

It is possible without System permissions from Android M onwards.

if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
        == PackageManager.PERMISSION_GRANTED)
        || (installerUid == Process.ROOT_UID)
        || mIsInstallerDeviceOwner) {
    mPermissionsAccepted = true;
} else {
    mPermissionsAccepted = false;
}

Silent install and uninstall of apps by Device Owner:

A Device Owner can now silently install and uninstall applications using the PackageInstaller APIs, independent of Google Play for Work.

More in this link.


This is possible from Android 6.0 and up.

  • Make your app the Device owner.

Once your app gets the Device owner permission, we can install, uninstall and update silently without any user intervention.

public static boolean installPackage(Context context, InputStream in, String packageName)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    params.setAppPackageName(packageName);
    // set params
    int sessionId = packageInstaller.createSession(params);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
    OutputStream out = session.openWrite("COSU", 0, -1);
    byte[] buffer = new byte[65536];
    int c;
    while ((c = in.read(buffer)) != -1) {
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    in.close();
    out.close();

    session.commit(createIntentSender(context, sessionId));
    return true;
}



private static IntentSender createIntentSender(Context context, int sessionId) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context,
                sessionId,
                new Intent(ACTION_INSTALL_COMPLETE),
                0);
        return pendingIntent.getIntentSender();
    }

Uninstall:

String appPackage = "com.your.app.package";
Intent intent = new Intent(getActivity(), getActivity().getClass());
PendingIntent sender = PendingIntent.getActivity(getActivity(), 0, intent, 0);
PackageInstaller mPackageInstaller = getActivity().getPackageManager().getPackageInstaller();
mPackageInstaller.uninstall(appPackage, sender.getIntentSender());

Git repo here.

You cannot silently install a third party application in the newly created user with PackageInstaller.Session.commit() without specific "rights".
You either need :

  • the INSTALL_PACKAGES permission. But this permission is not available for third-party application. So even with your profile owner app, you won't have this specific permission.
  • Run the process as ROOT_UID. Which means you'll have to root the device.

From the Android source code:

if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) == PackageManager.PERMISSION_GRANTED) 
   || (installerUid == Process.ROOT_UID)) {
    mPermissionsAccepted = true;
} else {
    mPermissionsAccepted = false;
}

If you neither have root access and the INSTALL_PACKAGES permission, then a message will be prompted to the user to ask if he confirms the permissions. This confirmation is then used during the commit process of the PackageInstaller's session. Obviously, in this case, this is not transparent, since the user will have to manually confirm the installation of your apps.

devz

The install method @amalBit provided did not work for me. It's strange since this is how it is implemented in the Google Sample.

This answer helped me to find a solution. I had to change some parts of the code. Here is my implementation:

public static void installPackage(Context context, InputStream inputStream)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
            .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
            1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}

This method can be called like this:

        InputStream inputStream = getActivity().getAssets().open("my_awesome_app.apk");
        InstallationHelper.installPackage(getActivity(), inputStream);

This works for me as well, although my device owner restricts the installation of apps and unknown sources by the user. Even if I running this example as device admin, I've got the java.lang.SecurityException: User restriction prevents installing.

openSession is checking for permissions. With this simple modificaton it is possible to reset the user restrictions only during a short method call.

public static DevicePolicyManager getDpm(Context context) {
    return (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
}

public static ComponentName getAdmin(Context context) {
    return new ComponentName(context, MyDevicePolicyReceiver.class);
}

public static void addMyRestrictions(Context context) {
   getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
   getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void clearMyRestrictions(Context context) {    
   getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
   getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void installPackage(Context context, InputStream inputStream)
    throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
        .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));

    //openSession checks for user restrictions
    clearMyRestrictions(context);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
    addMyRestrictions(context);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
        1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}

Please take care of exception handling.

You simply clear your restrictions

public static DevicePolicyManager getDpm(Context context) {
    return (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
}

public static ComponentName getAdmin(Context context) {
    return new ComponentName(context, MyDevicePolicyReceiver.class);
}

public static void addMyRestrictions(Context context) {
    getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
    getDpm(context).addUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void clearMyRestrictions(Context context) {
    getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_APPS);
    getDpm(context).clearUserRestriction(getAdmin(context), UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
}

public static void installPackage(Context context, InputStream inputStream)
        throws IOException {
    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
    int sessionId = packageInstaller.createSession(new PackageInstaller
            .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));

    //openSession checks for user restrictions
    clearMyRestrictions(context);
    PackageInstaller.Session session = packageInstaller.openSession(sessionId);

    long sizeBytes = 0;

    OutputStream out = null;
    out = session.openWrite("my_app_session", 0, sizeBytes);

    int total = 0;
    byte[] buffer = new byte[65536];
    int c;
    while ((c = inputStream.read(buffer)) != -1) {
        total += c;
        out.write(buffer, 0, c);
    }
    session.fsync(out);
    inputStream.close();
    out.close();

    // fake intent
    IntentSender statusReceiver = null;
    Intent intent = new Intent(context, SomeActivity.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
            1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    session.commit(pendingIntent.getIntentSender());
    session.close();
}

In Android Api-21 below is code snippet via which we can install apk silently.

private void runInstallWrite() throws IOException, RemoteException {
        long sizeBytes = -1;

        String opt;
        while ((opt = nextOption()) != null) {
            if (opt.equals("-S")) {
                sizeBytes = Long.parseLong(nextOptionData());
            } else {
                throw new IllegalArgumentException("Unknown option: " + opt);
            }
        }

        final int sessionId = Integer.parseInt(nextArg());
        final String splitName = nextArg();

        String path = nextArg();
        if ("-".equals(path)) {
            path = null;
        } else if (path != null) {
            final File file = new File(path);
            if (file.isFile()) {
                sizeBytes = file.length();
            }
        }

        final SessionInfo info = mInstaller.getSessionInfo(sessionId);

        PackageInstaller.Session session = null;
        InputStream in = null;
        OutputStream out = null;
        try {
            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));

            if (path != null) {
                in = new FileInputStream(path);
            } else {
                in = new SizedInputStream(System.in, sizeBytes);
            }
            out = session.openWrite(splitName, 0, sizeBytes);

            int total = 0;
            byte[] buffer = new byte[65536];
            int c;
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);

                if (info.sizeBytes > 0) {
                    final float fraction = ((float) c / (float) info.sizeBytes);
                    session.addProgress(fraction);
                }
            }
            session.fsync(out);

            System.out.println("Success: streamed " + total + " bytes");
        } finally {
            IoUtils.closeQuietly(out);
            IoUtils.closeQuietly(in);
            IoUtils.closeQuietly(session);
        }
    }

The above code is been took from Framework here

Can i use this code with device_owner or normal user in LoLiipop ?

Answer - No Since there are apis which is been @hide tag in android frameworks, although PackageManager.Session is introduced in API 21 but we cannot use new PAckageManager.Session() since it @hide in API 21.

If you wanna still use this code via framework.jar , you need to build Lolippop source code and extract jar from out/..../framework.jar and call above apis.

INSTALL:

Intent promptInstall = new Intent(Intent.ACTION_VIEW);
        promptInstall.setDataAndType(Uri.fromFile(new File(Environment
                .getExternalStorageDirectory() + "/download/" + APK_NAME)),
                "application/vnd.android.package-archive");
        promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(promptInstall);

UNINSTALL:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!