/* -*- 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 * Chris Lord * * 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.gfx.CairoImage; import org.mozilla.gecko.gfx.IntSize; import org.mozilla.gecko.gfx.LayerClient; import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.LayerRenderer; import org.mozilla.gecko.gfx.PointUtils; import org.mozilla.gecko.gfx.SingleTileLayer; import org.mozilla.gecko.FloatUtils; import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; import android.content.Context; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.util.DisplayMetrics; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; import java.nio.ByteBuffer; /** * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our * compositor. * * TODO: Throttle down Gecko's priority when we pan and zoom. */ public class GeckoSoftwareLayerClient extends LayerClient { private static final String LOGTAG = "GeckoSoftwareLayerClient"; private Context mContext; private int mWidth, mHeight, mFormat; private IntSize mScreenSize, mViewportSize; private ByteBuffer mBuffer; private final SingleTileLayer mTileLayer; /* The viewport rect that Gecko is currently displaying. */ private ViewportMetrics mGeckoViewport; private boolean mPendingViewportReply; private boolean mPendingViewportSet; /* Gecko has loaded new content, let its viewport metrics completely * override the LayerController on the next draw. */ private boolean mNewContent; private CairoImage mCairoImage; private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L; private long mLastViewportChangeTime; private boolean mPendingViewportAdjust; public GeckoSoftwareLayerClient(Context context) { mContext = context; mWidth = LayerController.TILE_WIDTH; mHeight = LayerController.TILE_HEIGHT; mFormat = CairoImage.FORMAT_RGB16_565; mScreenSize = new IntSize(1, 1); mNewContent = true; mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2); mCairoImage = new CairoImage() { @Override public ByteBuffer getBuffer() { return mBuffer; } @Override public int getWidth() { return mWidth; } @Override public int getHeight() { return mHeight; } @Override public int getFormat() { return mFormat; } }; mTileLayer = new SingleTileLayer(mCairoImage); } /** Attaches the root layer to the layer controller so that Gecko appears. */ @Override public void setLayerController(LayerController layerController) { super.setLayerController(layerController); layerController.setRoot(mTileLayer); if (mGeckoViewport != null) layerController.setViewportMetrics(mGeckoViewport); geometryChanged(); } public void beginDrawing() { mTileLayer.beginTransaction(); } /* * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require * a little more JNI magic. */ public void endDrawing(int x, int y, int width, int height, String metadata) { try { try { JSONObject metadataObject = new JSONObject(metadata); mGeckoViewport = new ViewportMetrics(metadataObject); mTileLayer.setOrigin(PointUtils.round(mGeckoViewport.getDisplayportOrigin())); mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); // Make sure LayerController metrics changes only happen in the // UI thread. final LayerController controller = getLayerController(); if (controller != null) { controller.post(new Runnable() { @Override public void run() { if (mNewContent) { mNewContent = false; controller.setViewportMetrics(mGeckoViewport); } else { // Don't adjust page size when zooming unless zoom levels are // approximately equal. if (FloatUtils.fuzzyEquals(controller.getZoomFactor(), mGeckoViewport.getZoomFactor())) controller.setPageSize(mGeckoViewport.getPageSize()); } Log.i(LOGTAG, "Viewport adjusted"); mPendingViewportReply = false; if (mPendingViewportSet) { mPendingViewportSet = false; adjustViewportWithThrottling(); } } }); } } catch (JSONException e) { throw new RuntimeException(e); } Rect rect = new Rect(x, y, x + width, y + height); mTileLayer.invalidate(rect); } finally { mTileLayer.endTransaction(); } } public ViewportMetrics getGeckoViewportMetrics() { // Return a copy, as we modify this inside the Gecko thread if (mGeckoViewport != null) return new ViewportMetrics(mGeckoViewport); return null; } public void geckoLoadedNewContent() { mNewContent = true; } /** Returns the back buffer. This function is for Gecko to use. */ public ByteBuffer lockBuffer() { return mBuffer; } /** * Gecko calls this function to signal that it is done with the back buffer. After this call, * it is forbidden for Gecko to touch the buffer. */ public void unlockBuffer() { /* no-op */ } @Override public void geometryChanged() { /* Let Gecko know if the screensize has changed */ DisplayMetrics metrics = new DisplayMetrics(); GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); if (metrics.widthPixels != mScreenSize.width || metrics.heightPixels != mScreenSize.height) { mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); Log.i(LOGTAG, "Screen-size changed to " + mScreenSize); GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED, LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT, metrics.widthPixels, metrics.heightPixels); GeckoAppShell.sendEventToGecko(event); } render(); } @Override public void render() { adjustViewportWithThrottling(); } private void adjustViewportWithThrottling() { if (!getLayerController().getRedrawHint()) return; if (mPendingViewportSet || mPendingViewportAdjust) return; long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime; if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) { getLayerController().getView().postDelayed( new Runnable() { @Override public void run() { mPendingViewportAdjust = false; adjustViewportWithThrottling(); } }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta); mPendingViewportAdjust = true; return; } adjustViewport(); } private void adjustViewport() { if (mPendingViewportReply) { mPendingViewportSet = true; return; } Log.i(LOGTAG, "Adjusting viewport"); mPendingViewportReply = true; ViewportMetrics viewportMetrics = new ViewportMetrics(getLayerController().getViewportMetrics()); PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(); viewportMetrics.setViewportOffset(viewportOffset); viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); GeckoEvent event = new GeckoEvent("Viewport:Change", viewportMetrics.toJSON()); GeckoAppShell.sendEventToGecko(event); mLastViewportChangeTime = System.currentTimeMillis(); } }