Bug 1416310 - 3. Merge GeckoLayerClient into other classes; r?rbarker
Move the GeckoLayerClient JNI callbacks to LayerSession.Compositor. Move
the rest of the viewport code to LayerView. Finally, move the input
event synthesis code to NativePanZoomController.
MozReview-Commit-ID: 1FEAM43KcwL
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -43,23 +43,23 @@ public class DynamicToolbarAnimator {
public interface ToolbarChromeProxy {
public Bitmap getBitmapOfToolbarChrome();
public boolean isToolbarChromeVisible();
public void toggleToolbarChrome(boolean aShow);
}
private final Set<PinReason> mPinFlags = Collections.synchronizedSet(EnumSet.noneOf(PinReason.class));
- private final GeckoLayerClient mTarget;
+ private final LayerView mTarget;
private LayerSession.Compositor mCompositor;
private final List<MetricsListener> mListeners;
private ToolbarChromeProxy mToolbarChromeProxy;
private int mMaxToolbarHeight;
- public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
+ public DynamicToolbarAnimator(final LayerView aTarget) {
mTarget = aTarget;
mListeners = new ArrayList<MetricsListener>();
}
public void addMetricsListener(MetricsListener aListener) {
mListeners.add(aListener);
}
@@ -149,27 +149,26 @@ public class DynamicToolbarAnimator {
mCompositor.sendToolbarAnimatorMessage(immediately ?
LayerView.REQUEST_HIDE_TOOLBAR_IMMEDIATELY : LayerView.REQUEST_HIDE_TOOLBAR_ANIMATED);
}
}
/* package-private */ IntSize getViewportSize() {
ThreadUtils.assertOnUiThread();
- int viewWidth = mTarget.getView().getWidth();
- int viewHeight = mTarget.getView().getHeight();
+ int viewWidth = mTarget.getWidth();
+ int viewHeight = mTarget.getHeight();
if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
viewHeight -= mMaxToolbarHeight;
}
return new IntSize(viewWidth, viewHeight);
}
public PointF getVisibleEndOfLayerView() {
- return new PointF(mTarget.getView().getWidth(),
- mTarget.getView().getHeight());
+ return new PointF(mTarget.getWidth(), mTarget.getHeight());
}
/* package-private */ void updateCompositor() {
if (isCompositorReady()) {
mCompositor.setMaxToolbarHeight(mMaxToolbarHeight);
if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
mCompositor.sendToolbarAnimatorMessage(LayerView.REQUEST_SHOW_TOOLBAR_IMMEDIATELY);
} else {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
@@ -26,16 +26,18 @@ public class LayerSession {
// Special message sent from UiCompositorControllerChild once it is open.
/* package */ final static int COMPOSITOR_CONTROLLER_OPEN = 20;
// Special message sent from controller to query if the compositor controller is open.
/* package */ final static int IS_COMPOSITOR_CONTROLLER_OPEN = 21;
protected class Compositor extends JNIObject {
public LayerView layerView;
+ private volatile boolean mContentDocumentIsDisplayed;
+
public boolean isReady() {
return LayerSession.this.isCompositorReady();
}
@WrapForJNI(calledFrom = "ui")
private void onCompositorAttached() {
LayerSession.this.onCompositorAttached();
}
@@ -50,18 +52,17 @@ public class LayerSession {
LayerSession.this.onCompositorDetached();
disposeNative();
}
@WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
@Override protected native void disposeNative();
@WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
- public native void attachToJava(GeckoLayerClient layerClient,
- NativePanZoomController npzc);
+ public native void attachToJava(NativePanZoomController npzc);
@WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
public native void onBoundsChanged(int left, int top, int width, int height);
// Gecko thread creates compositor; blocks UI thread.
@WrapForJNI(calledFrom = "ui", dispatchTo = "proxy")
public native void createCompositor(int width, int height, Object surface);
@@ -122,16 +123,38 @@ public class LayerSession {
}
@WrapForJNI(calledFrom = "ui", dispatchTo = "current")
public native void enableLayerUpdateNotifications(boolean enable);
@WrapForJNI(calledFrom = "ui", dispatchTo = "current")
public native void sendToolbarPixelsToCompositor(final int width, final int height,
final int[] pixels);
+
+ @WrapForJNI(calledFrom = "gecko")
+ private void contentDocumentChanged() {
+ mContentDocumentIsDisplayed = false;
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ private boolean isContentDocumentDisplayed() {
+ return mContentDocumentIsDisplayed;
+ }
+
+ // The compositor invokes this function just before compositing a frame where the
+ // document is different from the document composited on the last frame. In these
+ // cases, the viewport information we have in Java is no longer valid and needs to
+ // be replaced with the new viewport information provided.
+ @WrapForJNI(calledFrom = "ui")
+ public void updateRootFrameMetrics(float scrollX, float scrollY, float zoom) {
+ mContentDocumentIsDisplayed = true;
+ if (layerView != null) {
+ layerView.onMetricsChanged(scrollX, scrollY, zoom);
+ }
+ }
}
protected final Compositor mCompositor = new Compositor();
// Following fields are accessed on UI thread.
private GeckoDisplay mDisplay;
private boolean mAttachedCompositor;
private boolean mCalledCreateCompositor;
--- 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
@@ -44,22 +44,22 @@ import java.util.List;
/**
* A view rendered by the layer compositor.
*/
public class LayerView extends FrameLayout {
private static final String LOGTAG = "GeckoLayerView";
private static AccessibilityManager sAccessibilityManager;
- private GeckoLayerClient mLayerClient;
private PanZoomController mPanZoomController;
private DynamicToolbarAnimator mToolbarAnimator;
private FullScreenState mFullScreenState;
- private Listener mListener;
+ // Accessed on UI thread.
+ private ImmutableViewportMetrics mViewportMetrics;
/* This should only be modified on the Java UI thread. */
private final Overscroll mOverscroll;
private int mDefaultClearColor = Color.WHITE;
/* package */ GetPixelsResult mGetPixelsResult;
private final List<DrawListener> mDrawListeners;
@@ -112,23 +112,23 @@ public class LayerView extends FrameLayo
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();
+ adjustViewportSize();
postCompositorMessage(TOOLBAR_HIDDEN);
break;
case TOOLBAR_SHOW:
// Show toolbar.
mToolbarAnimator.onToggleChrome(true);
- mListener.surfaceChanged();
+ adjustViewportSize();
postCompositorMessage(TOOLBAR_VISIBLE);
break;
case FIRST_PAINT:
setSurfaceBackgroundColor(Color.TRANSPARENT);
break;
case LAYERS_UPDATED:
for (DrawListener listener : mDrawListeners) {
listener.drawFinished();
@@ -161,23 +161,24 @@ public class LayerView extends FrameLayo
mDrawListeners = new ArrayList<DrawListener>();
}
public LayerView(Context context) {
this(context, null);
}
public void initializeView() {
- mLayerClient = new GeckoLayerClient(this);
+ mToolbarAnimator = new DynamicToolbarAnimator(this);
+ mPanZoomController = PanZoomController.Factory.create(this);
if (mOverscroll != null) {
- mLayerClient.setOverscrollHandler(mOverscroll);
+ mPanZoomController.setOverscrollHandler(mOverscroll);
}
- mPanZoomController = mLayerClient.getPanZoomController();
- mToolbarAnimator = mLayerClient.getDynamicToolbarAnimator();
+ mViewportMetrics = new ImmutableViewportMetrics()
+ .setViewportSize(getWidth(), getHeight());
}
/**
* 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.
*/
@@ -190,28 +191,28 @@ public class LayerView extends FrameLayo
});
}
private static Point getEventRadius(MotionEvent event) {
return new Point((int)event.getToolMajor() / 2,
(int)event.getToolMinor() / 2);
}
- public void destroy() {
- if (mLayerClient != null) {
- mLayerClient.destroy();
+ protected void destroy() {
+ if (mPanZoomController != null) {
+ mPanZoomController.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) {
+ if (mOverscroll != null) {
mOverscroll.draw(canvas, getViewportMetrics());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
requestFocus();
@@ -268,24 +269,21 @@ public class LayerView extends FrameLayo
return true;
}
if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) {
return true;
}
return false;
}
- // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
- GeckoLayerClient getLayerClient() { return mLayerClient; }
-
public PanZoomController getPanZoomController() { return mPanZoomController; }
public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; }
public ImmutableViewportMetrics getViewportMetrics() {
- return mLayerClient.getViewportMetrics();
+ return mViewportMetrics;
}
public void setSurfaceBackgroundColor(int newColor) {
}
public interface GetPixelsResult {
public void onPixelsResult(int width, int height, IntBuffer pixels);
}
@@ -312,59 +310,67 @@ public class LayerView extends FrameLayo
/* package */ void recvScreenPixels(int width, int height, int[] pixels) {
if (mGetPixelsResult != null) {
mGetPixelsResult.onPixelsResult(width, height, IntBuffer.wrap(pixels));
mGetPixelsResult = null;
}
}
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- Listener getListener() {
- return mListener;
- }
-
protected void attachCompositor(final LayerSession session) {
mCompositor = session.mCompositor;
mCompositor.layerView = this;
+ // Reset the content-document-is-displayed flag.
+ mCompositor.updateRootFrameMetrics(/* scroolX */ 0, /* scrollY */ 0,
+ /* zoom */ 1.0f);
+
mToolbarAnimator.notifyCompositorCreated(mCompositor);
final NativePanZoomController npzc = (NativePanZoomController) mPanZoomController;
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- mCompositor.attachToJava(mLayerClient, npzc);
+ mCompositor.attachToJava(npzc);
} else {
GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
mCompositor, "attachToJava",
- GeckoLayerClient.class, mLayerClient,
NativePanZoomController.class, npzc);
}
}
@WrapForJNI(calledFrom = "ui")
private Object getCompositor() {
return isCompositorReady() ? mCompositor : null;
}
+ private void adjustViewportSize() {
+ final IntSize viewportSize = mToolbarAnimator.getViewportSize();
+
+ if (mViewportMetrics.viewportRectWidth == viewportSize.width &&
+ mViewportMetrics.viewportRectHeight == viewportSize.height) {
+ return;
+ }
+
+ mViewportMetrics = mViewportMetrics.setViewportSize(viewportSize.width,
+ viewportSize.height);
+ }
+
/* package */ void onSizeChanged(int width, int height) {
- if (mListener != null) {
- mListener.surfaceChanged();
- }
+ adjustViewportSize();
if (mOverscroll != null) {
mOverscroll.setSize(width, height);
}
}
- public interface Listener {
- void surfaceChanged();
+ /* package */ void onMetricsChanged(final float scrollX, final float scrollY,
+ final float zoom) {
+ mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY)
+ .setZoomFactor(zoom);
+ mToolbarAnimator.onMetricsChanged(mViewportMetrics);
}
@RobocopTarget
public void addDrawListener(final DrawListener listener) {
if (!ThreadUtils.isOnUiThread()) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
@@ -405,17 +411,17 @@ public class LayerView extends FrameLayo
}
@RobocopTarget
public static interface DrawListener {
public void drawFinished();
}
public float getZoomFactor() {
- return getLayerClient().getViewportMetrics().zoomFactor;
+ return getViewportMetrics().zoomFactor;
}
public void setFullScreenState(FullScreenState state) {
mFullScreenState = state;
}
public boolean isFullScreen() {
return mFullScreenState != FullScreenState.NONE;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -8,32 +8,39 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONObject;
import android.graphics.PointF;
+import android.os.SystemClock;
+import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.InputDevice;
+import java.util.ArrayList;
+
class NativePanZoomController extends JNIObject implements PanZoomController {
+ private static final String LOGTAG = "GeckoNPZC";
private final float MAX_SCROLL;
private final LayerView mView;
private boolean mDestroyed;
private Overscroll mOverscroll;
private float mPointerScrollFactor;
private long mLastDownTime;
+ private SynthesizedEventState mPointerState;
+
@WrapForJNI(calledFrom = "ui")
private native boolean handleMotionEvent(
int action, int actionIndex, long time, int metaState,
int pointerId[], float x[], float y[], float orientation[], float pressure[],
float toolMajor[], float toolMinor[]);
@WrapForJNI(calledFrom = "ui")
private native boolean handleScrollEvent(
@@ -251,9 +258,236 @@ class NativePanZoomController extends JN
/**
* Active SelectionCaretDrag requires DynamicToolbarAnimator to be pinned
* to avoid unwanted scroll interactions.
*/
@WrapForJNI(calledFrom = "gecko")
private void onSelectionDragState(boolean state) {
mView.getDynamicToolbarAnimator().setPinned(state, PinReason.CARET_DRAG);
}
+
+ private static class PointerInfo {
+ // We reserve one pointer ID for the mouse, so that tests don't have
+ // to worry about tracking pointer IDs if they just want to test mouse
+ // event synthesization. If somebody tries to use this ID for a
+ // synthesized touch event we'll throw an exception.
+ public static final int RESERVED_MOUSE_POINTER_ID = 100000;
+
+ public int pointerId;
+ public int source;
+ public int screenX;
+ public int screenY;
+ public double pressure;
+ public int orientation;
+
+ public MotionEvent.PointerCoords getCoords() {
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.orientation = orientation;
+ coords.pressure = (float)pressure;
+ coords.x = screenX;
+ coords.y = screenY;
+ return coords;
+ }
+ }
+
+ private static class SynthesizedEventState {
+ public final ArrayList<PointerInfo> pointers;
+ public long downTime;
+
+ SynthesizedEventState() {
+ pointers = new ArrayList<PointerInfo>();
+ }
+
+ int getPointerIndex(int pointerId) {
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).pointerId == pointerId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ int addPointer(int pointerId, int source) {
+ PointerInfo info = new PointerInfo();
+ info.pointerId = pointerId;
+ info.source = source;
+ pointers.add(info);
+ return pointers.size() - 1;
+ }
+
+ int getPointerCount(int source) {
+ int count = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ MotionEvent.PointerProperties[] getPointerProperties(int source) {
+ MotionEvent.PointerProperties[] props =
+ new MotionEvent.PointerProperties[getPointerCount(source)];
+ int index = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ MotionEvent.PointerProperties p = new MotionEvent.PointerProperties();
+ p.id = pointers.get(i).pointerId;
+ switch (source) {
+ case InputDevice.SOURCE_TOUCHSCREEN:
+ p.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ break;
+ case InputDevice.SOURCE_MOUSE:
+ p.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+ break;
+ }
+ props[index++] = p;
+ }
+ }
+ return props;
+ }
+
+ MotionEvent.PointerCoords[] getPointerCoords(int source) {
+ MotionEvent.PointerCoords[] coords =
+ new MotionEvent.PointerCoords[getPointerCount(source)];
+ int index = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ coords[index++] = pointers.get(i).getCoords();
+ }
+ }
+ return coords;
+ }
+ }
+
+ private void synthesizeNativePointer(int source, int pointerId, int eventType,
+ int screenX, int screenY, double pressure,
+ int orientation)
+ {
+ final View view = mView;
+ final int[] origin = new int[2];
+ view.getLocationOnScreen(origin);
+ screenX -= origin[0];
+ screenY -= origin[1];
+
+ if (mPointerState == null) {
+ mPointerState = new SynthesizedEventState();
+ }
+
+ // Find the pointer if it already exists
+ int pointerIndex = mPointerState.getPointerIndex(pointerId);
+
+ // Event-specific handling
+ switch (eventType) {
+ case MotionEvent.ACTION_POINTER_UP:
+ if (pointerIndex < 0) {
+ Log.w(LOGTAG, "Pointer-up for invalid pointer");
+ return;
+ }
+ if (mPointerState.pointers.size() == 1) {
+ // Last pointer is going up
+ eventType = MotionEvent.ACTION_UP;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (pointerIndex < 0) {
+ Log.w(LOGTAG, "Pointer-cancel for invalid pointer");
+ return;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (pointerIndex < 0) {
+ // Adding a new pointer
+ pointerIndex = mPointerState.addPointer(pointerId, source);
+ if (pointerIndex == 0) {
+ // first pointer
+ eventType = MotionEvent.ACTION_DOWN;
+ mPointerState.downTime = SystemClock.uptimeMillis();
+ }
+ } else {
+ // We're moving an existing pointer
+ eventType = MotionEvent.ACTION_MOVE;
+ }
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (pointerIndex < 0) {
+ // Mouse-move a pointer without it going "down". However
+ // in order to send the right MotionEvent without a lot of
+ // duplicated code, we add the pointer to mPointerState,
+ // and then remove it at the bottom of this function.
+ pointerIndex = mPointerState.addPointer(pointerId, source);
+ } else {
+ // We're moving an existing mouse pointer that went down.
+ eventType = MotionEvent.ACTION_MOVE;
+ }
+ break;
+ }
+
+ // Update the pointer with the new info
+ PointerInfo info = mPointerState.pointers.get(pointerIndex);
+ info.screenX = screenX;
+ info.screenY = screenY;
+ info.pressure = pressure;
+ info.orientation = orientation;
+
+ // Dispatch the event
+ int action = 0;
+ if (eventType == MotionEvent.ACTION_POINTER_DOWN ||
+ eventType == MotionEvent.ACTION_POINTER_UP) {
+ // for pointer-down and pointer-up events we need to add the
+ // index of the relevant pointer.
+ action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
+ }
+ action |= (eventType & MotionEvent.ACTION_MASK);
+ boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
+ (eventType == MotionEvent.ACTION_DOWN ||
+ eventType == MotionEvent.ACTION_MOVE);
+ final MotionEvent event = MotionEvent.obtain(
+ /*downTime*/ mPointerState.downTime,
+ /*eventTime*/ SystemClock.uptimeMillis(),
+ /*action*/ action,
+ /*pointerCount*/ mPointerState.getPointerCount(source),
+ /*pointerProperties*/ mPointerState.getPointerProperties(source),
+ /*pointerCoords*/ mPointerState.getPointerCoords(source),
+ /*metaState*/ 0,
+ /*buttonState*/ (isButtonDown ? MotionEvent.BUTTON_PRIMARY : 0),
+ /*xPrecision*/ 0,
+ /*yPrecision*/ 0,
+ /*deviceId*/ 0,
+ /*edgeFlags*/ 0,
+ /*source*/ source,
+ /*flags*/ 0);
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.dispatchTouchEvent(event);
+ }
+ });
+
+ // Forget about removed pointers
+ if (eventType == MotionEvent.ACTION_POINTER_UP ||
+ eventType == MotionEvent.ACTION_UP ||
+ eventType == MotionEvent.ACTION_CANCEL ||
+ eventType == MotionEvent.ACTION_HOVER_MOVE)
+ {
+ mPointerState.pointers.remove(pointerIndex);
+ }
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ private void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
+ int screenY, double pressure, int orientation)
+ {
+ if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
+ throw new IllegalArgumentException("Pointer ID reserved for mouse");
+ }
+ synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
+ eventType, screenX, screenY, pressure, orientation);
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ private void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) {
+ synthesizeNativePointer(InputDevice.SOURCE_MOUSE,
+ PointerInfo.RESERVED_MOUSE_POINTER_ID,
+ eventType, screenX, screenY, 0, 0);
+ }
}