Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static boolean shouldSkipPermissionDefinition(String name) {
@Override
public List<ParsedUsesPermissionImpl> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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(
Expand Down