Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit b6d9f9e

Browse files
committed
add automatic installation of play apps into work profile
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.
1 parent c5d1d0f commit b6d9f9e

File tree

3 files changed

+125
-6
lines changed

3 files changed

+125
-6
lines changed

packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import android.Manifest;
2323
import android.app.Activity;
24+
import android.app.AppOpsManager;
2425
import android.app.DialogFragment;
2526
import android.app.admin.DevicePolicyManager;
2627
import android.content.ContentResolver;
@@ -54,6 +55,7 @@ public class InstallStart extends Activity {
5455

5556
private PackageManager mPackageManager;
5657
private UserManager mUserManager;
58+
private AppOpsManager mAppOpsManager;
5759
private boolean mAbortInstall = false;
5860
private boolean mShouldFinish = true;
5961

@@ -78,6 +80,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
7880
}
7981
mPackageManager = getPackageManager();
8082
mUserManager = getSystemService(UserManager.class);
83+
mAppOpsManager = getSystemService(AppOpsManager.class);
8184

8285
Intent intent = getIntent();
8386
String callingPackage = getCallingPackage();
@@ -152,7 +155,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
152155
mAbortInstall = true;
153156
}
154157

155-
checkDevicePolicyRestrictions(isTrustedSource);
158+
// During RUNTIME an app store that is not a trusted source cannot install
159+
// anything when the work profile has been restricted to not allow unknown sources
160+
161+
// In order to work around this, USER_TRUSTED_SOURCE flag is created
162+
// This will allow trusting this source, but only for this user/profile
163+
// and only disable some checks, like device policy restrictions on unknown sources
164+
165+
boolean isUserTrustedSource = false;
166+
if (callingPackage != null && !isTrustedSource) {
167+
isUserTrustedSource =
168+
mAppOpsManager.checkOp(AppOpsManager.OP_USER_TRUSTED_SOURCE, callingUid, callingPackage)
169+
== AppOpsManager.MODE_ALLOWED;
170+
Log.i(TAG, "Calling package " + callingPackage + " isUserTrustedSource=" + isUserTrustedSource);
171+
}
172+
173+
checkDevicePolicyRestrictions(isTrustedSource || isUserTrustedSource);
156174

157175
final String installerPackageNameFromIntent = getIntent().getStringExtra(
158176
Intent.EXTRA_INSTALLER_PACKAGE_NAME);

services/core/java/com/android/server/pm/ext/PlayStoreHooks.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public List<ParsedUsesPermissionImpl> addUsesPermissions() {
1616
res.addAll(createUsesPerms(
1717
Manifest.permission.REQUEST_INSTALL_PACKAGES,
1818
Manifest.permission.REQUEST_DELETE_PACKAGES,
19-
Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION
19+
Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION,
20+
Manifest.permission.USER_TRUSTED_SOURCE
2021
));
2122
return res;
2223
}

services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@
398398
import android.content.res.Resources;
399399
import android.database.ContentObserver;
400400
import android.database.Cursor;
401+
import android.ext.PackageId;
401402
import android.graphics.Bitmap;
402403
import android.hardware.usb.UsbManager;
403404
import android.location.Location;
@@ -581,7 +582,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
581582

582583
private static final String ATTRIBUTION_TAG = "DevicePolicyManagerService";
583584

584-
static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
585+
static final boolean VERBOSE_LOG = true; // DO NOT SUBMIT WITH TRUE
585586

586587
static final String DEVICE_POLICIES_XML = "device_policies.xml";
587588

@@ -10960,7 +10961,7 @@ private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
1096010961
if (mOwners.hasDeviceOwner()) {
1096110962
return false;
1096210963
}
10963-
10964+
1096410965
final ComponentName profileOwner = getProfileOwnerAsUser(userId);
1096510966
if (profileOwner == null) {
1096610967
return false;
@@ -10969,7 +10970,7 @@ private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
1096910970
if (isManagedProfile(userId)) {
1097010971
return false;
1097110972
}
10972-
10973+
1097310974
return true;
1097410975
}
1097510976
private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -12392,6 +12393,7 @@ public UserHandle createAndManageUser(ComponentName admin, String name,
1239212393
final long id = mInjector.binderClearCallingIdentity();
1239312394
try {
1239412395
maybeInstallDevicePolicyManagementRoleHolderInUser(userHandle);
12396+
// maybeInstallPlay(userHandle);
1239512397

1239612398
manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
1239712399
/* showDisclaimer= */ true);
@@ -21117,6 +21119,7 @@ public UserHandle createAndProvisionManagedProfile(
2111721119
callerPackage);
2111821120

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

2112121124
installExistingAdminPackage(userInfo.id, admin.getPackageName());
2112221125
if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
@@ -21241,6 +21244,103 @@ private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId
2124121244
}
2124221245
}
2124321246

