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

Commit a4ed6819 authored by Maurice Lam's avatar Maurice Lam
Browse files

Implement VirtualDevice's device policy enforcement

The policy defined in DevicePolicyManager.getNearbyAppStreamingPolicy
is implemented in this change. The policy in each profile defines its
own behavior. For example, if a work profile sets the policy as ENABLED,
activities for the work profile can be started on virtual displays
created for the parent (personal) profile as well.

If the policy is SAME_MANAGED_ACCOUNT_ONLY, VirtualDeviceManager will
compare whether the UserHandle associated with the activity is listed as
"usersWithMatchingAccounts", which is supplied by the virtual device
owner.

If usersWithMatchingAccounts is not given, it is treated the same way as
an empty list, in which case SAME_MANAGED_ACCOUNT_ONLY policy has the
same behavior as DISABLED.

Bug: 179910177
Test: To be added in ag/15781910
Change-Id: I0d89aaafecdc579482a38ee8c0bff0cc300d4f41
parent 57cd8018
Loading
Loading
Loading
Loading
+49 −4
Original line number Diff line number Diff line
@@ -23,12 +23,16 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;

/**
 * Params that can be configured when creating virtual devices.
@@ -60,20 +64,40 @@ public final class VirtualDeviceParams implements Parcelable {
    public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;

    private final int mLockState;
    private final ArraySet<UserHandle> mUsersWithMatchingAccounts;

    private VirtualDeviceParams(@LockState int lockState) {
    private VirtualDeviceParams(
            @LockState int lockState,
            @NonNull Set<UserHandle> usersWithMatchingAccounts) {
        mLockState = lockState;
        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
    }

    @SuppressWarnings("unchecked")
    private VirtualDeviceParams(Parcel parcel) {
        mLockState = parcel.readInt();
        mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
    }

    /**
     * Returns the lock state of the virtual device.
     */
    @LockState
    public int getLockState() {
        return mLockState;
    }

    /**
     * Returns the user handles with matching managed accounts on the remote device to which
     * this virtual device is streaming.
     *
     * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
     */
    @NonNull
    public Set<UserHandle> getUsersWithMatchingAccounts() {
        return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
    }

    @Override
    public int describeContents() {
        return 0;
@@ -82,6 +106,7 @@ public final class VirtualDeviceParams implements Parcelable {
    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mLockState);
        dest.writeArraySet(mUsersWithMatchingAccounts);
    }

    @Override
@@ -93,18 +118,20 @@ public final class VirtualDeviceParams implements Parcelable {
            return false;
        }
        VirtualDeviceParams that = (VirtualDeviceParams) o;
        return mLockState == that.mLockState;
        return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
                that.mUsersWithMatchingAccounts);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mLockState);
        return Objects.hash(mLockState, mUsersWithMatchingAccounts);
    }

    @Override
    public String toString() {
        return "VirtualDeviceParams("
                + " mLockState=" + mLockState
                + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
                + ")";
    }

@@ -125,6 +152,7 @@ public final class VirtualDeviceParams implements Parcelable {
    public static final class Builder {

        private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
        private Set<UserHandle> mUsersWithMatchingAccounts;

        /**
         * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -141,12 +169,29 @@ public final class VirtualDeviceParams implements Parcelable {
            return this;
        }

        /**
         * Sets the user handles with matching managed accounts on the remote device to which
         * this virtual device is streaming.
         *
         * @param usersWithMatchingAccounts A set of user handles with matching managed
         *   accounts on the remote device this is streaming to.
         * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
         */
        public Builder setUsersWithMatchingAccounts(
                @NonNull Set<UserHandle> usersWithMatchingAccounts) {
            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
            return this;
        }

        /**
         * Builds the {@link VirtualDeviceParams} instance.
         */
        @NonNull
        public VirtualDeviceParams build() {
            return new VirtualDeviceParams(mLockState);
            if (mUsersWithMatchingAccounts == null) {
                mUsersWithMatchingAccounts = Collections.emptySet();
            }
            return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
        }
    }
}
+36 −19
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.companion.virtual;

