--- 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);
+ }
+ }
}