Merge "Fix the transition from Split to PiP" into sc-v2-dev

This commit is contained in:
Hongwei Wang
2021-12-10 08:25:28 +00:00
committed by Android (Google) Code Review
6 changed files with 147 additions and 48 deletions

View File

@@ -19,6 +19,7 @@ package android.window;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@@ -34,9 +35,10 @@ import java.util.Objects;
* @hide * @hide
*/ */
public final class PictureInPictureSurfaceTransaction implements Parcelable { public final class PictureInPictureSurfaceTransaction implements Parcelable {
private static final float NOT_SET = -1f;
public final float mPositionX; public final float mAlpha;
public final float mPositionY; public final PointF mPosition;
public final float[] mFloat9; public final float[] mFloat9;
@@ -45,33 +47,37 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
public final float mCornerRadius; public final float mCornerRadius;
private final Rect mWindowCrop = new Rect(); private final Rect mWindowCrop;
public PictureInPictureSurfaceTransaction(Parcel in) { private PictureInPictureSurfaceTransaction(Parcel in) {
mPositionX = in.readFloat(); mAlpha = in.readFloat();
mPositionY = in.readFloat(); mPosition = in.readTypedObject(PointF.CREATOR);
mFloat9 = new float[9]; mFloat9 = new float[9];
in.readFloatArray(mFloat9); in.readFloatArray(mFloat9);
mRotation = in.readFloat(); mRotation = in.readFloat();
mCornerRadius = in.readFloat(); mCornerRadius = in.readFloat();
mWindowCrop.set(Objects.requireNonNull(in.readTypedObject(Rect.CREATOR))); mWindowCrop = in.readTypedObject(Rect.CREATOR);
} }
public PictureInPictureSurfaceTransaction(float positionX, float positionY, private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position,
float[] float9, float rotation, float cornerRadius, @Nullable float[] float9, float rotation, float cornerRadius,
@Nullable Rect windowCrop) { @Nullable Rect windowCrop) {
mPositionX = positionX; mAlpha = alpha;
mPositionY = positionY; mPosition = position;
mFloat9 = Arrays.copyOf(float9, 9); if (float9 == null) {
mRotation = rotation; mFloat9 = new float[9];
mCornerRadius = cornerRadius; Matrix.IDENTITY_MATRIX.getValues(mFloat9);
if (windowCrop != null) { mRotation = 0;
mWindowCrop.set(windowCrop); } else {
mFloat9 = Arrays.copyOf(float9, 9);
mRotation = rotation;
} }
mCornerRadius = cornerRadius;
mWindowCrop = (windowCrop == null) ? null : new Rect(windowCrop);
} }
public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) { public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) {
this(other.mPositionX, other.mPositionY, this(other.mAlpha, other.mPosition,
other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop); other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop);
} }
@@ -82,13 +88,18 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
return matrix; return matrix;
} }
/** @return {@code true} if this transaction contains setting corner radius. */
public boolean hasCornerRadiusSet() {
return mCornerRadius > 0;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (!(o instanceof PictureInPictureSurfaceTransaction)) return false; if (!(o instanceof PictureInPictureSurfaceTransaction)) return false;
PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o; PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o;
return Objects.equals(mPositionX, that.mPositionX) return Objects.equals(mAlpha, that.mAlpha)
&& Objects.equals(mPositionY, that.mPositionY) && Objects.equals(mPosition, that.mPosition)
&& Arrays.equals(mFloat9, that.mFloat9) && Arrays.equals(mFloat9, that.mFloat9)
&& Objects.equals(mRotation, that.mRotation) && Objects.equals(mRotation, that.mRotation)
&& Objects.equals(mCornerRadius, that.mCornerRadius) && Objects.equals(mCornerRadius, that.mCornerRadius)
@@ -97,7 +108,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(mPositionX, mPositionY, Arrays.hashCode(mFloat9), return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9),
mRotation, mCornerRadius, mWindowCrop); mRotation, mCornerRadius, mWindowCrop);
} }
@@ -108,8 +119,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
@Override @Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
out.writeFloat(mPositionX); out.writeFloat(mAlpha);
out.writeFloat(mPositionY); out.writeTypedObject(mPosition, 0 /* flags */);
out.writeFloatArray(mFloat9); out.writeFloatArray(mFloat9);
out.writeFloat(mRotation); out.writeFloat(mRotation);
out.writeFloat(mCornerRadius); out.writeFloat(mCornerRadius);
@@ -120,8 +131,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
public String toString() { public String toString() {
final Matrix matrix = getMatrix(); final Matrix matrix = getMatrix();
return "PictureInPictureSurfaceTransaction(" return "PictureInPictureSurfaceTransaction("
+ " posX=" + mPositionX + " alpha=" + mAlpha
+ " posY=" + mPositionY + " position=" + mPosition
+ " matrix=" + matrix.toShortString() + " matrix=" + matrix.toShortString()
+ " rotation=" + mRotation + " rotation=" + mRotation
+ " cornerRadius=" + mCornerRadius + " cornerRadius=" + mCornerRadius
@@ -134,11 +145,20 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
@NonNull SurfaceControl surfaceControl, @NonNull SurfaceControl surfaceControl,
@NonNull SurfaceControl.Transaction tx) { @NonNull SurfaceControl.Transaction tx) {
final Matrix matrix = surfaceTransaction.getMatrix(); final Matrix matrix = surfaceTransaction.getMatrix();
tx.setMatrix(surfaceControl, matrix, new float[9]) tx.setMatrix(surfaceControl, matrix, new float[9]);
.setPosition(surfaceControl, if (surfaceTransaction.mPosition != null) {
surfaceTransaction.mPositionX, surfaceTransaction.mPositionY) tx.setPosition(surfaceControl,
.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop) surfaceTransaction.mPosition.x, surfaceTransaction.mPosition.y);
.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius); }
if (surfaceTransaction.mWindowCrop != null) {
tx.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop);
}
if (surfaceTransaction.hasCornerRadiusSet()) {
tx.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius);
}
if (surfaceTransaction.mAlpha != NOT_SET) {
tx.setAlpha(surfaceControl, surfaceTransaction.mAlpha);
}
} }
public static final @android.annotation.NonNull Creator<PictureInPictureSurfaceTransaction> public static final @android.annotation.NonNull Creator<PictureInPictureSurfaceTransaction>
@@ -151,4 +171,44 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
return new PictureInPictureSurfaceTransaction[size]; return new PictureInPictureSurfaceTransaction[size];
} }
}; };
public static class Builder {
private float mAlpha = NOT_SET;
private PointF mPosition;
private float[] mFloat9;
private float mRotation;
private float mCornerRadius = NOT_SET;
private Rect mWindowCrop;
public Builder setAlpha(float alpha) {
mAlpha = alpha;
return this;
}
public Builder setPosition(float x, float y) {
mPosition = new PointF(x, y);
return this;
}
public Builder setTransform(@NonNull float[] float9, float rotation) {
mFloat9 = Arrays.copyOf(float9, 9);
mRotation = rotation;
return this;
}
public Builder setCornerRadius(float cornerRadius) {
mCornerRadius = cornerRadius;
return this;
}
public Builder setWindowCrop(@NonNull Rect windowCrop) {
mWindowCrop = new Rect(windowCrop);
return this;
}
public PictureInPictureSurfaceTransaction build() {
return new PictureInPictureSurfaceTransaction(mAlpha, mPosition,
mFloat9, mRotation, mCornerRadius, mWindowCrop);
}
}
} }

