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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user