21247+
/**
21248+
* Check if app requires play services
21249+
*/
21250+
private boolean requiresPlay(String pkg, int callerUserId) throws RemoteException {
21251+
ApplicationInfo ai = mIPackageManager.getApplicationInfo(pkg, PackageManager.GET_META_DATA, callerUserId);
21252+
if (ai.metaData != null) {
21253+
int playVersion = ai.metaData.getInt("com.google.android.gms.version", -1);
21254+
return playVersion != -1;
21255+
}
21256+
21257+
return false;
21258+
}
21259+
21260+
/**
21261+
* GrapheneOS Handler to check if any app such as role owner in a profile
21262+
* requires play services and install them
21263+
*/
21264+
private void maybeInstallPlay(int targetUserId, int callerUserId, String[] pkgNames) {
21265+
boolean shouldInstall = false;
21266+
21267+
for (String pkgName : pkgNames) {
21268+
try {
21269+
if (requiresPlay(pkgName, callerUserId)) {
21270+
Slogf.i(LOG_TAG, "Detected " + pkgName + " needs play services");
21271+
shouldInstall = true;
21272+
}
21273+
} catch (RemoteException e) {
21274+
// Does not happen, same process
21275+
}
21276+
}
21277+
21278+
if (shouldInstall) {
21279+
installPlay(targetUserId, callerUserId);
21280+
}
21281+
}
21282+
/**
21283+
* GrapheneOS Handler to install sandboxed play into managed user profile
21284+
* in order to allow DPC apps that require play services to work normally
21285+
*/
21286+
private void installPlay(int targetUserId, int callerUserId) {
21287+
// TODO: possibly copy permissions from existing install in managing user?
21288+
Slogf.i(LOG_TAG, "Installing play for user " + targetUserId);
21289+
21290+
List<String> playPkgList = Arrays.asList(PackageId.GSF_NAME, PackageId.GMS_CORE_NAME, PackageId.PLAY_STORE_NAME);
21291+
21292+
boolean playAllAvailableOnSystem = true;
21293+
21294+
try {
21295+
for (final String playPkg : playPkgList) {
21296+
if (mIPackageManager.getApplicationInfo(playPkg, 0, callerUserId) == null) {
21297+
playAllAvailableOnSystem = false;
21298+
Slogf.w(LOG_TAG, "Play package missing: " + playPkg);
21299+
}
21300+
}
21301+
if (playAllAvailableOnSystem) {
21302+
for (final String playPkg : playPkgList) {
21303+
if (mIPackageManager.isPackageAvailable(playPkg, targetUserId)) {
21304+
Slogf.d(LOG_TAG, "The play package "
21305+
+ playPkg + " is already installed in "
21306+
+ "user " + targetUserId);
21307+
continue;
21308+
}
21309+
Slogf.d(LOG_TAG, "Installing play package "
21310+
+ playPkg + " in user " + targetUserId);
21311+
mIPackageManager.installExistingPackageAsUser(
21312+
playPkg,
21313+
targetUserId,
21314+
/* installFlags= */ 0,
21315+
PackageManager.INSTALL_REASON_POLICY,
21316+
/* whiteListedPermissions= */ null);
21317+
}
21318+
} else {
21319+
// TODO: intent to app store to install play packages?
21320+
Slogf.w(LOG_TAG, "Play Services not installed, yet requested for profile!");
21321+
return;
21322+
}
21323+
21324+
Slogf.d(LOG_TAG, "Granting REQUEST_INSTALL_PACKAGES to Play Store");
21325+
21326+
// We need to grant Play Store "Allow from source" / REQUEST_INSTALL_PACKAGES,
21327+
// as this is not possible later if changing that setting is blocked
21328+
// It will appear as "set by admin"
21329+
21330+
final int storeUid = mIPackageManager.getPackageUid(
21331+
PackageId.PLAY_STORE_NAME, /* flags= */ 0, targetUserId);
21332+
mInjector.getAppOpsManager().setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, storeUid,
21333+
PackageId.PLAY_STORE_NAME, MODE_ALLOWED);
21334+
21335+
Slogf.d(LOG_TAG, "Granting USER_TRUSTED_SOURCE to Play Store");
21336+
21337+
mInjector.getAppOpsManager().setMode(AppOpsManager.OP_USER_TRUSTED_SOURCE, storeUid,
21338+
PackageId.PLAY_STORE_NAME, MODE_ALLOWED);
21339+
} catch (RemoteException e) {
21340+
// Does not happen, same process
21341+
}
21342+
}
21343+
2124421344
/**
2124521345
* If multiple packages hold the role, returns the first package in the list.
2124621346
*/
@@ -24051,7 +24151,7 @@ private void migrateAccountManagementDisabledPolicyLocked() {
2405124151
}
2405224152
});
2405324153
}
24054-
24154+
2405524155
private void migrateUserControlDisabledPackagesLocked() {
2405624156
Binder.withCleanCallingIdentity(() -> {
2405724157
List<UserInfo> users = mUserManager.getUsers();

0 commit comments

Comments
 (0)