Merge "Allow virtual device owners to specify custom policies per policy type."
This commit is contained in:
committed by
Android (Google) Code Review
commit
07e7541ea4
@@ -2909,6 +2909,7 @@ package android.companion.virtual {
|
||||
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
|
||||
method public int getDefaultActivityPolicy();
|
||||
method public int getDefaultNavigationPolicy();
|
||||
method public int getDevicePolicy(int);
|
||||
method public int getLockState();
|
||||
method @Nullable public String getName();
|
||||
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
|
||||
@@ -2916,14 +2917,18 @@ package android.companion.virtual {
|
||||
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
|
||||
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
|
||||
field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
|
||||
field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
|
||||
field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
|
||||
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
|
||||
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
|
||||
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
|
||||
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
|
||||
}
|
||||
|
||||
public static final class VirtualDeviceParams.Builder {
|
||||
ctor public VirtualDeviceParams.Builder();
|
||||
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
|
||||
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
|
||||
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
|
||||
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
|
||||
|
||||
@@ -51,6 +51,11 @@ interface IVirtualDeviceManager {
|
||||
*/
|
||||
List<VirtualDevice> getVirtualDevices();
|
||||
|
||||
/**
|
||||
* Returns the device policy for the given virtual device and policy type.
|
||||
*/
|
||||
int getDevicePolicy(int deviceId, int policyType);
|
||||
|
||||
/**
|
||||
* Creates a virtual display owned by a particular virtual device.
|
||||
*
|
||||
|
||||
@@ -181,6 +181,28 @@ public final class VirtualDeviceManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device policy for the given virtual device and policy type.
|
||||
*
|
||||
* <p>In case the virtual device identifier is not valid, or there's no explicitly specified
|
||||
* policy for that device and policy type, then
|
||||
* {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
|
||||
int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
|
||||
return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
|
||||
}
|
||||
try {
|
||||
return mService.getDevicePolicy(deviceId, policyType);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
|
||||
* creator of a virtual device can take the output from the virtual display and stream it over
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -103,6 +104,47 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
*/
|
||||
public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "DEVICE_POLICY_", value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
|
||||
public @interface DevicePolicy {}
|
||||
|
||||
/**
|
||||
* Indicates that there is no special logic for this virtual device and it should be treated
|
||||
* the same way as the default device, keeping the default behavior unchanged.
|
||||
*/
|
||||
public static final int DEVICE_POLICY_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Indicates that there is custom logic, specific to this virtual device, which should be
|
||||
* triggered instead of the default behavior.
|
||||
*/
|
||||
public static final int DEVICE_POLICY_CUSTOM = 1;
|
||||
|
||||
/**
|
||||
* Any relevant component must be able to interpret the correct meaning of a custom policy for
|
||||
* a given policy type.
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
|
||||
public @interface PolicyType {}
|
||||
|
||||
/**
|
||||
* Tells the sensor framework how to handle sensor requests from contexts associated with this
|
||||
* virtual device, namely the sensors returned by
|
||||
* {@link android.hardware.SensorManager#getSensorList}:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device.
|
||||
* <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if
|
||||
* the virtual device did not create any virtual sensors, then an empty list is returned.
|
||||
* </ul>
|
||||
*/
|
||||
public static final int POLICY_TYPE_SENSORS = 0;
|
||||
|
||||
private final int mLockState;
|
||||
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
|
||||
@NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
|
||||
@@ -114,6 +156,8 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
@ActivityPolicy
|
||||
private final int mDefaultActivityPolicy;
|
||||
@Nullable private final String mName;
|
||||
// Mapping of @PolicyType to @DevicePolicy
|
||||
@NonNull private final SparseIntArray mDevicePolicies;
|
||||
|
||||
private VirtualDeviceParams(
|
||||
@LockState int lockState,
|
||||
@@ -124,12 +168,14 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
@NonNull Set<ComponentName> allowedActivities,
|
||||
@NonNull Set<ComponentName> blockedActivities,
|
||||
@ActivityPolicy int defaultActivityPolicy,
|
||||
@Nullable String name) {
|
||||
@Nullable String name,
|
||||
@NonNull SparseIntArray devicePolicies) {
|
||||
Preconditions.checkNotNull(usersWithMatchingAccounts);
|
||||
Preconditions.checkNotNull(allowedCrossTaskNavigations);
|
||||
Preconditions.checkNotNull(blockedCrossTaskNavigations);
|
||||
Preconditions.checkNotNull(allowedActivities);
|
||||
Preconditions.checkNotNull(blockedActivities);
|
||||
Preconditions.checkNotNull(devicePolicies);
|
||||
|
||||
mLockState = lockState;
|
||||
mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
|
||||
@@ -140,6 +186,7 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
mBlockedActivities = new ArraySet<>(blockedActivities);
|
||||
mDefaultActivityPolicy = defaultActivityPolicy;
|
||||
mName = name;
|
||||
mDevicePolicies = devicePolicies;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -153,6 +200,7 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
|
||||
mDefaultActivityPolicy = parcel.readInt();
|
||||
mName = parcel.readString8();
|
||||
mDevicePolicies = parcel.readSparseIntArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,6 +306,16 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
|
||||
* policy for this type has been explicitly specified.
|
||||
*
|
||||
* @see Builder#addDevicePolicy
|
||||
*/
|
||||
public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
|
||||
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -274,6 +332,7 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
dest.writeArraySet(mBlockedActivities);
|
||||
dest.writeInt(mDefaultActivityPolicy);
|
||||
dest.writeString8(mName);
|
||||
dest.writeSparseIntArray(mDevicePolicies);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -285,6 +344,18 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
VirtualDeviceParams that = (VirtualDeviceParams) o;
|
||||
final int devicePoliciesCount = mDevicePolicies.size();
|
||||
if (devicePoliciesCount != that.mDevicePolicies.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < devicePoliciesCount; i++) {
|
||||
if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) {
|
||||
return false;
|
||||
}
|
||||
if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return mLockState == that.mLockState
|
||||
&& mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
|
||||
&& Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations)
|
||||
@@ -298,10 +369,15 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
int hashCode = Objects.hash(
|
||||
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
|
||||
mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
|
||||
mBlockedActivities, mDefaultActivityPolicy, mName);
|
||||
mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
|
||||
for (int i = 0; i < mDevicePolicies.size(); i++) {
|
||||
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
|
||||
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -317,6 +393,7 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
+ " mBlockedActivities=" + mBlockedActivities
|
||||
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
|
||||
+ " mName=" + mName
|
||||
+ " mDevicePolicies=" + mDevicePolicies
|
||||
+ ")";
|
||||
}
|
||||
|
||||
@@ -350,6 +427,7 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
|
||||
private boolean mDefaultActivityPolicyConfigured = false;
|
||||
@Nullable private String mName;
|
||||
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
|
||||
|
||||
/**
|
||||
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
|
||||
@@ -527,6 +605,18 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a policy for this virtual device.
|
||||
*
|
||||
* @param policyType the type of policy, i.e. which behavior to specify a policy for.
|
||||
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
|
||||
mDevicePolicies.put(policyType, devicePolicy);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link VirtualDeviceParams} instance.
|
||||
*/
|
||||
@@ -541,7 +631,8 @@ public final class VirtualDeviceParams implements Parcelable {
|
||||
mAllowedActivities,
|
||||
mBlockedActivities,
|
||||
mDefaultActivityPolicy,
|
||||
mName);
|
||||
mName,
|
||||
mDevicePolicies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
|
||||
return mParams.getName();
|
||||
}
|
||||
|
||||
/** Returns the policy specified for this policy type */
|
||||
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
|
||||
@VirtualDeviceParams.PolicyType int policyType) {
|
||||
return mParams.getDevicePolicy(policyType);
|
||||
}
|
||||
|
||||
/** Returns the unique device ID of this device. */
|
||||
@Override // Binder call
|
||||
public int getDeviceId() {
|
||||
|
||||
@@ -233,6 +233,13 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
mLocalService.onAppsOnVirtualDeviceChanged();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
|
||||
synchronized (mVirtualDeviceManagerLock) {
|
||||
mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
|
||||
VirtualDeviceImpl.PendingTrampolineCallback {
|
||||
|
||||
@@ -358,6 +365,12 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
return virtualDevices;
|
||||
}
|
||||
|
||||
@Override // BinderCall
|
||||
@VirtualDeviceParams.DevicePolicy
|
||||
public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
|
||||
return mLocalService.getDevicePolicy(deviceId, policyType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
|
||||
final int callingUserId = getCallingUserHandle().getIdentifier();
|
||||
@@ -438,6 +451,20 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VirtualDeviceParams.DevicePolicy
|
||||
public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
|
||||
synchronized (mVirtualDeviceManagerLock) {
|
||||
for (int i = 0; i < mVirtualDevices.size(); i++) {
|
||||
final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
|
||||
if (device.getDeviceId() == deviceId) {
|
||||
return device.getDevicePolicy(policyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualDisplayCreated(int displayId) {
|
||||
final VirtualDisplayListener[] listeners;
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.companion.virtual;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.companion.virtual.IVirtualDevice;
|
||||
import android.companion.virtual.VirtualDeviceParams;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -109,4 +110,14 @@ public abstract class VirtualDeviceManagerInternal {
|
||||
* Returns true if the {@code displayId} is owned by any virtual device
|
||||
*/
|
||||
public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
|
||||
|
||||
/**
|
||||
* Returns the device policy for the given virtual device and policy type.
|
||||
*
|
||||
* <p>In case the virtual device identifier is not valid, or there's no explicitly specified
|
||||
* policy for that device and policy type, then
|
||||
* {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
|
||||
*/
|
||||
public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
|
||||
int deviceId, @VirtualDeviceParams.PolicyType int policyType);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server.companion.virtual;
|
||||
|
||||
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
|
||||
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
|
||||
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
|
||||
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
@@ -44,6 +47,7 @@ import android.app.WindowConfiguration;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.virtual.IVirtualDeviceActivityListener;
|
||||
import android.companion.virtual.VirtualDeviceManager;
|
||||
import android.companion.virtual.VirtualDeviceParams;
|
||||
import android.companion.virtual.audio.IAudioConfigChangedCallback;
|
||||
import android.companion.virtual.audio.IAudioRoutingCallback;
|
||||
@@ -240,6 +244,55 @@ public class VirtualDeviceManagerServiceTest {
|
||||
mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
|
||||
mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
|
||||
mActivityListener, mRunningAppsChangedCallback, params);
|
||||
mVdms.addVirtualDevice(mDeviceImpl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDevicePolicy_invalidDeviceId_returnsDefault() {
|
||||
assertThat(
|
||||
mLocalService.getDevicePolicy(
|
||||
VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDevicePolicy_defaultDeviceId_returnsDefault() {
|
||||
assertThat(
|
||||
mLocalService.getDevicePolicy(
|
||||
VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
|
||||
assertThat(
|
||||
mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
|
||||
assertThat(
|
||||
mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(DEVICE_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDevicePolicy_returnsCustom() {
|
||||
VirtualDeviceParams params = new VirtualDeviceParams
|
||||
.Builder()
|
||||
.setBlockedActivities(getBlockedActivities())
|
||||
.addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
|
||||
.build();
|
||||
mDeviceImpl = new VirtualDeviceImpl(mContext,
|
||||
mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
|
||||
mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
|
||||
mActivityListener, mRunningAppsChangedCallback, params);
|
||||
mVdms.addVirtualDevice(mDeviceImpl);
|
||||
|
||||
assertThat(
|
||||
mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(DEVICE_POLICY_CUSTOM);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,6 +37,8 @@ public class VirtualDeviceParamsTest {
|
||||
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
|
||||
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
|
||||
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
|
||||
.addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
|
||||
VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
|
||||
.build();
|
||||
Parcel parcel = Parcel.obtain();
|
||||
originalParams.writeToParcel(parcel, 0);
|
||||
@@ -47,5 +49,7 @@ public class VirtualDeviceParamsTest {
|
||||
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
|
||||
assertThat(params.getUsersWithMatchingAccounts())
|
||||
.containsExactly(UserHandle.of(123), UserHandle.of(456));
|
||||
assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
|
||||
.isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user