View File

@@ -102,6 +102,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
static final int EXIT_REASON_SCREEN_LOCKED = 7; static final int EXIT_REASON_SCREEN_LOCKED = 7;
static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
@IntDef(value = { @IntDef(value = {
EXIT_REASON_UNKNOWN, EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -112,6 +113,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_ROOT_TASK_VANISHED, EXIT_REASON_ROOT_TASK_VANISHED,
EXIT_REASON_SCREEN_LOCKED, EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface ExitReason{} @interface ExitReason{}
@@ -406,6 +408,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "APP_FINISHED"; return "APP_FINISHED";
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW: case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
return "APP_DOES_NOT_SUPPORT_MULTIWINDOW"; return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
return "CHILD_TASK_ENTER_PIP";
default: default:
return "unknown reason, reason int = " + exitReason; return "unknown reason, reason int = " + exitReason;
} }

View File

@@ -35,6 +35,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -629,8 +630,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}); });
mShouldUpdateRecents = false; mShouldUpdateRecents = false;
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); // When the exit split-screen is caused by one of the task enters auto pip,
mMainStage.deactivate(wct, childrenToTop == mMainStage); // we want the tasks to be put to bottom instead of top, otherwise it will end up
// a fullscreen plus a pinned task instead of pinned only at the end of the transition.
final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP;
mSideStage.removeAllTasks(wct, !fromEnteringPip && childrenToTop == mSideStage);
mMainStage.deactivate(wct, !fromEnteringPip && childrenToTop == mMainStage);
mTaskOrganizer.applyTransaction(wct); mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> t mSyncQueue.runInSync(t -> t
.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mMainStage.mRootLeash, null)
@@ -660,6 +665,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case EXIT_REASON_DRAG_DIVIDER: case EXIT_REASON_DRAG_DIVIDER:
// Either of the split apps have finished // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED: case EXIT_REASON_APP_FINISHED:
// One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
return true; return true;
default: default:
return false; return false;
@@ -749,6 +756,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} }
} }
private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) {
exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage,
EXIT_REASON_CHILD_TASK_ENTER_PIP);
}
private void updateRecentTasksSplitPair() { private void updateRecentTasksSplitPair() {
if (!mShouldUpdateRecents) { if (!mShouldUpdateRecents) {
return; return;
@@ -1436,6 +1448,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
} }
@Override
public void onChildTaskEnterPip(int taskId) {
StageCoordinator.this.onStageChildTaskEnterPip(this, taskId);
}
@Override @Override
public void onRootTaskVanished() { public void onRootTaskVanished() {
reset(); reset();

View File

@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -73,6 +74,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onChildTaskEnterPip(int taskId);
void onRootTaskVanished(); void onRootTaskVanished();
void onNoLongerSupportMultiWindow(); void onNoLongerSupportMultiWindow();
@@ -256,6 +259,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenTaskInfo.remove(taskId); mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId); mChildrenLeashes.remove(taskId);
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
mCallbacks.onChildTaskEnterPip(taskId);
}
if (ENABLE_SHELL_TRANSITIONS) { if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle. // Status is managed/synchronized by the transition lifecycle.
return; return;

View File

@@ -53,8 +53,8 @@ public class PipSurfaceTransactionHelper {
tx.setMatrix(leash, mTmpTransform, mTmpFloat9) tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setPosition(leash, positionX, positionY) .setPosition(leash, positionX, positionY)
.setCornerRadius(leash, cornerRadius); .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction( return newPipSurfaceTransaction(positionX, positionY,
positionX, positionY, mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds); mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
} }
public PictureInPictureSurfaceTransaction scale( public PictureInPictureSurfaceTransaction scale(
@@ -70,8 +70,8 @@ public class PipSurfaceTransactionHelper {
tx.setMatrix(leash, mTmpTransform, mTmpFloat9) tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setPosition(leash, positionX, positionY) .setPosition(leash, positionX, positionY)
.setCornerRadius(leash, cornerRadius); .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction( return newPipSurfaceTransaction(positionX, positionY,
positionX, positionY, mTmpFloat9, degree, cornerRadius, sourceBounds); mTmpFloat9, degree, cornerRadius, sourceBounds);
} }
public PictureInPictureSurfaceTransaction scaleAndCrop( public PictureInPictureSurfaceTransaction scaleAndCrop(
@@ -93,8 +93,8 @@ public class PipSurfaceTransactionHelper {
.setWindowCrop(leash, mTmpDestinationRect) .setWindowCrop(leash, mTmpDestinationRect)
.setPosition(leash, left, top) .setPosition(leash, left, top)
.setCornerRadius(leash, cornerRadius); .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction( return newPipSurfaceTransaction(left, top,
left, top, mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect); mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
} }
public PictureInPictureSurfaceTransaction scaleAndRotate( public PictureInPictureSurfaceTransaction scaleAndRotate(
@@ -125,8 +125,7 @@ public class PipSurfaceTransactionHelper {
.setWindowCrop(leash, mTmpDestinationRect) .setWindowCrop(leash, mTmpDestinationRect)
.setPosition(leash, adjustedPositionX, adjustedPositionY) .setPosition(leash, adjustedPositionX, adjustedPositionY)
.setCornerRadius(leash, cornerRadius); .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction( return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY,
adjustedPositionX, adjustedPositionY,
mTmpFloat9, degree, cornerRadius, mTmpDestinationRect); mTmpFloat9, degree, cornerRadius, mTmpDestinationRect);
} }
@@ -137,6 +136,17 @@ public class PipSurfaceTransactionHelper {
return mCornerRadius * scale; return mCornerRadius * scale;
} }
private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction(
float posX, float posY, float[] float9, float rotation, float cornerRadius,
Rect windowCrop) {
return new PictureInPictureSurfaceTransaction.Builder()
.setPosition(posX, posY)
.setTransform(float9, rotation)
.setCornerRadius(cornerRadius)
.setWindowCrop(windowCrop)
.build();
}
/** @return {@link SurfaceControl.Transaction} instance with vsync-id */ /** @return {@link SurfaceControl.Transaction} instance with vsync-id */
public static SurfaceControl.Transaction newSurfaceControlTransaction() { public static SurfaceControl.Transaction newSurfaceControlTransaction() {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();

View File

@@ -255,25 +255,27 @@ class PinnedTaskController {
mDestRotatedBounds = null; mDestRotatedBounds = null;
mPipTransaction = null; mPipTransaction = null;
final Rect areaBounds = taskArea.getBounds(); final Rect areaBounds = taskArea.getBounds();
if (pipTx != null) { if (pipTx != null && pipTx.mPosition != null) {
// The transaction from recents animation is in old rotation. So the position needs to // The transaction from recents animation is in old rotation. So the position needs to
// be rotated. // be rotated.
float dx = pipTx.mPositionX; float dx = pipTx.mPosition.x;
float dy = pipTx.mPositionY; float dy = pipTx.mPosition.y;
final Matrix matrix = pipTx.getMatrix(); final Matrix matrix = pipTx.getMatrix();
if (pipTx.mRotation == 90) { if (pipTx.mRotation == 90) {
dx = pipTx.mPositionY; dx = pipTx.mPosition.y;
dy = areaBounds.right - pipTx.mPositionX; dy = areaBounds.right - pipTx.mPosition.x;
matrix.postRotate(-90); matrix.postRotate(-90);
} else if (pipTx.mRotation == -90) { } else if (pipTx.mRotation == -90) {
dx = areaBounds.bottom - pipTx.mPositionY; dx = areaBounds.bottom - pipTx.mPosition.y;
dy = pipTx.mPositionX; dy = pipTx.mPosition.x;
matrix.postRotate(90); matrix.postRotate(90);
} }
matrix.postTranslate(dx, dy); matrix.postTranslate(dx, dy);
final SurfaceControl leash = pinnedTask.getSurfaceControl(); final SurfaceControl leash = pinnedTask.getSurfaceControl();
t.setMatrix(leash, matrix, new float[9]) t.setMatrix(leash, matrix, new float[9]);
.setCornerRadius(leash, pipTx.mCornerRadius); if (pipTx.hasCornerRadiusSet()) {
t.setCornerRadius(leash, pipTx.mCornerRadius);
}
Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy); Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
return; return;
} }