From 62f0464d2933c7684a6db9ce0b138df4611fed5c Mon Sep 17 00:00:00 2001 From: Chris Li Date: Wed, 23 Nov 2022 11:37:40 +0800 Subject: [PATCH] Provide startBounds for closing container with legacy transition Before, we only provide startBounds when the animation target is MODE_CHANGING, with which we won't know the start bounds for target that is closing while resizing. Now, we provide startBounds for all targets, so that we can use it to provide seamless animation when the target is closing while resizing. Also make sure the closing container's surface is in the start bounds before the animation starts to avoid any flicker. Bug: 241043533 Test: Verify with switching between split and stack. Change-Id: I4d8e1ee44d418249dcb434425c7ab3d63a429bad --- .../android/view/RemoteAnimationTarget.java | 29 +++++---- .../TaskFragmentAnimationAdapter.java | 35 +++++++++-- .../TaskFragmentAnimationRunner.java | 3 +- .../embedding/TaskFragmentAnimationSpec.java | 4 +- .../server/wm/AppTransitionController.java | 20 +++++++ .../com/android/server/wm/DisplayContent.java | 6 ++ .../server/wm/RemoteAnimationController.java | 26 +++++++- .../com/android/server/wm/TaskFragment.java | 29 ++++++--- .../android/server/wm/WindowContainer.java | 60 +++++++++++++++---- 9 files changed, 173 insertions(+), 39 deletions(-) diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index e407707231cae..8816eac33cfb0 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -35,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import android.annotation.ColorInt; import android.annotation.IntDef; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.TaskInfo; import android.app.WindowConfiguration; @@ -175,10 +176,16 @@ public class RemoteAnimationTarget implements Parcelable { public final Rect screenSpaceBounds; /** - * The starting bounds of the source container in screen space coordinates. This is {@code null} - * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size - * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds - * is the end bounds of a change transition. + * The starting bounds of the source container in screen space coordinates. + * For {@link #MODE_OPENING}, this will be equivalent to {@link #screenSpaceBounds}. + * For {@link #MODE_CLOSING}, this will be equivalent to {@link #screenSpaceBounds} unless the + * closing container is also resizing. For example, when ActivityEmbedding split pair becomes + * stacked, the container on the back will be resized to fullscreen, but will also be covered + * (closing) by the container in the front. + * For {@link #MODE_CHANGING}, since this is the starting bounds, its size should be equivalent + * to the bounds of the starting thumbnail. + * + * Note that {@link #screenSpaceBounds} is the end bounds of a transition. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public final Rect startBounds; @@ -247,7 +254,8 @@ public class RemoteAnimationTarget implements Parcelable { Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, - SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl startLeash, @Nullable Rect startBounds, + ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip) { this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash, @@ -258,7 +266,7 @@ public class RemoteAnimationTarget implements Parcelable { Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, - SurfaceControl startLeash, Rect startBounds, + SurfaceControl startLeash, @Nullable Rect startBounds, ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip, @WindowManager.LayoutParams.WindowType int windowType) { this.mode = mode; @@ -275,10 +283,13 @@ public class RemoteAnimationTarget implements Parcelable { this.windowConfiguration = windowConfig; this.isNotInRecents = isNotInRecents; this.startLeash = startLeash; - this.startBounds = startBounds == null ? null : new Rect(startBounds); this.taskInfo = taskInfo; this.allowEnterPip = allowEnterPip; this.windowType = windowType; + // Same as screenSpaceBounds if the window is not resizing. + this.startBounds = startBounds == null + ? new Rect(screenSpaceBounds) + : new Rect(startBounds); } public RemoteAnimationTarget(Parcel in) { @@ -399,9 +410,7 @@ public class RemoteAnimationTarget implements Parcelable { if (startLeash != null) { startLeash.dumpDebug(proto, START_LEASH); } - if (startBounds != null) { - startBounds.dumpDebug(proto, START_BOUNDS); - } + startBounds.dumpDebug(proto, START_BOUNDS); proto.end(token); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index af5d8c5618740..33220c44a3b53 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -18,7 +18,9 @@ package androidx.window.extensions.embedding; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import android.graphics.Point; import android.graphics.Rect; import android.view.Choreographer; import android.view.RemoteAnimationTarget; @@ -49,6 +51,16 @@ class TaskFragmentAnimationAdapter { /** Area in absolute coordinate that the animation surface shouldn't go beyond. */ @NonNull private final Rect mWholeAnimationBounds = new Rect(); + /** + * Area in absolute coordinate that should represent all the content to show for this window. + * This should be the end bounds for opening window, and start bounds for closing window in case + * the window is resizing during the open/close transition. + */ + @NonNull + private final Rect mContentBounds = new Rect(); + /** Offset relative to the window parent surface for {@link #mContentBounds}. */ + @NonNull + private final Point mContentRelOffset = new Point(); @NonNull final Transformation mTransformation = new Transformation(); @@ -78,6 +90,21 @@ class TaskFragmentAnimationAdapter { mTarget = target; mLeash = leash; mWholeAnimationBounds.set(wholeAnimationBounds); + if (target.mode == MODE_CLOSING) { + // When it is closing, we want to show the content at the start position in case the + // window is resizing as well. For example, when the activities is changing from split + // to stack, the bottom TaskFragment will be resized to fullscreen when hiding. + final Rect startBounds = target.startBounds; + final Rect endBounds = target.screenSpaceBounds; + mContentBounds.set(startBounds); + mContentRelOffset.set(target.localBounds.left, target.localBounds.top); + mContentRelOffset.offset( + startBounds.left - endBounds.left, + startBounds.top - endBounds.top); + } else { + mContentBounds.set(target.screenSpaceBounds); + mContentRelOffset.set(target.localBounds.left, target.localBounds.top); + } } /** @@ -108,8 +135,7 @@ class TaskFragmentAnimationAdapter { /** To be overridden by subclasses to adjust the animation surface change. */ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { // Update the surface position and alpha. - mTransformation.getMatrix().postTranslate( - mTarget.localBounds.left, mTarget.localBounds.top); + mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); @@ -117,9 +143,8 @@ class TaskFragmentAnimationAdapter { // positionX/Y are in local coordinate, so minus the local offset to get the slide amount. final int positionX = Math.round(mMatrix[MTRANS_X]); final int positionY = Math.round(mMatrix[MTRANS_Y]); - final Rect cropRect = new Rect(mTarget.screenSpaceBounds); - final Rect localBounds = mTarget.localBounds; - cropRect.offset(positionX - localBounds.left, positionY - localBounds.top); + final Rect cropRect = new Rect(mContentBounds); + cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y); // Store the current offset of the surface top left from (0,0) in absolute coordinate. final int offsetX = cropRect.left; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 0e13c59e593c8..322f85449450e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.view.RemoteAnimationTarget.MODE_CHANGING; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; @@ -254,7 +255,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { @NonNull RemoteAnimationTarget[] targets) { final List adapters = new ArrayList<>(); for (RemoteAnimationTarget target : targets) { - if (target.startBounds != null) { + if (target.mode == MODE_CHANGING) { // This is the target with bounds change. final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(target); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 13afa4910ae11..1f866c3b99c98 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -114,8 +114,8 @@ class TaskFragmentAnimationSpec { @NonNull Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) { final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds(); - // TODO(b/258126915): we want to keep track of the closing start bounds - final Rect bounds = target.screenSpaceBounds; + // Use startBounds if the window is closing in case it may also resize. + final Rect bounds = target.startBounds; final int endTop; final int endLeft; if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 7e93109dcb6d4..abaa3630ff7b5 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -268,6 +268,7 @@ public class AppTransitionController { handleClosingApps(); handleOpeningApps(); handleChangingApps(transit); + handleClosingChangingContainers(); appTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp, topChangingApp); @@ -287,6 +288,7 @@ public class AppTransitionController { mDisplayContent.mClosingApps.clear(); mDisplayContent.mChangingContainers.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); + mDisplayContent.mClosingChangingContainers.clear(); // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. @@ -1171,6 +1173,24 @@ public class AppTransitionController { } } + private void handleClosingChangingContainers() { + final ArrayMap containers = + mDisplayContent.mClosingChangingContainers; + while (!containers.isEmpty()) { + final WindowContainer container = containers.keyAt(0); + containers.remove(container); + + // For closing changing windows that are part of the transition, they should have been + // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter() + // If the closing changing TaskFragment is not part of the transition, update its + // surface after removing it from mClosingChangingContainers. + final TaskFragment taskFragment = container.asTaskFragment(); + if (taskFragment != null) { + taskFragment.updateOrganizedTaskFragmentSurface(); + } + } + } + private void handleChangingApps(@TransitionOldType int transit) { final ArraySet apps = mDisplayContent.mChangingContainers; final int appsCount = apps.size(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 576296e0873c3..7abaaa375405b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -192,6 +192,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.DisplayUtils; @@ -348,6 +349,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final ArraySet mClosingApps = new ArraySet<>(); final ArraySet mChangingContainers = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; + /** + * If a container is closing when resizing, keeps track of its starting bounds when it is + * removed from {@link #mChangingContainers}. + */ + final ArrayMap mClosingChangingContainers = new ArrayMap<>(); private MetricsLogger mMetricsLogger; diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 95371a52410f1..4c5d607ef7beb 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -103,10 +103,29 @@ class RemoteAnimationController implements DeathRecipient { RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, Point position, Rect localBounds, Rect endBounds, Rect startBounds, boolean showBackdrop) { + return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds, + startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */); + } + + /** + * Creates an animation record for each individual {@link WindowContainer}. + * + * @param windowContainer The windows to animate. + * @param position The position app bounds relative to its parent. + * @param localBounds The bounds of the app relative to its parent. + * @param endBounds The end bounds after the transition, in screen coordinates. + * @param startBounds The start bounds before the transition, in screen coordinates. + * @param showBackdrop To show background behind a window during animation. + * @param shouldCreateSnapshot Whether this target should create a snapshot animation. + * @return The record representing animation(s) to run on the app. + */ + RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, + Point position, Rect localBounds, Rect endBounds, Rect startBounds, + boolean showBackdrop, boolean shouldCreateSnapshot) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s", windowContainer); final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position, - localBounds, endBounds, startBounds, showBackdrop); + localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot); mPendingAnimations.add(adapters); return adapters; } @@ -438,14 +457,15 @@ class RemoteAnimationController implements DeathRecipient { private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING; RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds, - Rect endBounds, Rect startBounds, boolean showBackdrop) { + Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop, + boolean shouldCreateSnapshot) { mWindowContainer = windowContainer; mShowBackdrop = showBackdrop; if (startBounds != null) { mStartBounds = new Rect(startBounds); mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, mStartBounds, mShowBackdrop); - if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { + if (shouldCreateSnapshot && mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { final Rect thumbnailLocalBounds = new Rect(startBounds); thumbnailLocalBounds.offsetTo(0, 0); // Snapshot is located at (0,0) of the animation leash. It doesn't have size diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index cb254982a7807..f5a09e04fc4bc 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2345,11 +2345,7 @@ class TaskFragment extends WindowContainer { @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - - if (mTaskFragmentOrganizer != null) { - updateOrganizedTaskFragmentSurface(); - } - + updateOrganizedTaskFragmentSurface(); sendTaskFragmentInfoChanged(); } @@ -2362,8 +2358,13 @@ class TaskFragment extends WindowContainer { updateOrganizedTaskFragmentSurface(); } - private void updateOrganizedTaskFragmentSurface() { - if (mDelayOrganizedTaskFragmentSurfaceUpdate) { + /** + * TaskFragmentOrganizer doesn't have access to the surface for security reasons, so we need to + * update its surface on the server side if it is not collected for Shell or in pending + * animation. + */ + void updateOrganizedTaskFragmentSurface() { + if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) { return; } if (mTransitionController.isShellTransitionsEnabled() @@ -2395,7 +2396,10 @@ class TaskFragment extends WindowContainer { return; } - final Rect bounds = getBounds(); + // If this TaskFragment is closing while resizing, crop to the starting bounds instead. + final Rect bounds = isClosingWhenResizing() + ? mDisplayContent.mClosingChangingContainers.get(this) + : getBounds(); final int width = bounds.width(); final int height = bounds.height(); if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { @@ -2443,6 +2447,15 @@ class TaskFragment extends WindowContainer { || endBounds.height() != startBounds.height(); } + /** Records the starting bounds of the closing organized TaskFragment. */ + void setClosingChangingStartBoundsIfNeeded() { + if (isOrganizedTaskFragment() && mDisplayContent != null + && mDisplayContent.mChangingContainers.remove(this)) { + mDisplayContent.mClosingChangingContainers.put( + this, new Rect(mSurfaceFreezer.mFreezeBounds)); + } + } + @Override boolean isSyncFinished() { return super.isSyncFinished() && isReadyToTransit(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 80357eb0c7faa..0b80914272dd0 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -810,6 +810,7 @@ class WindowContainer extends ConfigurationContainer< void removeImmediately() { final DisplayContent dc = getDisplayContent(); if (dc != null) { + dc.mClosingChangingContainers.remove(this); mSurfaceFreezer.unfreeze(getSyncTransaction()); } while (!mChildren.isEmpty()) { @@ -1019,9 +1020,12 @@ class WindowContainer extends ConfigurationContainer< * @param dc The display this container is on after changes. */ void onDisplayChanged(DisplayContent dc) { - if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) { - // Cancel any change transition queued-up for this container on the old display. - mSurfaceFreezer.unfreeze(getSyncTransaction()); + if (mDisplayContent != null) { + mDisplayContent.mClosingChangingContainers.remove(this); + if (mDisplayContent.mChangingContainers.remove(this)) { + // Cancel any change transition queued-up for this container on the old display. + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } } mDisplayContent = dc; if (dc != null && dc != this) { @@ -1298,6 +1302,13 @@ class WindowContainer extends ConfigurationContainer< // If we are losing visibility, then a snapshot isn't necessary and we are no-longer // part of a change transition. if (!visible) { + if (asTaskFragment() != null) { + // If the organized TaskFragment is closing while resizing, we want to keep track of + // its starting bounds to make sure the animation starts at the correct position. + // This should be called before unfreeze() because we record the starting bounds + // in SurfaceFreezer. + asTaskFragment().setClosingChangingStartBoundsIfNeeded(); + } mSurfaceFreezer.unfreeze(getSyncTransaction()); } WindowContainer parent = getParent(); @@ -1306,6 +1317,12 @@ class WindowContainer extends ConfigurationContainer< } } + /** Whether this window is closing while resizing. */ + boolean isClosingWhenResizing() { + return mDisplayContent != null + && mDisplayContent.mClosingChangingContainers.containsKey(this); + } + void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); @@ -3004,10 +3021,22 @@ class WindowContainer extends ConfigurationContainer< } final Rect localBounds = new Rect(mTmpRect); localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y); - final RemoteAnimationController.RemoteAnimationRecord adapters = - controller.createRemoteAnimationRecord( - this, mTmpPoint, localBounds, screenBounds, - (isChanging ? mSurfaceFreezer.mFreezeBounds : null), showBackdrop); + final RemoteAnimationController.RemoteAnimationRecord adapters; + if (!isChanging && !enter && isClosingWhenResizing()) { + // Container that is closing while resizing. Pass in the closing start bounds, so + // the animation can start with the correct bounds, there won't be a snapshot. + // Cleanup the mClosingChangingContainers so that when the animation is finished, it + // will reset the surface. + final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers + .remove(this); + adapters = controller.createRemoteAnimationRecord( + this, mTmpPoint, localBounds, screenBounds, closingStartBounds, + showBackdrop, false /* shouldCreateSnapshot */); + } else { + final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null; + adapters = controller.createRemoteAnimationRecord( + this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop); + } if (backdropColor != 0) { adapters.setBackDropColor(backdropColor); } @@ -3464,7 +3493,13 @@ class WindowContainer extends ConfigurationContainer< return; } - getRelativePosition(mTmpPos); + if (isClosingWhenResizing()) { + // This container is closing while resizing, keep its surface at the starting position + // to prevent animation flicker. + getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos); + } else { + getRelativePosition(mTmpPos); + } final int deltaRotation = getRelativeDisplayRotation(); if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) { return; @@ -3529,9 +3564,14 @@ class WindowContainer extends ConfigurationContainer< outSurfaceInsets.setEmpty(); } + /** Gets the position of this container in its parent's coordinate. */ void getRelativePosition(Point outPos) { - final Rect dispBounds = getBounds(); - outPos.set(dispBounds.left, dispBounds.top); + getRelativePosition(getBounds(), outPos); + } + + /** Gets the position of {@code curBounds} in this container's parent's coordinate. */ + void getRelativePosition(Rect curBounds, Point outPos) { + outPos.set(curBounds.left, curBounds.top); final WindowContainer parent = getParent(); if (parent != null) { final Rect parentBounds = parent.getBounds();