Sync SurfaceView position changes to parent render.

In order to provide pixel perfect movement of SurfaceViews
'within' other views (e.g. scrolling) we need to be able to
synchronize the attached (parent window) painting with the
movement of the SurfaceView (recall, SurfaceViews are positioned
behind their attached windows and the parent must render a
transparent region for the SurfaceView to appear). Provide
a new WindowManager method to reposition an attaching window
(that is to say, a window which has an attached window like
SurfaceView) and defer the transaction until the parent frame.
SurfaceView is hooked up to use this for movement. This is still
'racy' in the hardware accelerated case as the render thread
could be on either side of dequeing the frame we are working on.

Bug: 22802885
Change-Id: I025d2bdcbe15c1c11047cc0dbca2cf2b7d67c632
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 017364a..1be2f95 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -96,6 +96,23 @@
             out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
 
     /**
+     *  Position a window relative to it's parent (attached) window without triggering
+     *  a full relayout. This action may be deferred until a given frame number
+     *  for the parent window appears. This allows for synchronizing movement of a child
+     *  to repainting the contents of the parent.
+     *
+     *  @param window The window being modified. Must be attached to a parent window
+     *  or this call will fail.
+     *  @param x The new x position
+     *  @param y The new y position
+     *  @param deferTransactionUntilFrame Frame number from our parent (attached) to
+     *  defer this action until.
+     *  @param outFrame Rect in which is placed the new position/size on screen.
+     */
+    void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+            out Rect outFrame);
+
+    /**
      * If a call to relayout() asked to have the surface destroy deferred,
      * it must call this once it is okay to destroy that surface.
      */
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6de4d3e..394660f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -56,6 +56,8 @@
     private static native int nativeGetWidth(long nativeObject);
     private static native int nativeGetHeight(long nativeObject);
 
+    private static native long nativeGetNextFrameNumber(long nativeObject);
+
     public static final Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
         @Override
@@ -220,6 +222,18 @@
     }
 
     /**
+     * Returns the next frame number which will be dequeued for rendering.
+     * Intended for use with SurfaceFlinger's deferred transactions API.
+     *
+     * @hide
+     */
+    public long getNextFrameNumber() {
+        synchronized (mLock) {
+            return nativeGetNextFrameNumber(mNativeObject);
+        }
+    }
+
+    /**
      * Returns true if the consumer of this Surface is running behind the producer.
      *
      * @return True if the consumer is more than one buffer ahead of the producer.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bcf9b2c..b58c68f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -81,6 +81,9 @@
     private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
     private static native void nativeSetDisplayPowerMode(
             IBinder displayToken, int mode);
+    private static native void nativeDeferTransactionUntil(long nativeObject,
+            IBinder handle, long frame);
+    private static native IBinder nativeGetHandle(long nativeObject);
 
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -358,6 +361,14 @@
         nativeCloseTransaction();
     }
 
+    public void deferTransactionUntil(IBinder handle, long frame) {
+        nativeDeferTransactionUntil(mNativeObject, handle, frame);
+    }
+
+    public IBinder getHandle() {
+        return nativeGetHandle(mNativeObject);
+    }
+
     /** flag the transaction as an animation */
     public static void setAnimationTransaction() {
         nativeSetAnimationTransaction();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db68c29..dddea21 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -448,11 +448,10 @@
         final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
         final boolean visibleChanged = mVisible != mRequestedVisible;
         final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
+        final boolean positionChanged = mLeft != mLocation[0] || mTop != mLocation[1];
 
         if (force || creating || formatChanged || sizeChanged || visibleChanged
-            || mLeft != mLocation[0] || mTop != mLocation[1]
             || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
-
             if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
                     + " visible=" + visibleChanged
@@ -616,11 +615,22 @@
                     mSession.performDeferredDestroy(mWindow);
                 }
             } catch (RemoteException ex) {
+                Log.e(TAG, "Exception from relayout", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
                 " w=" + mLayout.width + " h=" + mLayout.height +
                 ", frame=" + mSurfaceFrame);
+        } else if (positionChanged) { // Only the position has changed
+            mLeft = mLocation[0];
+            mTop = mLocation[1];
+            try {
+                mSession.repositionChild(mWindow, mLeft, mTop,
+                        viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
+                        mWinFrame);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Exception from relayout", ex);
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e17bdd7..faeb353 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6607,6 +6607,20 @@
         }
     }
 
