diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index c508e0e4d5c25..1f56af45464fd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -33,7 +33,9 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; +import android.ext.PackageId; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -182,7 +184,22 @@ && checkPermission(Manifest.permission.INSTALL_PACKAGES, /* pid= */ -1, mAbortInstall = true; } - checkDevicePolicyRestrictions(isTrustedSource); + // When a work profile with play services gets created Play Store can't install any apps + // as the work profile may have a policy active to forbid unknown sources as it is not + // aware that play store is not a system app and gets blocked by that policy. + // Here we detect if the play store is genuine and is trying to install an app + // in a work profile. If that is the case we allow it to proceed, despite not being a + // trusted source. + boolean isTrustedPlayStore = false; + try { + isTrustedPlayStore = callingPackage != null && + mPackageManager.getApplicationInfo(callingPackage, 0).ext().getPackageId() == PackageId.PLAY_STORE; + } catch(NameNotFoundException e) { + // Should never happen. + } + boolean isManagedProfile = mUserManager.isManagedProfile(mPackageManager.getUserId()); + + checkDevicePolicyRestrictions(isTrustedSource || (isTrustedPlayStore && isManagedProfile)); final String installerPackageNameFromIntent = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); diff --git a/services/core/java/com/android/server/pm/ext/GmsCoreHooks.java b/services/core/java/com/android/server/pm/ext/GmsCoreHooks.java index db24a73d417f8..1a9eb232b355f 100644 --- a/services/core/java/com/android/server/pm/ext/GmsCoreHooks.java +++ b/services/core/java/com/android/server/pm/ext/GmsCoreHooks.java @@ -75,7 +75,7 @@ static boolean shouldSkipPermissionDefinition(String name) { @Override public List addUsesPermissions() { var res = super.addUsesPermissions(); - var l = createUsesPerms(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Manifest.permission.READ_PHONE_NUMBERS); + var l = createUsesPerms(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.REQUEST_INSTALL_PACKAGES); res.addAll(l); return res; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyGmsHooks.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyGmsHooks.java new file mode 100644 index 0000000000000..0ee9b9a04ead9 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyGmsHooks.java @@ -0,0 +1,148 @@ +package com.android.server.devicepolicy; + +import static android.app.AppOpsManager.MODE_ALLOWED; + + +import android.annotation.SuppressLint; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.ext.PackageId; +import android.os.RemoteException; +import android.provider.Settings; +import android.widget.Toast; + +import com.android.server.utils.Slogf; + +import java.util.Arrays; +import java.util.List; + +public class DevicePolicyGmsHooks { + protected static final String LOG_TAG = "DevicePolicyGmsHooks"; + final IPackageManager mIPackageManager; + final AppOpsManager mIAppOpsManager; + final Context mContext; + + public DevicePolicyGmsHooks(IPackageManager _mIPackageManager, Context _mContext, AppOpsManager _mIAppOpsManager) { + mIPackageManager = _mIPackageManager; + mContext = _mContext; + mIAppOpsManager = _mIAppOpsManager; + } + + /** + * Check if app requires play services + */ + private boolean requiresPlay(String pkg, int callerUserId) throws RemoteException { + ApplicationInfo ai = mIPackageManager.getApplicationInfo(pkg, PackageManager.GET_META_DATA, callerUserId); + if (ai.metaData != null) { + int playVersion = ai.metaData.getInt("com.google.android.gms.version", -1); + return playVersion != -1; + } + + return false; + } + + /** + * GrapheneOS Handler to check if any app such as role owner in a profile + * requires play services and install them + */ + public void maybeInstallPlay(int targetUserId, int callerUserId, String[] pkgNames) { + boolean shouldInstall = false; + + for (String pkgName : pkgNames) { + try { + if (requiresPlay(pkgName, callerUserId)) { + Slogf.i(LOG_TAG, "Detected " + pkgName + " needs play services"); + shouldInstall = true; + } + } catch (RemoteException e) { + // Does not happen, same process + } + } + + if (shouldInstall) { + if (!installPlay(targetUserId, callerUserId)) { + Toast.makeText(mContext.getApplicationContext(), + "Failed to install Google Play in work profile despite requested by work profile App!", + Toast.LENGTH_LONG + ).show(); + } + } + } + /** + * GrapheneOS Handler to install sandboxed play into managed user profile + * in order to allow DPC apps that require play services to work normally + */ + @SuppressLint("MissingPermission") + private boolean installPlay(int targetUserId, int callerUserId) { + // TODO: possibly copy permissions from existing install in managing user? + Slogf.i(LOG_TAG, "Installing play for user " + targetUserId); + + // NOTE: we never need to copy GSF over, since it counts as a fresh install, so existence of GSF in owner is irrelevant. + List playPkgList = Arrays.asList(PackageId.GMS_CORE_NAME, PackageId.PLAY_STORE_NAME); + + boolean playAllAvailableOnSystem = true; + + try { + for (final String playPkg : playPkgList) { + ApplicationInfo ai = mIPackageManager.getApplicationInfo(playPkg, 0, callerUserId); + if (ai == null) { + playAllAvailableOnSystem = false; + Slogf.w(LOG_TAG, "Play package missing: " + playPkg); + continue; + } + + // Check signature + if (ai.ext().getPackageId() != PackageId.PLAY_STORE && ai.ext().getPackageId() != PackageId.GMS_CORE) { + playAllAvailableOnSystem = false; + Slogf.w(LOG_TAG, "Play package is not passing signature checks: " + playPkg); + } + } + if (playAllAvailableOnSystem) { + for (final String playPkg : playPkgList) { + if (mIPackageManager.isPackageAvailable(playPkg, targetUserId)) { + Slogf.d(LOG_TAG, "The play package " + + playPkg + " is already installed in " + + "user " + targetUserId); + continue; + } + Slogf.d(LOG_TAG, "Installing play package " + + playPkg + " in user " + targetUserId); + mIPackageManager.installExistingPackageAsUser( + playPkg, + targetUserId, + /* installFlags= */ 0, + PackageManager.INSTALL_REASON_POLICY, + /* whiteListedPermissions= */ null); + } + } else { + // TODO: intent to app store to install play packages? + Slogf.w(LOG_TAG, "Play Services not installed, yet requested for profile!"); + return false; + } + + /* Signature check. If play store was already installed into profile earlier, + but with untrusted signature (should not happen) then this will throw */ + ApplicationInfo aiStore = mIPackageManager.getApplicationInfo(PackageId.PLAY_STORE_NAME, 0, targetUserId); + assert aiStore.ext().getPackageId() == PackageId.PLAY_STORE; + + Slogf.d(LOG_TAG, "Granting REQUEST_INSTALL_PACKAGES to Play Store"); + + // We need to grant Play Store "Allow from source" / REQUEST_INSTALL_PACKAGES, + // as this is not possible later if changing that setting is blocked by device policy + // The setting will appear as "set by admin" + + final int storeUid = mIPackageManager.getPackageUid( + PackageId.PLAY_STORE_NAME, /* flags= */ 0, targetUserId); + mIAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, storeUid, + PackageId.PLAY_STORE_NAME, MODE_ALLOWED); + + return true; + } catch (RemoteException e) { + // Does not happen, same process + return false; + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index afb95130c82e9..ed70185fa1e79 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -385,6 +385,7 @@ import android.app.admin.flags.Flags; import android.app.backup.IBackupManager; import android.app.compat.CompatChanges; +import android.app.compat.gms.GmsCompat; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.supervision.SupervisionManagerInternal; @@ -428,6 +429,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; +import android.ext.PackageId; import android.graphics.Bitmap; import android.hardware.usb.UsbManager; import android.health.connect.HealthConnectManager; @@ -619,7 +621,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTRIBUTION_TAG = "DevicePolicyManagerService"; - static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE + static final boolean VERBOSE_LOG = true; // DO NOT SUBMIT WITH TRUE static final String DEVICE_POLICIES_XML = "device_policies.xml"; @@ -21621,6 +21623,13 @@ public UserHandle createAndProvisionManagedProfile( maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id, caller); + DevicePolicyGmsHooks hooks = new DevicePolicyGmsHooks(mIPackageManager, mContext, mInjector.getAppOpsManager()); + int userId = userInfo.id; + int callingUserId = caller.getUserId(); + mInjector.binderWithCleanCallingIdentity(() -> { + hooks.maybeInstallPlay(userId, callingUserId, new String[]{admin.getPackageName()}); + }); + installExistingAdminPackage(userInfo.id, admin.getPackageName()); if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) { throw new ServiceSpecificException(