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 565573 5e68d85ce6156383223e4d022c391e93beb8f1e7
parent 565572 6a4a15ca3a7807a46d9cacc4a187c33f73858bd2
child 565574 7063934c54936173b9ed9f29225522e9c5b88437
push id54904
push userbmo:rbarker@mozilla.com
push dateThu, 20 Apr 2017 02:39:58 +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. MozReview-Commit-ID: DjCaTFR1eZS
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,99 @@ 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;
+    /* package */ GetPixelsResult mGetPixelsResult;
+    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/ipc/UiCompositorControllerMessageTypes.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;  // Sent from compositor after first paint
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6;  // Sent to compositor requesting toolbar be shown immediately
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_ANIMATED    = 7;  // Sent to compositor requesting toolbar be shown animated
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8;  // Sent to compositor requesting toolbar be hidden immediately
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_ANIMATED    = 9;  // Sent to compositor requesting toolbar be hidden animated
+    /* package */ final static int LAYERS_UPDATED                    = 10; // Sent from compositor when a layer has been updated
+    /* 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,44 +126,133 @@ 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 message);
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvToolbarAnimatorMessage(int message) {
+            handleToolbarAnimatorMessage(message);
+        }
+
+        @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 width, int height, int[] pixels) {
+            if (mGetPixelsResult != null) {
+                mGetPixelsResult.onPixelsResult(width, height, IntBuffer.wrap(pixels));
+                mGetPixelsResult = null;
+            }
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void enableLayerUpdateNotifications(boolean enable);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void sendToolbarPixelsToCompositor(final int width, final int height, final int[] pixels);
+
         @WrapForJNI(calledFrom = "gecko")
         private void reattach() {
             mCompositorCreated = true;
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    // Make sure it is still valid
+                    if (mCompositorCreated) {
+                        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.mLayerClient.setGeckoReady(false);
 
             // Then clear out any pending calls on the UI thread by disposing on the UI thread.
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
+                    LayerView.this.mToolbarAnimator.notifyCompositorDestroyed();
+                    LayerView.this.mDrawListeners.clear();
                     disposeNative();
                 }
             });
         }
     }
 
-    private final Compositor mCompositor = new Compositor();
+    /* package */ void handleToolbarAnimatorMessage(int message) {
+        switch(message) {
+            case STATIC_TOOLBAR_NEEDS_UPDATE:
+                // Send updated toolbar image to compositor.
+                Bitmap bm = mToolbarAnimator.getBitmapOfToolbarChrome();
+                if (bm == null) {
+                    break;
+                }
+                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);
+                    mCompositor.sendToolbarPixelsToCompositor(width, height, pixels);
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Caught exception 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 LAYERS_UPDATED:
+                for (DrawListener listener : mDrawListeners) {
+                    listener.drawFinished();
+                }
+                break;
+            case COMPOSITOR_CONTROLLER_OPEN:
+                mToolbarAnimator.notifyCompositorControllerOpen();
+                break;
+            default:
+                Log.e(LOGTAG,"Unhandled Toolbar Animator Message: " + message);
+                break;
+        }
+    }
 
-    /* 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;
+    private final Compositor mCompositor = new Compositor();
 
     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;
 
         /*
@@ -149,49 +270,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 +329,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 +347,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 +452,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 +479,38 @@ 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 width, int height, IntBuffer pixels);
     }
 
-    /** Used by robocop for testing purposes. Not for production use! */
     @RobocopTarget
-    public IntBuffer getPixels() {
-        return mRenderer.getPixels();
-    }
+    public void getPixels(final GetPixelsResult getPixelsResult) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    getPixels(getPixelsResult);
+                }
+            });
+            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 (mCompositorCreated) {
+            mGetPixelsResult = getPixelsResult;
+            mCompositor.requestScreenPixels();
+        } else {
+            getPixelsResult.onPixelsResult(0, 0, null);
+        }
     }
 
     public void setListener(Listener listener) {
         mListener = listener;
     }
 
     Listener getListener() {
         return mListener;
@@ -451,16 +565,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 +608,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) {
@@ -539,30 +656,18 @@ public class LayerView extends FrameLayo
 
     public Object getSurface() {
       if (mSurfaceView != null) {
         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);
         }
@@ -578,19 +683,19 @@ public class LayerView extends FrameLayo
     }
 
     /* A subclass of SurfaceView to listen to layout changes, as
      * View.OnLayoutChangeListener requires API level 11.
      */
     private class LayerSurfaceView extends SurfaceView {
         private LayerView mParent;
 
-        public LayerSurfaceView(Context aContext, LayerView aParent) {
-            super(aContext);
-            mParent = aParent;
+        public LayerSurfaceView(Context context, LayerView parent) {
+            super(context);
+            mParent = parent;
         }
 
         @Override
         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
             super.onLayout(changed, left, top, right, bottom);
             if (changed && mParent.mServerSurfaceValid) {
                 mParent.surfaceChanged(right - left, bottom - top);
             }
@@ -618,23 +723,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 +793,33 @@ 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 maxHeight) {
+        mToolbarAnimator.setMaxToolbarHeight(maxHeight);
     }
 
-    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 setClearColor(final int color) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    setClearColor(color);
+                }
+            });
+            return;
+        }
 
-    public void removeZoomedViewListener(ZoomedViewListener listener) {
-        mRenderer.removeZoomedViewListener(listener);
-    }
-
-    public void setClearColor(int color) {
-        if (mLayerClient != null) {
-            mLayerClient.setClearColor(color);
+        mDefaultClearColor = color;
+        if (mCompositorCreated) {
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
         }
-    }
+   }
 }