Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08841efc authored by arangelov's avatar arangelov
Browse files

Add profile owner transfer functionality.

Test: cts-tradefed run cts-dev --module DevicePolicyManager --test com.android.cts.devicepolicy.TransferProfileOwnerTest
Test: bit FrameworksServicesTests:com.android.server.devicepolicy.DevicePolicyManagerTest
Bug: 69542817

Change-Id: I824fcb334e0ca3157fb67920f7583b309a14bf85
parent a1ac208a
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -8753,4 +8753,44 @@ public class DevicePolicyManager {
            throw re.rethrowFromSystemServer();
        }
    }

    //TODO STOPSHIP Add link to onTransferComplete callback when implemented.
    /**
     * Transfers the current administrator. All policies from the current administrator are
     * migrated to the new administrator. The whole operation is atomic - the transfer is either
     * complete or not done at all.
     *
     * Depending on the current administrator (device owner, profile owner, corporate owned
     * profile owner), you have the following expected behaviour:
     * <ul>
     *     <li>A device owner can only be transferred to a new device owner</li>
     *     <li>A profile owner can only be transferred to a new profile owner</li>
     *     <li>A corporate owned managed profile can have two cases:
     *          <ul>
     *              <li>If the device owner and profile owner are the same package,
     *              both will be transferred.</li>
     *              <li>If the device owner and profile owner are different packages,
     *              and if this method is called from the profile owner, only the profile owner
     *              is transferred. Similarly, if it is called from the device owner, only
     *              the device owner is transferred.</li>
     *          </ul>
     *     </li>
     * </ul>
     *
     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
     * @param target Which {@link DeviceAdminReceiver} we want the new administrator to be.
     * @param bundle Parameters - This bundle allows the current administrator to pass data to the
     *               new administrator. The parameters will be received in the
     *               onTransferComplete callback.
     * @hide
     */
    public void transferOwner(@NonNull ComponentName admin, @NonNull ComponentName target,
            PersistableBundle bundle) {
        throwIfParentInstance("transferOwner");
        try {
            mService.transferOwner(admin, target, bundle);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -378,4 +378,5 @@ interface IDevicePolicyManager {
    boolean isLogoutEnabled();

    List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction);
    void transferOwner(in ComponentName admin, in ComponentName target, in PersistableBundle bundle);
}
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.server.devicepolicy;

import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.os.PersistableBundle;

import com.android.internal.R;
import com.android.server.SystemService;
@@ -57,4 +58,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
    abstract void handleStopUser(int userId);
    
    public void setSystemSetting(ComponentName who, String setting, String value){}

    public void transferOwner(ComponentName admin, ComponentName target, PersistableBundle bundle) {}
}
+109 −20
Original line number Diff line number Diff line
@@ -751,7 +751,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
        private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";

        final DeviceAdminInfo info;
        DeviceAdminInfo info;


        static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
