Bug 1335895 - part 24: update LayerView.java support new DynamicToolbar r=jchen,kats draft
authorRandall Barker <rbarker@mozilla.com>
Mon, 27 Mar 2017 10:45:30 -0700
changeset 558766 d73190b4199e03bc9f0247ebcc511267468dfa5b
parent 558765 2d1646ecc7a696eb4ad023ec73f677acd312915d
child 558767 797499f4537e1a4e67b52bf6e3f7e7ae7b570b1a
push id52941
push userbmo:rbarker@mozilla.com
push dateFri, 07 Apr 2017 23:43:33 +0000
reviewersjchen, kats
bugs1335895
milestone55.0a1
Bug 1335895 - part 24: update LayerView.java support new DynamicToolbar r=jchen,kats LayerView.Compositor was updated to support IPC with the compositor thread to enable communicating with AndroidDynamicToolbarAnimator.
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -13,67 +13,100 @@ import org.mozilla.gecko.annotation.Robo
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.os.Parcelable;
+import android.support.v4.util.SimpleArrayMap;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.InputDevice;
 import android.widget.FrameLayout;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A view rendered by the layer compositor.
  */
 public class LayerView extends FrameLayout {
     private static final String LOGTAG = "GeckoLayerView";
 
     private GeckoLayerClient mLayerClient;
     private PanZoomController mPanZoomController;
     private DynamicToolbarAnimator mToolbarAnimator;
-    private LayerRenderer mRenderer;
     /* Must be a PAINT_xxx constant */
-    private int mPaintState;
     private FullScreenState mFullScreenState;
 
     private SurfaceView mSurfaceView;
     private TextureView mTextureView;
 
     private Listener mListener;
 
     /* This should only be modified on the Java UI thread. */
     private final Overscroll mOverscroll;
 
     private boolean mServerSurfaceValid;
     private int mWidth, mHeight;
 
     private boolean onAttachedToWindowCalled;
+    private int mDefaultClearColor = Color.WHITE;
+    private GetPixelsResult mGetPixelsResult;
+    private volatile boolean mGotPixelsResultFromCompositor;
+    private final List<DrawListener> mDrawListeners;
 
     /* This is written by the Gecko thread and the UI thread, and read by the UI thread. */
     @WrapForJNI(stubName = "CompositorCreated", calledFrom = "ui")
     /* package */ volatile boolean mCompositorCreated;
 
-    private class Compositor extends JNIObject {
+    //
+    // NOTE: These values are also defined in gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
+    //       and must be kept in sync. Any new AnimatorMessageType added here must also be added there.
+    //
+    /* package */ final static int STATIC_TOOLBAR_NEEDS_UPDATE = 0; // Sent from compositor when the static toolbar wants to hide.
+    /* package */ final static int STATIC_TOOLBAR_READY = 1;        // Sent from compositor when the static toolbar image has been updated and is ready to animate.
+    /* package */ final static int TOOLBAR_HIDDEN = 2;              // Sent to compositor when the real toolbar has been hidden.
+    /* package */ final static int TOOLBAR_VISIBLE = 3;             // Sent to compositor when the real toolbar is visible.
+    /* package */ final static int TOOLBAR_SHOW = 4;                // Sent from compositor when the static toolbar has been made visible so the real toolbar should be shown.
+    /* package */ final static int FIRST_PAINT = 5;
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6;
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_ANIMATED = 7;
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8;
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_ANIMATED = 9;
+    /* package */ final static int COMPOSITOR_CONTROLLER_OPEN = 20; // Special message sent from UiCompositorControllerChild once it is open
+
+    private void postCompositorMessage (final int message) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mCompositor.sendToolbarAnimatorMessage(message);
+            }
+        });
+    }
+
+    /* package */ class Compositor extends JNIObject {
         public Compositor() {
         }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         @Override protected native void disposeNative();
 
         // Gecko thread sets its Java instances; does not block UI thread.
         @WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
@@ -94,45 +127,128 @@ public class LayerView extends FrameLayo
 
         // UI thread resumes compositor and notifies Gecko thread; does not block UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         /* package */ native void syncResumeResizeCompositor(int width, int height, Object surface);
 
         @WrapForJNI(calledFrom = "any", dispatchTo = "current")
         /* package */ native void syncInvalidateAndScheduleComposite();
 
+        @WrapForJNI(calledFrom = "any", dispatchTo = "current")
+        /* package */ native void setMaxToolbarHeight(int height);
+
+        @WrapForJNI(calledFrom = "any", dispatchTo = "current")
+        /* package */ native void setPinned(boolean pinned, int reason);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void sendToolbarAnimatorMessage(int aMessage);
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvToolbarAnimatorMessage(int aMessage) {
+            switch(aMessage) {
+                case STATIC_TOOLBAR_NEEDS_UPDATE:
+                    // Send updated toolbar image to compositor.
+                    Bitmap bm = mToolbarAnimator.getBitmapOfToolbarChrome();
+                    if (bm != null) {
+                        final int width = bm.getWidth();
+                        final int height = bm.getHeight();
+                        int[] pixels = new int[bm.getByteCount() / 4];
+                        try {
+                            bm.getPixels(pixels, /* offset */ 0, /* stride */ width, /* x */ 0, /* y */ 0, width, height);
+                            sendToolbarPixelsToCompositor(width, height, pixels);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(LOGTAG, "Caught IllegalArgumentException while getting toolbar pixels from Bitmap: " + e.toString());
+                        } catch (ArrayIndexOutOfBoundsException e) {
+                            Log.e(LOGTAG, "Caught ArrayIndexOutOfBoundsException while getting toolbar pixels from Bitmap: " + e.toString());
+                        } catch (IllegalStateException e) {
+                            Log.e(LOGTAG, "Caught IllegalStateException while getting toolbar pixels from Bitmap: " + e.toString());
+                        }
+                    }
+                break;
+                case STATIC_TOOLBAR_READY:
+                    // Hide toolbar and send TOOLBAR_HIDDEN message to compositor
+                    mToolbarAnimator.onToggleChrome(false);
+                    mListener.surfaceChanged();
+                    postCompositorMessage(TOOLBAR_HIDDEN);
+                break;
+                case TOOLBAR_SHOW:
+                    // Show toolbar.
+                    mToolbarAnimator.onToggleChrome(true);
+                    mListener.surfaceChanged();
+                    postCompositorMessage(TOOLBAR_VISIBLE);
+                break;
+                case FIRST_PAINT:
+                    setSurfaceBackgroundColor(Color.TRANSPARENT);
+                break;
+                case COMPOSITOR_CONTROLLER_OPEN:
+                    mToolbarAnimator.notifyCompositorControllerOpen();
+                break;
+                default:
+                    Log.e(LOGTAG,"Unhandled Toolbar Animator Message: " + aMessage);
+            }
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void setDefaultClearColor(int color);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void requestScreenPixels();
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvScreenPixels(int aWidth, int aHeight, int aPixels[]) {
+            if (mGetPixelsResult != null) {
+                mGetPixelsResult.onPixelsResult(aWidth, aHeight, IntBuffer.wrap(aPixels));
+                mGetPixelsResult = null;
+                mGotPixelsResultFromCompositor = true;
+            }
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void enableLayerUpdateNotifications(boolean aEnable);
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvLayerUpdated() {
+            for (DrawListener listener : mDrawListeners) {
+                listener.drawFinished();
+            }
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void sendToolbarPixelsToCompositor(final int aWidth, final int aHight, final int[] aPixels);
+
         @WrapForJNI(calledFrom = "gecko")
         private void reattach() {
             mCompositorCreated = true;
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
+            mCompositor.enableLayerUpdateNotifications(!mDrawListeners.isEmpty());
+            mToolbarAnimator.notifyCompositorCreated(mCompositor);
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private void destroy() {
             // The nsWindow has been closed. First mark our compositor as destroyed.
             LayerView.this.mCompositorCreated = false;
+            LayerView.this.mToolbarAnimator.notifyCompositorDestroyed();
 
             LayerView.this.mLayerClient.setGeckoReady(false);
 
+            LayerView.this.mDrawListeners.clear();
+
             // Then clear out any pending calls on the UI thread by disposing on the UI thread.
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     disposeNative();
                 }
             });
         }
     }
 
     private final Compositor mCompositor = new Compositor();
 
-    /* Flags used to determine when to show the painted surface. */
-    public static final int PAINT_START = 0;
-    public static final int PAINT_BEFORE_FIRST = 1;
-    public static final int PAINT_AFTER_FIRST = 2;
-
     public boolean shouldUseTextureView() {
         // Disable TextureView support for now as it causes panning/zooming
         // performance regressions (see bug 792259). Uncomment the code below
         // once this bug is fixed.
         return false;
 
         /*
         // we can only use TextureView on ICS or higher
@@ -149,49 +265,54 @@ public class LayerView extends FrameLayo
             Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString());
             return false;
         } */
     }
 
     public LayerView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mPaintState = PAINT_START;
         mFullScreenState = FullScreenState.NONE;
 
         mOverscroll = new OverscrollEdgeEffect(this);
+        mDrawListeners = new ArrayList<DrawListener>();
     }
 
     public LayerView(Context context) {
         this(context, null);
     }
 
     public void initializeView() {
         mLayerClient = new GeckoLayerClient(getContext(), this);
         if (mOverscroll != null) {
             mLayerClient.setOverscrollHandler(mOverscroll);
         }
 
         mPanZoomController = mLayerClient.getPanZoomController();
         mToolbarAnimator = mLayerClient.getDynamicToolbarAnimator();
 
-        mRenderer = new LayerRenderer(this);
-
         setFocusable(true);
         setFocusableInTouchMode(true);
 
         GeckoAccessibility.setDelegate(this);
     }
 
     /**
      * MotionEventHelper dragAsync() robocop tests can instruct
      * PanZoomController not to generate longpress events.
+     * This call comes in from a thread other than the UI thread.
+     * So dispatch to UI thread first to prevent assert in nsWindow.
      */
