Merge "Add tests to verify last pip component name updates"
This commit is contained in:
@@ -29,7 +29,6 @@ import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@@ -45,6 +44,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.common.DisplayChangeController;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
import com.android.wm.shell.pip.PipBoundsHandler;
|
||||
@@ -62,7 +62,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
private static final String TAG = "PipController";
|
||||
|
||||
private Context mContext;
|
||||
private Handler mHandler = new Handler();
|
||||
private ShellExecutor mMainExecutor;
|
||||
|
||||
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
|
||||
private final Rect mTmpInsetBounds = new Rect();
|
||||
@@ -82,6 +82,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
protected PipMenuActivityController mMenuController;
|
||||
protected PipTaskOrganizer mPipTaskOrganizer;
|
||||
protected PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
|
||||
new PipControllerPinnedStackListener();
|
||||
|
||||
/**
|
||||
* Handler for display rotation changes.
|
||||
@@ -150,12 +152,12 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
PinnedStackListenerForwarder.PinnedStackListener {
|
||||
@Override
|
||||
public void onListenerRegistered(IPinnedStackController controller) {
|
||||
mHandler.post(() -> mTouchHandler.setPinnedStackController(controller));
|
||||
mMainExecutor.execute(() -> mTouchHandler.setPinnedStackController(controller));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
|
||||
mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
|
||||
});
|
||||
@@ -163,19 +165,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
|
||||
mHandler.post(() -> updateMovementBounds(null /* toBounds */,
|
||||
mMainExecutor.execute(() -> updateMovementBounds(null /* toBounds */,
|
||||
false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
|
||||
null /* windowContainerTransaction */));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
|
||||
mHandler.post(() -> mMenuController.setAppActions(actions));
|
||||
mMainExecutor.execute(() -> mMenuController.setAppActions(actions));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityHidden(ComponentName componentName) {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
|
||||
// The activity was removed, we don't want to restore to the reentry state
|
||||
// saved for this component anymore.
|
||||
@@ -186,12 +188,12 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(DisplayInfo displayInfo) {
|
||||
mHandler.post(() -> mPipBoundsState.setDisplayInfo(displayInfo));
|
||||
mMainExecutor.execute(() -> mPipBoundsState.setDisplayInfo(displayInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged() {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mPipBoundsHandler.onConfigurationChanged(mContext);
|
||||
mTouchHandler.onConfigurationChanged();
|
||||
});
|
||||
@@ -201,7 +203,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
public void onAspectRatioChanged(float aspectRatio) {
|
||||
// TODO(b/169373982): Remove this callback as it is redundant with PipTaskOrg params
|
||||
// change.
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mPipBoundsState.setAspectRatio(aspectRatio);
|
||||
mTouchHandler.onAspectRatioChanged();
|
||||
});
|
||||
@@ -217,7 +219,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
PipMenuActivityController pipMenuActivityController,
|
||||
PipTaskOrganizer pipTaskOrganizer,
|
||||
PipTouchHandler pipTouchHandler,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
ShellExecutor mainExecutor
|
||||
) {
|
||||
// Ensure that we are the primary user's SystemUI.
|
||||
final int processUser = UserManager.get(context).getUserHandle();
|
||||
@@ -231,6 +234,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
mPipBoundsHandler = pipBoundsHandler;
|
||||
mPipBoundsState = pipBoundsState;
|
||||
mPipTaskOrganizer = pipTaskOrganizer;
|
||||
mMainExecutor = mainExecutor;
|
||||
mPipTaskOrganizer.registerPipTransitionCallback(this);
|
||||
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
|
||||
final DisplayInfo newDisplayInfo = new DisplayInfo();
|
||||
@@ -254,8 +258,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
mPipBoundsState.setDisplayInfo(displayInfo);
|
||||
|
||||
try {
|
||||
mWindowManagerShellWrapper.addPinnedStackListener(
|
||||
new PipControllerPinnedStackListener());
|
||||
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to register pinned stack listener", e);
|
||||
}
|
||||
@@ -263,14 +266,14 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void onDensityOrFontScaleChanged() {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPinned(String packageName) {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mTouchHandler.onActivityPinned();
|
||||
mMediaController.onActivityPinned();
|
||||
mMenuController.onActivityPinned();
|
||||
@@ -280,7 +283,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void onActivityUnpinned(ComponentName topActivity) {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mMenuController.onActivityUnpinned();
|
||||
mTouchHandler.onActivityUnpinned(topActivity);
|
||||
mAppOpsListener.onActivityUnpinned();
|
||||
@@ -299,7 +302,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void onOverlayChanged() {
|
||||
mHandler.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
|
||||
updateMovementBounds(null /* toBounds */,
|
||||
false /* fromRotation */, false /* fromImeAdjustment */,
|
||||
@@ -358,7 +361,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
*/
|
||||
@Override
|
||||
public void setShelfHeight(boolean visible, int height) {
|
||||
mHandler.post(() -> setShelfHeightLocked(visible, height));
|
||||
mMainExecutor.execute(() -> setShelfHeightLocked(visible, height));
|
||||
}
|
||||
|
||||
private void setShelfHeightLocked(boolean visible, int height) {
|
||||
@@ -374,12 +377,12 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
@Override
|
||||
public void setPinnedStackAnimationType(int animationType) {
|
||||
mHandler.post(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
|
||||
mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
|
||||
mHandler.post(() -> mPinnedStackAnimationRecentsCallback = callback);
|
||||
mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -476,7 +479,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
|
||||
PipMenuActivityController pipMenuActivityController,
|
||||
PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper) {
|
||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
ShellExecutor mainExecutor) {
|
||||
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
|
||||
Slog.w(TAG, "Device doesn't support Pip feature");
|
||||
return null;
|
||||
@@ -484,6 +488,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
|
||||
return new PipController(context, displayController, pipAppOpsListener, pipBoundsHandler,
|
||||
pipBoundsState, pipMediaController, pipMenuActivityController,
|
||||
pipTaskOrganizer, pipTouchHandler, windowManagerShellWrapper);
|
||||
pipTaskOrganizer, pipTouchHandler, windowManagerShellWrapper, mainExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +69,13 @@ public class PipTaskOrganizerTest extends PipTestCase {
|
||||
private PipBoundsState mPipBoundsState;
|
||||
|
||||
private ComponentName mComponent1;
|
||||
private ComponentName mComponent2;
|
||||
|
||||
@Before
|
||||
public void setUp() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mComponent1 = new ComponentName(mContext, "component1");
|
||||
mComponent2 = new ComponentName(mContext, "component2");
|
||||
mPipBoundsState = new PipBoundsState();
|
||||
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
|
||||
mMockPipBoundsHandler, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen,
|
||||
@@ -100,30 +102,58 @@ public class PipTaskOrganizerTest extends PipTestCase {
|
||||
assertEquals(aspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startSwipePipToHome_updatesLastPipComponentName() {
|
||||
mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, null, null);
|
||||
|
||||
assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTaskAppeared_updatesAspectRatio() {
|
||||
final Rational aspectRatio = new Rational(2, 1);
|
||||
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
|
||||
createPipParams(aspectRatio)), null /* leash */);
|
||||
|
||||
assertEquals(aspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTaskAppeared_updatesLastPipComponentName() {
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)),
|
||||
null /* leash */);
|
||||
|
||||
assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTaskInfoChanged_updatesAspectRatioIfChanged() {
|
||||
final Rational startAspectRatio = new Rational(2, 1);
|
||||
final Rational newAspectRatio = new Rational(1, 2);
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
|
||||
createPipParams(startAspectRatio)), null /* leash */);
|
||||
|
||||
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(createPipParams(newAspectRatio)));
|
||||
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1,
|
||||
createPipParams(newAspectRatio)));
|
||||
|
||||
assertEquals(newAspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTaskInfoChanged_updatesLastPipComponentName() {
|
||||
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
|
||||
createPipParams(null)), null /* leash */);
|
||||
|
||||
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
|
||||
createPipParams(null)));
|
||||
|
||||
assertEquals(mComponent2, mPipBoundsState.getLastPipComponentName());
|
||||
}
|
||||
|
||||
private void preparePipTaskOrg() {
|
||||
final DisplayInfo info = new DisplayInfo();
|
||||
mPipBoundsState.setDisplayInfo(info);
|
||||
when(mMockPipBoundsHandler.getDestinationBounds(any(), any())).thenReturn(new Rect());
|
||||
when(mMockPipBoundsHandler.getDestinationBounds(any(), any(), anyBoolean()))
|
||||
.thenReturn(new Rect());
|
||||
@@ -133,10 +163,12 @@ public class PipTaskOrganizerTest extends PipTestCase {
|
||||
doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any());
|
||||
}
|
||||
|
||||
private static ActivityManager.RunningTaskInfo createTaskInfo(PictureInPictureParams params) {
|
||||
private static ActivityManager.RunningTaskInfo createTaskInfo(
|
||||
ComponentName componentName, PictureInPictureParams params) {
|
||||
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
|
||||
info.token = mock(WindowContainerToken.class);
|
||||
info.pictureInPictureParams = params;
|
||||
info.topActivity = componentName;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,14 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
@@ -34,6 +37,7 @@ import android.testing.TestableLooper;
|
||||
|
||||
import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.pip.PipBoundsHandler;
|
||||
import com.android.wm.shell.pip.PipBoundsState;
|
||||
import com.android.wm.shell.pip.PipMediaController;
|
||||
@@ -64,6 +68,7 @@ public class PipControllerTest extends PipTestCase {
|
||||
@Mock private PipTouchHandler mMockPipTouchHandler;
|
||||
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
|
||||
@Mock private PipBoundsState mMockPipBoundsState;
|
||||
@Mock private ShellExecutor mMockExecutor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws RemoteException {
|
||||
@@ -71,7 +76,11 @@ public class PipControllerTest extends PipTestCase {
|
||||
mPipController = new PipController(mContext, mMockDisplayController,
|
||||
mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState,
|
||||
mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
|
||||
mMockPipTouchHandler, mMockWindowManagerShellWrapper);
|
||||
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockExecutor);
|
||||
doAnswer(invocation -> {
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
}).when(mMockExecutor).execute(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,6 +108,27 @@ public class PipControllerTest extends PipTestCase {
|
||||
assertNull(PipController.create(spyContext, mMockDisplayController,
|
||||
mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState,
|
||||
mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
|
||||
mMockPipTouchHandler, mMockWindowManagerShellWrapper));
|
||||
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockExecutor));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() {
|
||||
final ComponentName component1 = new ComponentName(mContext, "component1");
|
||||
when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
|
||||
|
||||
mPipController.mPinnedStackListener.onActivityHidden(component1);
|
||||
|
||||
verify(mMockPipBoundsState).setLastPipComponentName(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() {
|
||||
final ComponentName component1 = new ComponentName(mContext, "component1");
|
||||
final ComponentName component2 = new ComponentName(mContext, "component2");
|
||||
when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
|
||||
|
||||
mPipController.mPinnedStackListener.onActivityHidden(component2);
|
||||
|
||||
verify(mMockPipBoundsState, never()).setLastPipComponentName(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.wm.shell.common.AnimationThread;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.common.HandlerExecutor;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.SystemWindows;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
@@ -157,9 +158,9 @@ public abstract class WMShellBaseModule {
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
|
||||
@Main Handler handler, TransactionPool transactionPool) {
|
||||
ShellExecutor mainExecutor, TransactionPool transactionPool) {
|
||||
return new ShellTaskOrganizer(syncQueue, transactionPool,
|
||||
new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
|
||||
mainExecutor, AnimationThread.instance().getExecutor());
|
||||
}
|
||||
|
||||
@BindsOptionalOf
|
||||
@@ -174,4 +175,11 @@ public abstract class WMShellBaseModule {
|
||||
DisplayController displayController) {
|
||||
return Optional.ofNullable(OneHandedController.create(context, displayController));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static ShellExecutor provideMainShellExecutor(@Main Handler handler) {
|
||||
return new HandlerExecutor(handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.common.FloatingContentCoordinator;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.SystemWindows;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
@@ -82,11 +83,12 @@ public class WMShellModule {
|
||||
PipAppOpsListener pipAppOpsListener, PipBoundsHandler pipBoundsHandler,
|
||||
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
|
||||
PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer,
|
||||
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper) {
|
||||
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
|
||||
ShellExecutor mainExecutor) {
|
||||
return Optional.ofNullable(PipController.create(context, displayController,
|
||||
pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
|
||||
pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
|
||||
windowManagerShellWrapper));
|
||||
windowManagerShellWrapper, mainExecutor));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
|
||||
Reference in New Issue
Block a user