gecko/mobile/android/base/gfx/GeckoLayerClient.java
Kartikaya Gupta fbe94f983a Merge the origin (Point) and size (IntSize) properties of Layers into a single position (Rect) property.
Not only does this reduce the amount of cruft needed while getting and setting these properties, it
makes the code more consistent because we don't have half of this stored in the Layer base class and
the other half provided by an abstract method implementation in subclasses. Furthermore, this
allows the VirtualLayer size to be updated based on the area painted by gecko rather than remaining
fixed at the view size when the virtual layer was created.
2012-02-26 10:47:47 -05:00

451 lines
17 KiB
Java

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.FloatUtils;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoEventListener;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GeckoLayerClient implements GeckoEventListener,
FlexibleGLSurfaceView.Listener {
private static final String LOGTAG = "GeckoLayerClient";
private LayerController mLayerController;
private LayerRenderer mLayerRenderer;
private boolean mLayerRendererInitialized;
private IntSize mScreenSize;
private IntSize mWindowSize;
private IntSize mBufferSize;
private VirtualLayer mRootLayer;
/* The viewport that Gecko is currently displaying. */
private ViewportMetrics mGeckoViewport;
/* The viewport that Gecko will display when drawing is finished */
private ViewportMetrics mNewGeckoViewport;
private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
private long mLastViewportChangeTime;
private boolean mPendingViewportAdjust;
private boolean mViewportSizeChanged;
private boolean mIgnorePaintsPendingViewportSizeChange;
private boolean mFirstPaint = true;
// mUpdateViewportOnEndDraw is used to indicate that we received a
// viewport update notification while drawing. therefore, when the
// draw finishes, we need to update the entire viewport rather than
// just the page size. this boolean should always be accessed from
// inside a transaction, so no synchronization is needed.
private boolean mUpdateViewportOnEndDraw;
private String mLastCheckerboardColor;
private static Pattern sColorPattern;
/* Used by robocop for testing purposes */
private DrawListener mDrawListener;
public GeckoLayerClient(Context context) {
mScreenSize = new IntSize(0, 0);
mBufferSize = new IntSize(0, 0);
}
/** Attaches the root layer to the layer controller so that Gecko appears. */
void setLayerController(LayerController layerController) {
mLayerController = layerController;
layerController.setRoot(mRootLayer);
if (mGeckoViewport != null) {
layerController.setViewportMetrics(mGeckoViewport);
}
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
sendResizeEventIfNecessary(false);
LayerView view = layerController.getView();
view.setListener(this);
mLayerRenderer = new LayerRenderer(view);
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public boolean beginDrawing(int width, int height, String metadata) {
Log.e(LOGTAG, "### beginDrawing " + width + " " + height);
// If the viewport has changed but we still don't have the latest viewport
// from Gecko, ignore the viewport passed to us from Gecko since that is going
// to be wrong.
if (!mFirstPaint && mIgnorePaintsPendingViewportSizeChange) {
return false;
}
mFirstPaint = false;
// If we've changed surface types, cancel this draw
if (initializeVirtualLayer()) {
Log.e(LOGTAG, "### Cancelling draw due to virtual layer initialization");
return false;
}
try {
JSONObject viewportObject = new JSONObject(metadata);
mNewGeckoViewport = new ViewportMetrics(viewportObject);
Log.e(LOGTAG, "### beginDrawing new Gecko viewport " + mNewGeckoViewport);
// Update the background color, if it's present.
String backgroundColorString = viewportObject.optString("backgroundColor");
if (backgroundColorString != null && !backgroundColorString.equals(mLastCheckerboardColor)) {
mLastCheckerboardColor = backgroundColorString;
mLayerController.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
}
} catch (JSONException e) {
Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
return false;
}
if (mBufferSize.width != width || mBufferSize.height != height) {
mBufferSize = new IntSize(width, height);
}
return true;
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void endDrawing() {
updateViewport(!mUpdateViewportOnEndDraw);
mUpdateViewportOnEndDraw = false;
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
/* Used by robocop for testing purposes */
if (mDrawListener != null) {
mDrawListener.drawFinished();
}
}
private void updateViewport(boolean onlyUpdatePageSize) {
synchronized (mLayerController) {
// save and restore the viewport size stored in java; never let the
// JS-side viewport dimensions override the java-side ones because
// java is the One True Source of this information, and allowing JS
// to override can lead to race conditions where this data gets clobbered.
FloatSize viewportSize = mLayerController.getViewportSize();
mGeckoViewport = mNewGeckoViewport;
mGeckoViewport.setSize(viewportSize);
RectF position = mGeckoViewport.getViewport();
mRootLayer.setPositionAndResolution(RectUtils.round(position), mGeckoViewport.getZoomFactor());
// Set the new origin and resolution instantly.
mRootLayer.performUpdates(null);
Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize +
" getTileViewport " + mGeckoViewport);
if (onlyUpdatePageSize) {
// Don't adjust page size when zooming unless zoom levels are
// approximately equal.
if (FloatUtils.fuzzyEquals(mLayerController.getZoomFactor(),
mGeckoViewport.getZoomFactor()))
mLayerController.setPageSize(mGeckoViewport.getPageSize());
} else {
mLayerController.setViewportMetrics(mGeckoViewport);
mLayerController.abortPanZoomAnimation();
}
}
}
/* Informs Gecko that the screen size has changed. */
private void sendResizeEventIfNecessary(boolean force) {
Log.d(LOGTAG, "### sendResizeEventIfNecessary " + force);
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
IntSize newWindowSize = getBufferSize();
boolean screenSizeChanged = mScreenSize == null || !mScreenSize.equals(newScreenSize);
boolean windowSizeChanged = mWindowSize == null || !mWindowSize.equals(newWindowSize);
if (!force && !screenSizeChanged && !windowSizeChanged) {
return;
}
mScreenSize = newScreenSize;
mWindowSize = newWindowSize;
if (screenSizeChanged) {
Log.i(LOGTAG, "### Screen-size changed to " + mScreenSize);
}
if (windowSizeChanged) {
Log.i(LOGTAG, "### Window-size changed to " + mWindowSize);
}
GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height, // Window (buffer) size
mScreenSize.width, mScreenSize.height); // Screen size
GeckoAppShell.sendEventToGecko(event);
}
// Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
// cannot be parsed, returns white.
private static int parseColorFromGecko(String string) {
if (sColorPattern == null) {
sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
}
Matcher matcher = sColorPattern.matcher(string);
if (!matcher.matches()) {
return Color.WHITE;
}
int r = Integer.parseInt(matcher.group(1));
int g = Integer.parseInt(matcher.group(2));
int b = Integer.parseInt(matcher.group(3));
return Color.rgb(r, g, b);
}
private boolean initializeVirtualLayer() {
if (mRootLayer != null) {
return false;
}
Log.e(LOGTAG, "### Creating virtual layer");
VirtualLayer virtualLayer = new VirtualLayer(getBufferSize());
mLayerController.setRoot(virtualLayer);
mRootLayer = virtualLayer;
sendResizeEventIfNecessary(true);
return true;
}
private IntSize getBufferSize() {
View view = mLayerController.getView();
IntSize size = new IntSize(view.getWidth(), view.getHeight());
Log.e(LOGTAG, "### getBufferSize " + size);
return size;
}
public Bitmap getBitmap() {
return null;
}
private void adjustViewportWithThrottling() {
if (!mLayerController.getRedrawHint())
return;
if (mPendingViewportAdjust)
return;
long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
mLayerController.getView().postDelayed(
new Runnable() {
public void run() {
mPendingViewportAdjust = false;
adjustViewport();
}
}, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
mPendingViewportAdjust = true;
return;
}
adjustViewport();
}
void viewportSizeChanged() {
mViewportSizeChanged = true;
mIgnorePaintsPendingViewportSizeChange = true;
}
private void adjustViewport() {
ViewportMetrics viewportMetrics =
new ViewportMetrics(mLayerController.getViewportMetrics());
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics));
if (mViewportSizeChanged) {
mViewportSizeChanged = false;
GeckoAppShell.viewSizeChanged();
}
mLastViewportChangeTime = System.currentTimeMillis();
}
/** Implementation of GeckoEventListener. */
public void handleMessage(String event, JSONObject message) {
if ("Viewport:UpdateAndDraw".equals(event)) {
Log.e(LOGTAG, "### Java side Viewport:UpdateAndDraw()!");
mUpdateViewportOnEndDraw = true;
mIgnorePaintsPendingViewportSizeChange = false;
// Redraw everything.
Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
} else if ("Viewport:UpdateLater".equals(event)) {
Log.e(LOGTAG, "### Java side Viewport:UpdateLater()!");
mUpdateViewportOnEndDraw = true;
mIgnorePaintsPendingViewportSizeChange = false;
}
}
void geometryChanged() {
/* Let Gecko know if the screensize has changed */
sendResizeEventIfNecessary(false);
adjustViewportWithThrottling();
}
public int getWidth() {
return mBufferSize.width;
}
public int getHeight() {
return mBufferSize.height;
}
public ViewportMetrics getGeckoViewportMetrics() {
// Return a copy, as we modify this inside the Gecko thread
if (mGeckoViewport != null)
return new ViewportMetrics(mGeckoViewport);
return null;
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public ViewTransform getViewTransform() {
Log.e(LOGTAG, "### getViewTransform()");
// NB: We don't begin a transaction here because this can be called in a synchronous
// manner between beginDrawing() and endDrawing(), and that will cause a deadlock.
synchronized (mLayerController) {
ViewportMetrics viewportMetrics = mLayerController.getViewportMetrics();
PointF viewportOrigin = viewportMetrics.getOrigin();
float scrollX = viewportOrigin.x;
float scrollY = viewportOrigin.y;
float zoomFactor = viewportMetrics.getZoomFactor();
Log.e(LOGTAG, "### Viewport metrics = " + viewportMetrics + " tile reso = " +
mRootLayer.getResolution());
return new ViewTransform(scrollX, scrollY, zoomFactor);
}
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public LayerRenderer.Frame createFrame() {
// Create the shaders and textures if necessary.
if (!mLayerRendererInitialized) {
mLayerRenderer.createProgram();
mLayerRendererInitialized = true;
}
// Build the contexts and create the frame.
Layer.RenderContext pageContext = mLayerRenderer.createPageContext();
Layer.RenderContext screenContext = mLayerRenderer.createScreenContext();
return mLayerRenderer.createFrame(pageContext, screenContext);
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void activateProgram() {
mLayerRenderer.activateProgram();
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void deactivateProgram() {
mLayerRenderer.deactivateProgram();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void renderRequested() {
Log.e(LOGTAG, "### Render requested, scheduling composite");
GeckoAppShell.scheduleComposite();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void compositionPauseRequested() {
Log.e(LOGTAG, "### Scheduling PauseComposition");
GeckoAppShell.schedulePauseComposition();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void compositionResumeRequested() {
Log.e(LOGTAG, "### Scheduling ResumeComposition");
GeckoAppShell.scheduleResumeComposition();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void surfaceChanged(int width, int height) {
compositionPauseRequested();
mLayerController.setViewportSize(new FloatSize(width, height));
compositionResumeRequested();
renderRequested();
}
/** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;
}
/** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
public interface DrawListener {
public void drawFinished();
}
}