-    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
-        mPanZoomController.setIsLongpressEnabled(isLongpressEnabled);
+    public void setIsLongpressEnabled(final boolean isLongpressEnabled) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mPanZoomController.setIsLongpressEnabled(isLongpressEnabled);
+            }
+        });
     }
 
     private static Point getEventRadius(MotionEvent event) {
         return new Point((int)event.getToolMajor() / 2,
                          (int)event.getToolMinor() / 2);
     }
 
     public void showSurface() {
@@ -203,19 +324,16 @@ public class LayerView extends FrameLayo
         // Fix this if TextureView support is turned back on above
         mSurfaceView.setVisibility(View.INVISIBLE);
     }
 
     public void destroy() {
         if (mLayerClient != null) {
             mLayerClient.destroy();
         }
-        if (mRenderer != null) {
-            mRenderer.destroy();
-        }
     }
 
     @Override
     public void dispatchDraw(final Canvas canvas) {
         super.dispatchDraw(canvas);
 
         // We must have a layer client to get valid viewport metrics
         if (mLayerClient != null && mOverscroll != null) {
@@ -224,22 +342,16 @@ public class LayerView extends FrameLayo
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             requestFocus();
         }
 
-        if (mToolbarAnimator != null && mToolbarAnimator.onInterceptTouchEvent(event)) {
-            if (mPanZoomController != null) {
-                mPanZoomController.onMotionEventVelocity(event.getEventTime(), mToolbarAnimator.getVelocity());
-            }
-            return true;
-        }
         if (!mLayerClient.isGeckoReady()) {
             // If gecko isn't loaded yet, don't try sending events to the
             // native code because it's just going to crash
             return true;
         }
         if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) {
             return true;
         }