+    long getNextFrameNumber() {
+        long frameNumber = -1;
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.mSurfaceLock.lock();
+        }
+        if (mSurface.isValid()) {
+            frameNumber =  mSurface.getNextFrameNumber();
+        }
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.mSurfaceLock.unlock();
+        }
+        return frameNumber;
+    }
+
     class TakenSurfaceHolder extends BaseSurfaceHolder {
         @Override
         public boolean onAllowLockCanvas() {
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index da96b93..ff51e4e 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -460,6 +460,10 @@
     anw->query(anw, NATIVE_WINDOW_HEIGHT, &value);
     return value;
 }
+static jlong nativeGetNextFrameNumber(JNIEnv *env, jclass clazz, jlong nativeObject) {
+    Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+    return surface->getNextFrameNumber();
+}
 
 namespace uirenderer {
 
@@ -536,6 +540,7 @@
             (void*)nativeWriteToParcel },
     {"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
     {"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
+    {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
 
     // HWUI context
     {"nHwuiCreate", "(JJ)J", (void*) hwui::create },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 931ad54..1dfe40a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -571,6 +571,21 @@
     return JNI_TRUE;
 }
 
+
+static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
+        jobject handleObject, jlong frameNumber) {
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    sp<IBinder> handle = ibinderForJavaObject(env, handleObject);
+
+    ctrl->deferTransactionUntil(handle, frameNumber);
+}
+
+static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+    return javaObjectForIBinder(env, ctrl->getHandle());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -638,6 +653,10 @@
             (void*)nativeGetAnimationFrameStats },
     {"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
             (void*)nativeSetDisplayPowerMode },
+    {"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
+            (void*)nativeDeferTransactionUntil },
+    {"nativeGetHandle", "(J)Landroid/os/IBinder;",
+            (void*)nativeGetHandle }
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f62bc1..c47c377 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -187,6 +187,12 @@
         mService.removeWindow(this, window);
     }
 
+    @Override
+    public void repositionChild(IWindow window, int x, int y, long deferTransactionUntilFrame,
+             Rect outFrame) {
+        mService.repositionChild(this, window, x, y, deferTransactionUntilFrame, outFrame);
+    }
+
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewFlags,
             int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f31f285..4ccaa30 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2468,6 +2468,54 @@
         }
     }
 
+    void repositionChild(Session session, IWindow client,
+            int x, int y, long deferTransactionUntilFrame, Rect outFrame) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized(mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return;
+                }
+                if (win.mAttachedWindow == null) {
+                    throw new IllegalArgumentException(
+                            "repositionChild called but window is not"
+                            + "attached to a parent win=" + win);
+                }
+
+                win.mFrame.left = x;
+                win.mFrame.top = y;
+
+                win.mWinAnimator.computeShownFrameLocked();
+
+                if (SHOW_TRANSACTIONS) {
+                    Slog.i(TAG, ">>> OPEN TRANSACTION repositionChild");
+                }
+
+                SurfaceControl.openTransaction();
+
+                if (deferTransactionUntilFrame > 0) {
+                    win.mWinAnimator.mSurfaceControl.deferTransactionUntil(
+                            win.mAttachedWindow.mWinAnimator.mSurfaceControl.getHandle(),
+                            deferTransactionUntilFrame);
+                }
+                win.mWinAnimator.setSurfaceBoundariesLocked(false);
+
+                SurfaceControl.closeTransaction();
+                if (SHOW_TRANSACTIONS) {
+                    Slog.i(TAG, "<<< CLOSE TRANSACTION repositionChild");
+                }
+
+                outFrame = win.mCompatFrame;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
     public int relayoutWindow(Session session, IWindow client, int seq,
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, int flags,