Skip to content

Commit

Permalink
add automatic installation of play apps into work profile
Browse files Browse the repository at this point in the history
Currently when using any DPC app (like Google Device Policy or others)
that uses play services in any way it expects it to be present in the
newly created work profile, as this is the case on phones with
un-sandboxed-gapps.

That's currently broken on GOS due to play apps not being global here.

This can be solved with installing the play services into the work
profile.

We automatically detect if the app needs play services and install them
into the work profile alongside it.

Because this needs to happen at the earliest possible moments in order
for the DPC app to do it's job properly (mainly Google Device Policy,
likely others aswell), this needs to be part of the profile creation
itself.
  • Loading branch information
mkg20001 committed Sep 6, 2024
1 parent f9ebbcd commit 8d66946
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import android.Manifest;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.DialogFragment;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class InstallStart extends Activity {
private PackageManager mPackageManager;
private PackageInstaller mPackageInstaller;
private UserManager mUserManager;
private AppOpsManager mAppOpsManager;
private boolean mAbortInstall = false;
private boolean mShouldFinish = true;

Expand All @@ -78,6 +80,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
mPackageManager = getPackageManager();
mPackageInstaller = mPackageManager.getPackageInstaller();
mUserManager = getSystemService(UserManager.class);
mAppOpsManager = getSystemService(AppOpsManager.class);

Intent intent = getIntent();
String callingPackage = getLaunchedFromPackage();
Expand Down Expand Up @@ -155,7 +158,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
mAbortInstall = true;
}

checkDevicePolicyRestrictions(isTrustedSource);
// During RUNTIME an app store that is not a trusted source cannot install
// anything when the work profile has been restricted to not allow unknown sources

// In order to work around this, USER_TRUSTED_SOURCE flag is created
// This will allow trusting this source, but only for this user/profile
// and only disable some checks, like device policy restrictions on unknown sources

boolean isUserTrustedSource = false;
if (callingPackage != null && !isTrustedSource) {
isUserTrustedSource =
mAppOpsManager.checkOp(AppOpsManager.OP_USER_TRUSTED_SOURCE, callingUid, callingPackage)
== AppOpsManager.MODE_ALLOWED;
Log.i(TAG, "Calling package " + callingPackage + " isUserTrustedSource=" + isUserTrustedSource);
}

checkDevicePolicyRestrictions(isTrustedSource || isUserTrustedSource);

final String installerPackageNameFromIntent = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public List<ParsedUsesPermissionImpl> addUsesPermissions() {
res.addAll(createUsesPerms(
Manifest.permission.REQUEST_INSTALL_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES,
Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION
Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION,
Manifest.permission.USER_TRUSTED_SOURCE
));
return res;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,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.location.Location;
Expand Down Expand Up @@ -584,7 +585,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 @@ -11149,7 +11150,7 @@ private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
if (mOwners.hasDeviceOwner()) {
return false;
}

final ComponentName profileOwner = getProfileOwnerAsUser(userId);
if (profileOwner == null) {
return false;
Expand All @@ -11158,7 +11159,7 @@ private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
if (isManagedProfile(userId)) {
return false;
}

return true;
}
private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
Expand Down Expand Up @@ -12622,6 +12623,7 @@ public UserHandle createAndManageUser(ComponentName admin, String name,
final long id = mInjector.binderClearCallingIdentity();
try {
maybeInstallDevicePolicyManagementRoleHolderInUser(userHandle);
// maybeInstallPlay(userHandle);

manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
/* showDisclaimer= */ true);
Expand Down Expand Up @@ -21469,6 +21471,7 @@ public UserHandle createAndProvisionManagedProfile(
callerPackage);

maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
maybeInstallPlay(userInfo.id, caller.getUserId(), new String[]{admin.getPackageName()});

installExistingAdminPackage(userInfo.id, admin.getPackageName());
if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
Expand Down Expand Up @@ -21593,6 +21596,103 @@ private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId
}
}

/**
* 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
*/
private 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) {
installPlay(targetUserId, callerUserId);
}
}
/**
* GrapheneOS Handler to install sandboxed play into managed user profile
* in order to allow DPC apps that require play services to work normally
*/
private void installPlay(int targetUserId, int callerUserId) {
// TODO: possibly copy permissions from existing install in managing user?
Slogf.i(LOG_TAG, "Installing play for user " + targetUserId);

List<String> playPkgList = Arrays.asList(PackageId.GSF_NAME, PackageId.GMS_CORE_NAME, PackageId.PLAY_STORE_NAME);

boolean playAllAvailableOnSystem = true;

try {
for (final String playPkg : playPkgList) {
if (mIPackageManager.getApplicationInfo(playPkg, 0, callerUserId) == null) {
playAllAvailableOnSystem = false;
Slogf.w(LOG_TAG, "Play package missing: " + 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;
}

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
// It will appear as "set by admin"

final int storeUid = mIPackageManager.getPackageUid(
PackageId.PLAY_STORE_NAME, /* flags= */ 0, targetUserId);
mInjector.getAppOpsManager().setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, storeUid,
PackageId.PLAY_STORE_NAME, MODE_ALLOWED);

Slogf.d(LOG_TAG, "Granting USER_TRUSTED_SOURCE to Play Store");

mInjector.getAppOpsManager().setMode(AppOpsManager.OP_USER_TRUSTED_SOURCE, storeUid,
PackageId.PLAY_STORE_NAME, MODE_ALLOWED);
} catch (RemoteException e) {
// Does not happen, same process
}
}

/**
* If multiple packages hold the role, returns the first package in the list.
*/
Expand Down Expand Up @@ -24471,7 +24571,7 @@ private void migrateAccountManagementDisabledPolicyLocked() {
}
});
}

private void migrateUserControlDisabledPackagesLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
Expand Down

0 comments on commit 8d66946

Please sign in to comment.