@@ -335,16 +447,20 @@ public class LayerView extends FrameLayo
         super.onDetachedFromWindow();
 
         onAttachedToWindowCalled = false;
     }
 
     // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
     GeckoLayerClient getLayerClient() { return mLayerClient; }
 
+    /* package */ boolean isGeckoReady() {
+        return mLayerClient.isGeckoReady();
+    }
+
     public PanZoomController getPanZoomController() { return mPanZoomController; }
     public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; }
 
     public ImmutableViewportMetrics getViewportMetrics() {
         return mLayerClient.getViewportMetrics();
     }
 
     public Matrix getMatrixForLayerRectToViewRect() {
@@ -358,45 +474,41 @@ public class LayerView extends FrameLayo
     }
 
     public void requestRender() {
         if (mCompositorCreated) {
             mCompositor.syncInvalidateAndScheduleComposite();
         }
     }
 
-    public void postRenderTask(RenderTask task) {
-        mRenderer.postRenderTask(task);
-    }
-
-    public void removeRenderTask(RenderTask task) {
-        mRenderer.removeRenderTask(task);
-    }
-
-    public int getMaxTextureSize() {
-        return mRenderer.getMaxTextureSize();
+    public interface GetPixelsResult {
+        public void onPixelsResult(int aWidth, int aHeight, IntBuffer aPixels);
     }
 
-    /** Used by robocop for testing purposes. Not for production use! */
     @RobocopTarget
-    public IntBuffer getPixels() {
-        return mRenderer.getPixels();
-    }
+    public void getPixels(final GetPixelsResult aGetPixelsResult) {
+        if (!ThreadUtils.isOnUiThread()) {
+            mGotPixelsResultFromCompositor = false;
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    getPixels(aGetPixelsResult);
+                }
+            });
+            return;
+        }
 