import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;

@@ -27,9 +28,10 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
import android.window.DisplayWindowPolicyController;

import java.util.HashSet;
import java.util.List;


@@ -38,6 +40,8 @@ import java.util.List;
 */
class GenericWindowPolicyController extends DisplayWindowPolicyController {

    private static final String TAG = "VirtualDeviceManager";

    /**
     * If required, allow the secure activity to display on remote device since
     * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -45,10 +49,13 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController {
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
    @NonNull private final ArraySet<UserHandle> mAllowedUsers;

    @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
    @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();

    GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
    GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
            @NonNull ArraySet<UserHandle> allowedUsers) {
        mAllowedUsers = allowedUsers;
        setInterestedWindowFlags(windowFlags, systemWindowFlags);
    }

@@ -58,7 +65,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController {
        final int activityCount = activities.size();
        for (int i = 0; i < activityCount; i++) {
            final ActivityInfo aInfo = activities.get(i);
            if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
            if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
                return false;
            }
        }
@@ -68,21 +75,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController {
    @Override
    public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
            int systemWindowFlags) {
        if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
            return false;
        }
        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
                activityInfo.packageName,
                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
            if ((windowFlags & FLAG_SECURE) != 0) {
                return false;
            }
            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
                return false;
            }
        }
        return true;
        return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
    }

    @Override
@@ -105,4 +98,28 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController {
    boolean containsUid(int uid) {
        return mRunningUids.contains(uid);
    }

    private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
            int systemWindowFlags) {
        if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
            return false;
        }
        final UserHandle activityUser =
                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
        if (!mAllowedUsers.contains(activityUser)) {
            Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
            return false;
        }
        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
                activityInfo.packageName, activityUser)) {
            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
            if ((windowFlags & FLAG_SECURE) != 0) {
                return false;
            }
            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
                return false;
            }
        }
        return true;
    }
}
+26 −1
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.server.companion.virtual;

import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;

import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
@@ -34,6 +38,9 @@ import android.hardware.input.VirtualTouchEvent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.SparseArray;
import android.window.DisplayWindowPolicyController;

@@ -286,11 +293,29 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        mVirtualDisplayIds.add(displayId);
        final GenericWindowPolicyController dwpc =
                new GenericWindowPolicyController(FLAG_SECURE,
                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
        mWindowPolicyControllers.put(displayId, dwpc);
        return dwpc;
    }

    private ArraySet<UserHandle> getAllowedUserHandles() {
        ArraySet<UserHandle> result = new ArraySet<>();
        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
        UserManager userManager = mContext.getSystemService(UserManager.class);
        for (UserHandle profile : userManager.getAllProfiles()) {
            int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
            if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
                    || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
                result.add(profile);
            } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
                if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
                    result.add(profile);
                }
            }
        }
        return result;
    }

    void onVirtualDisplayRemovedLocked(int displayId) {
        if (!mVirtualDisplayIds.contains(displayId)) {
            throw new IllegalStateException(
+9 −2
Original line number Diff line number Diff line
@@ -20,19 +20,24 @@ import static com.google.common.truth.Truth.assertThat;

import android.companion.virtual.VirtualDeviceParams;
import android.os.Parcel;
import android.os.UserHandle;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Set;

@RunWith(AndroidJUnit4.class)
public class VirtualDeviceParamsTest {

    @Test
    public void parcelable_shouldRecreateSuccessfully() {
        VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder().setLockState(
                VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED).build();
        VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
                .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
                .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
                .build();
        Parcel parcel = Parcel.obtain();
        originalParams.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
@@ -40,5 +45,7 @@ public class VirtualDeviceParamsTest {
        VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
        assertThat(params).isEqualTo(originalParams);
        assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
        assertThat(params.getUsersWithMatchingAccounts())
                .containsExactly(UserHandle.of(123), UserHandle.of(456));
    }
}