From d336f843bd14c67b34ab240a953a77e0fcee4903 Mon Sep 17 00:00:00 2001 From: Robert Horvath Date: Wed, 8 Apr 2020 17:05:05 +0200 Subject: [PATCH] Add PackageInstaller#uninstallExistingPackage This new API allows an app to be uninstalled silently by any app holding the DELETE_PACKAGES permission, as long as the app is installed in another user so won't be fully removed from the device. Bug: 149601842 Test: atest UninstallExistingPackageTest Change-Id: I69fe4d1dd4e9da83574b431257f7be6d1ac8b2bb --- api/current.txt | 1 + .../android/content/pm/IPackageInstaller.aidl | 3 ++ .../android/content/pm/IPackageManager.aidl | 10 +++++ .../android/content/pm/PackageInstaller.java | 19 +++++++++ .../server/pm/PackageInstallerService.java | 15 +++++++ .../server/pm/PackageManagerService.java | 39 +++++++++++++++++++ 6 files changed, 87 insertions(+) diff --git a/api/current.txt b/api/current.txt index ecee4f5ecb9f9..c3d82348594b7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11801,6 +11801,7 @@ package android.content.pm { method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender); method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender); + method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public void uninstallExistingPackage(@NonNull String, @Nullable android.content.IntentSender); method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback); method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap); method public void updateSessionAppLabel(int, @Nullable CharSequence); diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index af89c1c06c1e3..2147c397725b0 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -52,6 +52,9 @@ interface IPackageInstaller { void uninstall(in VersionedPackage versionedPackage, String callerPackageName, int flags, in IntentSender statusReceiver, int userId); + void uninstallExistingPackage(in VersionedPackage versionedPackage, String callerPackageName, + in IntentSender statusReceiver, int userId); + void installExistingPackage(String packageName, int installFlags, int installReason, in IntentSender statusReceiver, int userId, in List whiteListedPermissions); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 8bebafff37f02..f257326904fdd 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -235,6 +235,16 @@ interface IPackageManager { void deletePackageVersioned(in VersionedPackage versionedPackage, IPackageDeleteObserver2 observer, int userId, int flags); + /** + * Delete a package for a specific user. + * + * @param versionedPackage The package to delete. + * @param observer a callback to use to notify when the package deletion in finished. + * @param userId the id of the user for whom to delete the package + */ + void deleteExistingPackageAsUser(in VersionedPackage versionedPackage, + IPackageDeleteObserver2 observer, int userId); + @UnsupportedAppUsage String getInstallerPackageName(in String packageName); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index ce89a805cea20..6c2fe69cdcc41 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -723,6 +723,25 @@ public class PackageInstaller { } } + /** + * Uninstall the given package for the user for which this installer was created if the package + * will still exist for other users on the device. + * + * @param packageName The package to install. + * @param statusReceiver Where to deliver the result. + */ + @RequiresPermission(Manifest.permission.DELETE_PACKAGES) + public void uninstallExistingPackage(@NonNull String packageName, + @Nullable IntentSender statusReceiver) { + Objects.requireNonNull(packageName, "packageName cannot be null"); + try { + mInstaller.uninstallExistingPackage( + new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), + mInstallerPackageName, statusReceiver, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** {@hide} */ @SystemApi diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index ed4236a48a294..491abc320e076 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -936,6 +936,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + @Override + public void uninstallExistingPackage(VersionedPackage versionedPackage, + String callerPackageName, IntentSender statusReceiver, int userId) { + final int callingUid = Binder.getCallingUid(); + mContext.enforceCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES, null); + mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); + if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) { + mAppOps.checkPackage(callingUid, callerPackageName); + } + + final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, + statusReceiver, versionedPackage.getPackageName(), false, userId); + mPm.deleteExistingPackageAsUser(versionedPackage, adapter.getBinder(), userId); + } + @Override public void installExistingPackage(String packageName, int installFlags, int installReason, IntentSender statusReceiver, int userId, List whiteListedPermissions) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 59ebd25b7a883..c304887ae2f4b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -17698,9 +17698,47 @@ public class PackageManagerService extends IPackageManager.Stub new LegacyPackageDeleteObserver(observer).getBinder(), userId, flags); } + @Override + public void deleteExistingPackageAsUser(VersionedPackage versionedPackage, + final IPackageDeleteObserver2 observer, final int userId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.DELETE_PACKAGES, null); + Preconditions.checkNotNull(versionedPackage); + Preconditions.checkNotNull(observer); + final String packageName = versionedPackage.getPackageName(); + final long versionCode = versionedPackage.getLongVersionCode(); + + int installedForUsersCount = 0; + synchronized (mLock) { + // Normalize package name to handle renamed packages and static libs + final String internalPkgName = resolveInternalPackageNameLPr(packageName, versionCode); + final PackageSetting ps = mSettings.getPackageLPr(internalPkgName); + if (ps != null) { + int[] installedUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true); + installedForUsersCount = installedUsers.length; + } + } + + if (installedForUsersCount > 1) { + deletePackageVersionedInternal(versionedPackage, observer, userId, 0, true); + } else { + try { + observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR, + null); + } catch (RemoteException re) { + } + } + } + @Override public void deletePackageVersioned(VersionedPackage versionedPackage, final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) { + deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false); + } + + private void deletePackageVersionedInternal(VersionedPackage versionedPackage, + final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags, + final boolean allowSilentUninstall) { final int callingUid = Binder.getCallingUid(); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DELETE_PACKAGES, null); @@ -17721,6 +17759,7 @@ public class PackageManagerService extends IPackageManager.Stub final int uid = Binder.getCallingUid(); if (!isOrphaned(internalPackageName) + && !allowSilentUninstall && !isCallerAllowedToSilentlyUninstall(uid, internalPackageName)) { mHandler.post(() -> { try {