-    /* paintState must be a PAINT_xxx constant. */
-    public void setPaintState(int paintState) {
-        mPaintState = paintState;
-    }
-
-    public int getPaintState() {
-        return mPaintState;
-    }
-
-    public LayerRenderer getRenderer() {
-        return mRenderer;
+        if (aGetPixelsResult != null) {
+            if (mCompositorCreated) {
+                mGetPixelsResult = aGetPixelsResult;
+                mCompositor.requestScreenPixels();
+            } else {
+                aGetPixelsResult.onPixelsResult(0, 0, null);
+            }
+        }
     }
 
     public void setListener(Listener listener) {
         mListener = listener;
     }
 
     Listener getListener() {
         return mListener;
@@ -451,16 +563,19 @@ public class LayerView extends FrameLayo
         }
 
         // Only try to create the compositor if we have a valid surface and gecko is up. When these
         // two conditions are satisfied, we can be relatively sure that the compositor creation will
         // happen without needing to block anywhere.
         if (mServerSurfaceValid && getLayerClient().isGeckoReady()) {
             mCompositorCreated = true;
             mCompositor.createCompositor(mWidth, mHeight, getSurface());
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
+            mCompositor.enableLayerUpdateNotifications(!mDrawListeners.isEmpty());
+            mToolbarAnimator.notifyCompositorCreated(mCompositor);
         }
     }
 
     /* When using a SurfaceView (mSurfaceView != null), resizing happens in two
      * phases. First, the LayerView changes size, then, often some frames later,
      * the SurfaceView changes size. Because of this, we need to split the
      * resize into two phases to avoid jittering.
      *
@@ -491,17 +606,17 @@ public class LayerView extends FrameLayo
             mOverscroll.setSize(width, height);
         }
     }
 
     private void surfaceChanged(int width, int height) {
         serverSurfaceChanged(width, height);
 
         if (mListener != null) {
-            mListener.surfaceChanged(width, height);
+            mListener.surfaceChanged();
         }
 
         if (mOverscroll != null) {
             mOverscroll.setSize(width, height);
         }
     }
 
     void notifySizeChanged(int windowWidth, int windowHeight, int screenWidth, int screenHeight) {
@@ -542,27 +657,20 @@ public class LayerView extends FrameLayo
         return mSurfaceView.getHolder().getSurface();
       }
       return null;
     }
 
     // This method is called on the Gecko main thread.
     @WrapForJNI(calledFrom = "gecko")
     private static void updateZoomedView(ByteBuffer data) {
-        LayerView layerView = GeckoAppShell.getLayerView();
-        if (layerView != null) {
-            LayerRenderer layerRenderer = layerView.getRenderer();
-            if (layerRenderer != null) {
-                layerRenderer.updateZoomedView(data);
-            }
-        }
     }
 
     public interface Listener {
-        void surfaceChanged(int width, int height);
+        void surfaceChanged();
     }
 
     private class SurfaceListener implements SurfaceHolder.Callback {
         @Override
         public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                                 int height) {
             onSizeChanged(width, height);
         }
@@ -618,23 +726,51 @@ public class LayerView extends FrameLayo
 
         @Override
         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
         }
     }
 
     @RobocopTarget
-    public void addDrawListener(DrawListener listener) {
-        mLayerClient.addDrawListener(listener);
+    public void addDrawListener(final DrawListener listener) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    addDrawListener(listener);
+                }
+            });
+            return;
+        }
+
+        boolean wasEmpty = mDrawListeners.isEmpty();
+        mDrawListeners.add(listener);
+        if (mCompositorCreated && wasEmpty) {
+          mCompositor.enableLayerUpdateNotifications(true);
+        }
     }
 
     @RobocopTarget
-    public void removeDrawListener(DrawListener listener) {
-        mLayerClient.removeDrawListener(listener);
+    public void removeDrawListener(final DrawListener listener) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    removeDrawListener(listener);
+                }
+            });
+            return;
+        }
+
+        boolean notEmpty = mDrawListeners.isEmpty();
+        mDrawListeners.remove(listener);
+        if (mCompositorCreated && notEmpty && mDrawListeners.isEmpty()) {
+          mCompositor.enableLayerUpdateNotifications(false);
+        }
     }
 
     @RobocopTarget
     public static interface DrawListener {
         public void drawFinished();
     }
 
     @Override
@@ -660,49 +796,46 @@ public class LayerView extends FrameLayo
     public void setFullScreenState(FullScreenState state) {
         mFullScreenState = state;
     }
 
     public boolean isFullScreen() {
         return mFullScreenState != FullScreenState.NONE;
     }
 
-    public void setMaxTranslation(float aMaxTranslation) {
-        mToolbarAnimator.setMaxTranslation(aMaxTranslation);
-    }
-
-    public void setSurfaceTranslation(float translation) {
-        setTranslationY(translation);
+    public void setMaxToolbarHeight(int aMaxHeight) {
+        mToolbarAnimator.setMaxToolbarHeight(aMaxHeight);
     }
 
-    public float getSurfaceTranslation() {
-        return getTranslationY();
-    }
-
-    // Public hooks for dynamic toolbar translation
-
-    public interface DynamicToolbarListener {
-        public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation);
-        public void onPanZoomStopped();
-        public void onMetricsChanged(ImmutableViewportMetrics viewport);
+    public int getCurrentToolbarHeight() {
+        return mToolbarAnimator.getCurrentToolbarHeight();
     }
 
     // Public hooks for zoomed view
 
     public interface ZoomedViewListener {
         public void requestZoomedViewRender();
         public void updateView(ByteBuffer data);
     }
 
     public void addZoomedViewListener(ZoomedViewListener listener) {
-        mRenderer.addZoomedViewListener(listener);
     }
 
     public void removeZoomedViewListener(ZoomedViewListener listener) {
-        mRenderer.removeZoomedViewListener(listener);
     }
 
-    public void setClearColor(int color) {
-        if (mLayerClient != null) {
-            mLayerClient.setClearColor(color);
+    public void setClearColor(final int color) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    setClearColor(color);
+                }
+            });
+            return;
         }
-    }
+
+        mDefaultClearColor = color;
+        if (mCompositorCreated) {
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
+        }
+   }
 }