Merge "Ensure that PiP mode changed callback if animation is interrupted" into oc-mr1-dev
This commit is contained in:
@@ -659,14 +659,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
|
||||
}
|
||||
}
|
||||
|
||||
void updatePictureInPictureMode(Rect targetStackBounds) {
|
||||
void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) {
|
||||
if (task == null || task.getStack() == null || app == null || app.thread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
|
||||
(targetStackBounds != null);
|
||||
if (inPictureInPictureMode != mLastReportedPictureInPictureMode) {
|
||||
if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
|
||||
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
|
||||
// update that here in order
|
||||
mLastReportedPictureInPictureMode = inPictureInPictureMode;
|
||||
@@ -681,8 +681,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
|
||||
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
|
||||
try {
|
||||
app.thread.schedulePictureInPictureModeChanged(appToken,
|
||||
mLastReportedPictureInPictureMode,
|
||||
overrideConfig);
|
||||
mLastReportedPictureInPictureMode, overrideConfig);
|
||||
} catch (Exception e) {
|
||||
// If process died, no one cares.
|
||||
}
|
||||
|
||||
@@ -4410,31 +4410,29 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds, false /* immediate */);
|
||||
scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
|
||||
}
|
||||
|
||||
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds,
|
||||
boolean immediate) {
|
||||
|
||||
if (immediate) {
|
||||
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
|
||||
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
|
||||
final ActivityRecord r = task.mActivities.get(i);
|
||||
if (r.app != null && r.app.thread != null) {
|
||||
r.updatePictureInPictureMode(targetStackBounds);
|
||||
}
|
||||
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
|
||||
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
|
||||
final ActivityRecord r = task.mActivities.get(i);
|
||||
if (r.app != null && r.app.thread != null) {
|
||||
mPipModeChangedActivities.add(r);
|
||||
}
|
||||
} else {
|
||||
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
|
||||
final ActivityRecord r = task.mActivities.get(i);
|
||||
if (r.app != null && r.app.thread != null) {
|
||||
mPipModeChangedActivities.add(r);
|
||||
}
|
||||
}
|
||||
mPipModeChangedTargetStackBounds = targetStackBounds;
|
||||
}
|
||||
mPipModeChangedTargetStackBounds = targetStackBounds;
|
||||
|
||||
if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
|
||||
mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
|
||||
if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
|
||||
mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
|
||||
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
|
||||
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
|
||||
final ActivityRecord r = task.mActivities.get(i);
|
||||
if (r.app != null && r.app.thread != null) {
|
||||
r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4496,7 +4494,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
|
||||
synchronized (mService) {
|
||||
for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
|
||||
final ActivityRecord r = mPipModeChangedActivities.remove(i);
|
||||
r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds);
|
||||
r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds,
|
||||
false /* forceUpdate */);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -91,15 +91,16 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
|
||||
return mWindowContainerController.deferScheduleMultiWindowModeChanged();
|
||||
}
|
||||
|
||||
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
|
||||
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
|
||||
boolean forceUpdate) {
|
||||
// It is guaranteed that the activities requiring the update will be in the pinned stack at
|
||||
// this point (either reparented before the animation into PiP, or before reparenting after
|
||||
// the animation out of PiP)
|
||||
synchronized(this) {
|
||||
ArrayList<TaskRecord> tasks = getAllTasks();
|
||||
for (int i = 0; i < tasks.size(); i++ ) {
|
||||
mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(tasks.get(i),
|
||||
targetStackBounds, true /* immediate */);
|
||||
mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds,
|
||||
forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
||||
|
||||
import android.animation.AnimationHandler;
|
||||
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
@@ -32,13 +31,11 @@ import android.os.IBinder;
|
||||
import android.os.Debug;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
import android.view.Choreographer;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.WindowManagerInternal;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -142,9 +139,6 @@ public class BoundsAnimationController {
|
||||
// True if this this animation was canceled and will be replaced the another animation from
|
||||
// the same {@link #BoundsAnimationTarget} target.
|
||||
private boolean mSkipFinalResize;
|
||||
// True if this animation replaced a previous animation of the same
|
||||
// {@link #BoundsAnimationTarget} target.
|
||||
private final boolean mSkipAnimationStart;
|
||||
// True if this animation was canceled by the user, not as a part of a replacing animation
|
||||
private boolean mSkipAnimationEnd;
|
||||
|
||||
@@ -159,6 +153,7 @@ public class BoundsAnimationController {
|
||||
|
||||
// Whether to schedule PiP mode changes on animation start/end
|
||||
private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
|
||||
private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
|
||||
|
||||
// Depending on whether we are animating from
|
||||
// a smaller to a larger size
|
||||
@@ -171,14 +166,14 @@ public class BoundsAnimationController {
|
||||
|
||||
BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
|
||||
@SchedulePipModeChangedState int schedulePipModeChangedState,
|
||||
boolean moveFromFullscreen, boolean moveToFullscreen,
|
||||
boolean replacingExistingAnimation) {
|
||||
@SchedulePipModeChangedState int prevShedulePipModeChangedState,
|
||||
boolean moveFromFullscreen, boolean moveToFullscreen) {
|
||||
super();
|
||||
mTarget = target;
|
||||
mFrom.set(from);
|
||||
mTo.set(to);
|
||||
mSkipAnimationStart = replacingExistingAnimation;
|
||||
mSchedulePipModeChangedState = schedulePipModeChangedState;
|
||||
mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
|
||||
mMoveFromFullscreen = moveFromFullscreen;
|
||||
mMoveToFullscreen = moveToFullscreen;
|
||||
addUpdateListener(this);
|
||||
@@ -200,7 +195,7 @@ public class BoundsAnimationController {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
|
||||
+ " mSkipAnimationStart=" + mSkipAnimationStart
|
||||
+ " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
|
||||
+ " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
|
||||
mFinishAnimationAfterTransition = false;
|
||||
mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
|
||||
@@ -210,18 +205,26 @@ public class BoundsAnimationController {
|
||||
// running
|
||||
updateBooster();
|
||||
|
||||
// Ensure that we have prepared the target for animation before
|
||||
// we trigger any size changes, so it can swap surfaces
|
||||
// in to appropriate modes, or do as it wishes otherwise.
|
||||
if (!mSkipAnimationStart) {
|
||||
// Ensure that we have prepared the target for animation before we trigger any size
|
||||
// changes, so it can swap surfaces in to appropriate modes, or do as it wishes
|
||||
// otherwise.
|
||||
if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
|
||||
mTarget.onAnimationStart(mSchedulePipModeChangedState ==
|
||||
SCHEDULE_PIP_MODE_CHANGED_ON_START);
|
||||
SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
|
||||
|
||||
// When starting an animation from fullscreen, pause here and wait for the
|
||||
// windows-drawn signal before we start the rest of the transition down into PiP.
|
||||
if (mMoveFromFullscreen) {
|
||||
pause();
|
||||
}
|
||||
} else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
|
||||
mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
|
||||
// We are replacing a running animation into PiP, but since it hasn't completed, the
|
||||
// client will not currently receive any picture-in-picture mode change callbacks.
|
||||
// However, we still need to report to them that they are leaving PiP, so this will
|
||||
// force an update via a mode changed callback.
|
||||
mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */,
|
||||
true /* forceUpdate */);
|
||||
}
|
||||
|
||||
// Immediately update the task bounds if they have to become larger, but preserve
|
||||
@@ -388,6 +391,8 @@ public class BoundsAnimationController {
|
||||
boolean moveFromFullscreen, boolean moveToFullscreen) {
|
||||
final BoundsAnimator existing = mRunningAnimations.get(target);
|
||||
final boolean replacing = existing != null;
|
||||
@SchedulePipModeChangedState int prevSchedulePipModeChangedState =
|
||||
NO_PIP_MODE_CHANGED_CALLBACKS;
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
|
||||
+ " schedulePipModeChangedState=" + schedulePipModeChangedState
|
||||
@@ -403,6 +408,9 @@ public class BoundsAnimationController {
|
||||
return existing;
|
||||
}
|
||||
|
||||
// Save the previous state
|
||||
prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
|
||||
|
||||
// Update the PiP callback states if we are replacing the animation
|
||||
if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
|
||||
if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
|
||||
@@ -428,7 +436,8 @@ public class BoundsAnimationController {
|
||||
existing.cancel();
|
||||
}
|
||||
final BoundsAnimator animator = new BoundsAnimator(target, from, to,
|
||||
schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
|
||||
schedulePipModeChangedState, prevSchedulePipModeChangedState,
|
||||
moveFromFullscreen, moveToFullscreen);
|
||||
mRunningAnimations.put(target, animator);
|
||||
animator.setFloatValues(0f, 1f);
|
||||
animator.setDuration((animationDuration != -1 ? animationDuration
|
||||
|
||||
@@ -31,7 +31,7 @@ interface BoundsAnimationTarget {
|
||||
* @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
|
||||
* callbacks
|
||||
*/
|
||||
void onAnimationStart(boolean schedulePipModeChangedCallback);
|
||||
void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
|
||||
|
||||
/**
|
||||
* Sets the size of the target (without any intermediate steps, like scheduling animation)
|
||||
|
||||
@@ -202,10 +202,12 @@ public class PinnedStackWindowController extends StackWindowController {
|
||||
*/
|
||||
|
||||
/** Calls directly into activity manager so window manager lock shouldn't held. */
|
||||
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
|
||||
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
|
||||
boolean forceUpdate) {
|
||||
if (mListener != null) {
|
||||
PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
|
||||
listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
|
||||
listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
|
||||
forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@ public interface PinnedStackWindowListener extends StackWindowListener {
|
||||
* Called when the stack container pinned stack animation will change the picture-in-picture
|
||||
* mode. This is a direct call into ActivityManager.
|
||||
*/
|
||||
default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {}
|
||||
default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
|
||||
boolean forceUpdate) {}
|
||||
}
|
||||
|
||||
@@ -1498,7 +1498,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|
||||
}
|
||||
|
||||
@Override // AnimatesBounds
|
||||
public void onAnimationStart(boolean schedulePipModeChangedCallback) {
|
||||
public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
|
||||
// Hold the lock since this is called from the BoundsAnimator running on the UiThread
|
||||
synchronized (mService.mWindowMap) {
|
||||
mBoundsAnimatingRequested = false;
|
||||
@@ -1523,9 +1523,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|
||||
final PinnedStackWindowController controller =
|
||||
(PinnedStackWindowController) getController();
|
||||
if (schedulePipModeChangedCallback && controller != null) {
|
||||
// We need to schedule the PiP mode change after the animation down, so use the
|
||||
// final bounds
|
||||
controller.updatePictureInPictureModeForPinnedStackAnimation(null);
|
||||
// We need to schedule the PiP mode change before the animation up. It is possible
|
||||
// in this case for the animation down to not have been completed, so always
|
||||
// force-schedule and update to the client to ensure that it is notified that it
|
||||
// is no longer in picture-in-picture mode
|
||||
controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1553,7 +1555,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|
||||
// We need to schedule the PiP mode change after the animation down, so use the
|
||||
// final bounds
|
||||
controller.updatePictureInPictureModeForPinnedStackAnimation(
|
||||
mBoundsAnimationTarget);
|
||||
mBoundsAnimationTarget, false /* forceUpdate */);
|
||||
}
|
||||
|
||||
if (finalStackSize != null) {
|
||||
|
||||
@@ -126,6 +126,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
boolean mMovedToFullscreen;
|
||||
boolean mAnimationStarted;
|
||||
boolean mSchedulePipModeChangedOnStart;
|
||||
boolean mForcePipModeChangedCallback;
|
||||
boolean mAnimationEnded;
|
||||
Rect mAnimationEndFinalStackBounds;
|
||||
boolean mSchedulePipModeChangedOnEnd;
|
||||
@@ -140,6 +141,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
mAnimationStarted = false;
|
||||
mAnimationEnded = false;
|
||||
mAnimationEndFinalStackBounds = null;
|
||||
mForcePipModeChangedCallback = false;
|
||||
mSchedulePipModeChangedOnStart = false;
|
||||
mSchedulePipModeChangedOnEnd = false;
|
||||
mStackBounds = from;
|
||||
@@ -148,10 +150,11 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(boolean schedulePipModeChangedCallback) {
|
||||
public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
|
||||
mAwaitingAnimationStart = false;
|
||||
mAnimationStarted = true;
|
||||
mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
|
||||
mForcePipModeChangedCallback = forceUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,7 +235,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
return this;
|
||||
}
|
||||
|
||||
BoundsAnimationDriver restart(Rect to) {
|
||||
BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) {
|
||||
if (mAnimator == null) {
|
||||
throw new IllegalArgumentException("Call start() to start a new animation");
|
||||
}
|
||||
@@ -251,8 +254,15 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
assertSame(oldAnimator, mAnimator);
|
||||
}
|
||||
|
||||
// No animation start for replacing animation
|
||||
assertTrue(!mTarget.mAnimationStarted);
|
||||
if (expectStartedAndPipModeChangedCallback) {
|
||||
// Replacing animation with pending pip mode changed callback, ensure we update
|
||||
assertTrue(mTarget.mAnimationStarted);
|
||||
assertTrue(mTarget.mSchedulePipModeChangedOnStart);
|
||||
assertTrue(mTarget.mForcePipModeChangedCallback);
|
||||
} else {
|
||||
// No animation start for replacing animation
|
||||
assertTrue(!mTarget.mAnimationStarted);
|
||||
}
|
||||
mTarget.mAnimationStarted = true;
|
||||
return this;
|
||||
}
|
||||
@@ -467,7 +477,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
|
||||
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
|
||||
.update(0.25f)
|
||||
.restart(BOUNDS_FLOATING)
|
||||
.restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
|
||||
.end()
|
||||
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
|
||||
}
|
||||
@@ -478,7 +488,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
|
||||
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
|
||||
.update(0.25f)
|
||||
.restart(BOUNDS_SMALLER_FLOATING)
|
||||
.restart(BOUNDS_SMALLER_FLOATING,
|
||||
false /* expectStartedAndPipModeChangedCallback */)
|
||||
.end()
|
||||
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
|
||||
}
|
||||
@@ -486,10 +497,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
@UiThreadTest
|
||||
@Test
|
||||
public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
|
||||
// When animating from fullscreen and the animation is interruped, we expect the animation
|
||||
// start callback to be made, with a forced pip mode change callback
|
||||
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
|
||||
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
|
||||
.update(0.25f)
|
||||
.restart(BOUNDS_FULL)
|
||||
.restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
|
||||
.end()
|
||||
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
|
||||
}
|
||||
@@ -512,7 +525,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
|
||||
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
|
||||
.update(0.25f)
|
||||
.restart(BOUNDS_FULL)
|
||||
.restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
|
||||
.end()
|
||||
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
|
||||
}
|
||||
@@ -523,7 +536,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
|
||||
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
|
||||
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
|
||||
.update(0.25f)
|
||||
.restart(BOUNDS_SMALLER_FLOATING)
|
||||
.restart(BOUNDS_SMALLER_FLOATING,
|
||||
false /* expectStartedAndPipModeChangedCallback */)
|
||||
.end()
|
||||
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user