@@ -1402,6 +1402,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            return userRestrictions;
        }

        public void transfer(DeviceAdminInfo deviceAdminInfo) {
            if (hasParentActiveAdmin()) {
                parentAdmin.info = deviceAdminInfo;
            }
            info = deviceAdminInfo;
        }

        void dump(String prefix, PrintWriter pw) {
            pw.print(prefix); pw.print("uid="); pw.println(getUid());
            pw.print(prefix); pw.print("testOnlyAdmin=");
@@ -2541,7 +2548,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {


    public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle,
            boolean throwForMissiongPermission) {
            boolean throwForMissingPermission) {
        if (!mHasFeature) {
            return null;
        }
@@ -2564,7 +2571,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            final String message = "DeviceAdminReceiver " + adminName + " must be protected with "
                    + permission.BIND_DEVICE_ADMIN;
            Slog.w(LOG_TAG, message);
            if (throwForMissiongPermission &&
            if (throwForMissingPermission &&
                    ai.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
                throw new IllegalArgumentException(message);
            }
@@ -2889,7 +2896,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    try {
                        DeviceAdminInfo dai = findAdmin(
                                ComponentName.unflattenFromString(name), userHandle,
                                /* throwForMissionPermission= */ false);
                                /* throwForMissingPermission= */ false);
                        if (VERBOSE_LOG
                                && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
                                != userHandle)) {
@@ -3293,19 +3300,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {

        DevicePolicyData policy = getUserData(userHandle);
        DeviceAdminInfo info = findAdmin(adminReceiver, userHandle,
                /* throwForMissionPermission= */ true);
        if (info == null) {
            throw new IllegalArgumentException("Bad admin: " + adminReceiver);
        }
        if (!info.getActivityInfo().applicationInfo.isInternal()) {
            throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
                    + adminReceiver);
        }
        if (info.getActivityInfo().applicationInfo.isInstantApp()) {
            throw new IllegalArgumentException("Instant apps cannot be device admins: "
                    + adminReceiver);
        }
                /* throwForMissingPermission= */ true);
        synchronized (this) {
            checkActiveAdminPrecondition(adminReceiver, info, policy);
            long ident = mInjector.binderClearCallingIdentity();
            try {
                final ActiveAdmin existingAdmin
@@ -3313,10 +3310,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                if (!refreshing && existingAdmin != null) {
                    throw new IllegalArgumentException("Admin is already added");
                }
                if (policy.mRemovingAdmins.contains(adminReceiver)) {
                    throw new IllegalArgumentException(
                            "Trying to set an admin which is being removed");
                }
                ActiveAdmin newAdmin = new ActiveAdmin(info, /* parent */ false);
                newAdmin.testOnlyAdmin =
                        (existingAdmin != null) ? existingAdmin.testOnlyAdmin
@@ -3346,6 +3339,46 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }

    private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver,
            ComponentName outgoingReceiver, int userHandle) {
        final DevicePolicyData policy = getUserData(userHandle);
        final DeviceAdminInfo incomingDeviceInfo = findAdmin(incomingReceiver, userHandle,
            /* throwForMissingPermission= */ true);
        final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
        final int oldAdminUid = adminToTransfer.getUid();

        adminToTransfer.transfer(incomingDeviceInfo);
        policy.mAdminMap.remove(outgoingReceiver);
        policy.mAdminMap.put(incomingReceiver, adminToTransfer);
        if (policy.mPasswordOwner == oldAdminUid) {
            policy.mPasswordOwner = adminToTransfer.getUid();
        }

        saveSettingsLocked(userHandle);
        //TODO: Make sure we revert back when we detect a failure.
        sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
                null, null);
    }

    private void checkActiveAdminPrecondition(ComponentName adminReceiver, DeviceAdminInfo info,
            DevicePolicyData policy) {
        if (info == null) {
            throw new IllegalArgumentException("Bad admin: " + adminReceiver);
        }
        if (!info.getActivityInfo().applicationInfo.isInternal()) {
            throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
                    + adminReceiver);
        }
        if (info.getActivityInfo().applicationInfo.isInstantApp()) {
            throw new IllegalArgumentException("Instant apps cannot be device admins: "
                    + adminReceiver);
        }
        if (policy.mRemovingAdmins.contains(adminReceiver)) {
            throw new IllegalArgumentException(
                    "Trying to set an admin which is being removed");
        }
    }

    @Override
    public boolean isAdminActive(ComponentName adminReceiver, int userHandle) {
        if (!mHasFeature) {
@@ -11538,4 +11571,60 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        return new ArrayList<>(
                mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
    }

    //TODO: Add callback information to the javadoc once it is completed.
    //TODO: Make transferOwner atomic.
    @Override
    public void transferOwner(ComponentName admin, ComponentName target, PersistableBundle bundle) {
        if (!mHasFeature) {
            return;
        }

        Preconditions.checkNotNull(admin, "Admin cannot be null.");
        Preconditions.checkNotNull(target, "Target cannot be null.");

        enforceProfileOrDeviceOwner(admin);

        if (admin.equals(target)) {
            throw new IllegalArgumentException("Provided administrator and target are "
                    + "the same object.");
        }

        if (admin.getPackageName().equals(target.getPackageName())) {
            throw new IllegalArgumentException("Provided administrator and target have "
                    + "the same package name.");
        }

        final int callingUserId = mInjector.userHandleGetCallingUserId();
        final DevicePolicyData policy = getUserData(callingUserId);
        final DeviceAdminInfo incomingDeviceInfo = findAdmin(target, callingUserId,
                /* throwForMissingPermission= */ true);
        checkActiveAdminPrecondition(target, incomingDeviceInfo, policy);

        final long id = mInjector.binderClearCallingIdentity();
        try {
            //STOPSHIP add support for COMP, DO, edge cases when device is rebooted/work mode off,
            //transfer callbacks and broadcast
            if (isProfileOwner(admin, callingUserId)) {
                transferProfileOwner(admin, target, callingUserId);
            }
        } finally {
            mInjector.binderRestoreCallingIdentity(id);
        }
    }

    /**
     * Transfers the profile owner for user with id profileOwnerUserId from admin to target.
     */
    private void transferProfileOwner(ComponentName admin, ComponentName target,
            int profileOwnerUserId) {
        synchronized (this) {
            transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
            mOwners.transferProfileOwner(target, profileOwnerUserId);
            Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
            mOwners.writeProfileOwner(profileOwnerUserId);
            mDeviceAdminServiceController.startServiceForOwner(
                    target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
        }
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -277,6 +277,17 @@ class Owners {
        }
    }

    void transferProfileOwner(ComponentName target, int userId) {
        synchronized (mLock) {
            final OwnerInfo ownerInfo = mProfileOwners.get(userId);
            final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target,
                    ownerInfo.userRestrictionsMigrated, ownerInfo.remoteBugreportUri,
                    ownerInfo.remoteBugreportHash);
            mProfileOwners.put(userId, newOwnerInfo);
            pushToPackageManagerLocked();
        }
    }

    ComponentName getProfileOwnerComponent(int userId) {
        synchronized (mLock) {
            OwnerInfo profileOwner = mProfileOwners.get(userId);