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