michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import org.mozilla.gecko.GeckoAccessibility; michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.GeckoEvent; michael@0: import org.mozilla.gecko.PrefsHelper; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.Tab; michael@0: import org.mozilla.gecko.Tabs; michael@0: import org.mozilla.gecko.TouchEventInterceptor; michael@0: import org.mozilla.gecko.ZoomConstraints; michael@0: import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; michael@0: import org.mozilla.gecko.mozglue.RobocopTarget; michael@0: import org.mozilla.gecko.EventDispatcher; michael@0: michael@0: import android.content.Context; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.BitmapFactory; michael@0: import android.graphics.Canvas; michael@0: import android.graphics.Color; michael@0: import android.graphics.Point; michael@0: import android.graphics.PointF; michael@0: import android.graphics.Rect; michael@0: import android.graphics.SurfaceTexture; michael@0: import android.os.Build; michael@0: import android.os.Handler; michael@0: import android.util.AttributeSet; michael@0: import android.util.DisplayMetrics; michael@0: import android.util.Log; michael@0: import android.view.KeyEvent; michael@0: import android.view.MotionEvent; michael@0: import android.view.SurfaceHolder; michael@0: import android.view.SurfaceView; michael@0: import android.view.TextureView; michael@0: import android.view.View; michael@0: import android.view.ViewGroup; michael@0: import android.view.inputmethod.EditorInfo; michael@0: import android.view.inputmethod.InputConnection; michael@0: import android.widget.FrameLayout; michael@0: michael@0: import java.nio.IntBuffer; michael@0: import java.util.ArrayList; michael@0: michael@0: /** michael@0: * A view rendered by the layer compositor. michael@0: * michael@0: * Note that LayerView is accessed by Robocop via reflection. michael@0: */ michael@0: public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener { michael@0: private static String LOGTAG = "GeckoLayerView"; michael@0: michael@0: private GeckoLayerClient mLayerClient; michael@0: private PanZoomController mPanZoomController; michael@0: private LayerMarginsAnimator mMarginsAnimator; michael@0: private GLController mGLController; michael@0: private InputConnectionHandler mInputConnectionHandler; michael@0: private LayerRenderer mRenderer; michael@0: /* Must be a PAINT_xxx constant */ michael@0: private int mPaintState; michael@0: private int mBackgroundColor; michael@0: private boolean mFullScreen; michael@0: michael@0: private SurfaceView mSurfaceView; michael@0: private TextureView mTextureView; michael@0: michael@0: private Listener mListener; michael@0: michael@0: /* This should only be modified on the Java UI thread. */ michael@0: private final ArrayList mTouchInterceptors; michael@0: private final Overscroll mOverscroll; michael@0: michael@0: /* Flags used to determine when to show the painted surface. */ michael@0: public static final int PAINT_START = 0; michael@0: public static final int PAINT_BEFORE_FIRST = 1; michael@0: public static final int PAINT_AFTER_FIRST = 2; michael@0: michael@0: public boolean shouldUseTextureView() { michael@0: // Disable TextureView support for now as it causes panning/zooming michael@0: // performance regressions (see bug 792259). Uncomment the code below michael@0: // once this bug is fixed. michael@0: return false; michael@0: michael@0: /* michael@0: // we can only use TextureView on ICS or higher michael@0: if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { michael@0: Log.i(LOGTAG, "Not using TextureView: not on ICS+"); michael@0: return false; michael@0: } michael@0: michael@0: try { michael@0: // and then we can only use it if we have a hardware accelerated window michael@0: Method m = View.class.getMethod("isHardwareAccelerated", (Class[]) null); michael@0: return (Boolean) m.invoke(this); michael@0: } catch (Exception e) { michael@0: Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString()); michael@0: return false; michael@0: } */ michael@0: } michael@0: michael@0: public LayerView(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: michael@0: mGLController = GLController.getInstance(this); michael@0: mPaintState = PAINT_START; michael@0: mBackgroundColor = Color.WHITE; michael@0: michael@0: mTouchInterceptors = new ArrayList(); michael@0: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { michael@0: mOverscroll = new OverscrollEdgeEffect(this); michael@0: } else { michael@0: mOverscroll = null; michael@0: } michael@0: Tabs.registerOnTabsChangedListener(this); michael@0: } michael@0: michael@0: public LayerView(Context context) { michael@0: this(context, null); michael@0: } michael@0: michael@0: public void initializeView(EventDispatcher eventDispatcher) { michael@0: mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher); michael@0: if (mOverscroll != null) { michael@0: mLayerClient.setOverscrollHandler(mOverscroll); michael@0: } michael@0: michael@0: mPanZoomController = mLayerClient.getPanZoomController(); michael@0: mMarginsAnimator = mLayerClient.getLayerMarginsAnimator(); michael@0: michael@0: mRenderer = new LayerRenderer(this); michael@0: mInputConnectionHandler = null; michael@0: michael@0: setFocusable(true); michael@0: setFocusableInTouchMode(true); michael@0: michael@0: GeckoAccessibility.setDelegate(this); michael@0: } michael@0: michael@0: private Point getEventRadius(MotionEvent event) { michael@0: if (Build.VERSION.SDK_INT >= 9) { michael@0: return new Point((int)event.getToolMajor()/2, michael@0: (int)event.getToolMinor()/2); michael@0: } michael@0: michael@0: float size = event.getSize(); michael@0: DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics(); michael@0: size = size * Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels); michael@0: return new Point((int)size, (int)size); michael@0: } michael@0: michael@0: public void geckoConnected() { michael@0: // See if we want to force 16-bit colour before doing anything michael@0: PrefsHelper.getPref("gfx.android.rgb16.force", new PrefsHelper.PrefHandlerBase() { michael@0: @Override public void prefValue(String pref, boolean force16bit) { michael@0: if (force16bit) { michael@0: GeckoAppShell.setScreenDepthOverride(16); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: mLayerClient.notifyGeckoReady(); michael@0: addTouchInterceptor(new TouchEventInterceptor() { michael@0: private PointF mInitialTouchPoint = null; michael@0: michael@0: @Override michael@0: public boolean onInterceptTouchEvent(View view, MotionEvent event) { michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onTouch(View view, MotionEvent event) { michael@0: if (event == null) { michael@0: return true; michael@0: } michael@0: michael@0: int action = event.getActionMasked(); michael@0: PointF point = new PointF(event.getX(), event.getY()); michael@0: if (action == MotionEvent.ACTION_DOWN) { michael@0: mInitialTouchPoint = point; michael@0: } michael@0: michael@0: if (mInitialTouchPoint != null && action == MotionEvent.ACTION_MOVE) { michael@0: Point p = getEventRadius(event); michael@0: michael@0: if (PointUtils.subtract(point, mInitialTouchPoint).length() < michael@0: Math.max(PanZoomController.CLICK_THRESHOLD, Math.min(Math.min(p.x, p.y), PanZoomController.PAN_THRESHOLD))) { michael@0: // Don't send the touchmove event if if the users finger hasn't moved far. michael@0: // Necessary for Google Maps to work correctly. See bug 771099. michael@0: return true; michael@0: } else { michael@0: mInitialTouchPoint = null; michael@0: } michael@0: } michael@0: michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event, false)); michael@0: return true; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public void showSurface() { michael@0: // Fix this if TextureView support is turned back on above michael@0: mSurfaceView.setVisibility(View.VISIBLE); michael@0: } michael@0: michael@0: public void hideSurface() { michael@0: // Fix this if TextureView support is turned back on above michael@0: mSurfaceView.setVisibility(View.INVISIBLE); michael@0: } michael@0: michael@0: public void destroy() { michael@0: if (mLayerClient != null) { michael@0: mLayerClient.destroy(); michael@0: } michael@0: if (mRenderer != null) { michael@0: mRenderer.destroy(); michael@0: } michael@0: Tabs.unregisterOnTabsChangedListener(this); michael@0: } michael@0: michael@0: public void addTouchInterceptor(final TouchEventInterceptor aTouchInterceptor) { michael@0: post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: mTouchInterceptors.add(aTouchInterceptor); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public void removeTouchInterceptor(final TouchEventInterceptor aTouchInterceptor) { michael@0: post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: mTouchInterceptors.remove(aTouchInterceptor); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: private boolean runTouchInterceptors(MotionEvent event, boolean aOnTouch) { michael@0: boolean result = false; michael@0: for (TouchEventInterceptor i : mTouchInterceptors) { michael@0: if (aOnTouch) { michael@0: result |= i.onTouch(this, event); michael@0: } else { michael@0: result |= i.onInterceptTouchEvent(this, event); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: @Override michael@0: public void dispatchDraw(final Canvas canvas) { michael@0: super.dispatchDraw(canvas); michael@0: michael@0: // We must have a layer client to get valid viewport metrics michael@0: if (mLayerClient != null && mOverscroll != null) { michael@0: mOverscroll.draw(canvas, getViewportMetrics()); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public boolean onTouchEvent(MotionEvent event) { michael@0: if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { michael@0: requestFocus(); michael@0: } michael@0: michael@0: if (runTouchInterceptors(event, false)) { michael@0: return true; michael@0: } michael@0: if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) { michael@0: return true; michael@0: } michael@0: if (runTouchInterceptors(event, true)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onHoverEvent(MotionEvent event) { michael@0: if (runTouchInterceptors(event, true)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onGenericMotionEvent(MotionEvent event) { michael@0: if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: protected void onAttachedToWindow() { michael@0: // This check should not be done before the view is attached to a window michael@0: // as hardware acceleration will not be enabled at that point. michael@0: // We must create and add the SurfaceView instance before the view tree michael@0: // is fully created to avoid flickering (see bug 801477). michael@0: if (shouldUseTextureView()) { michael@0: mTextureView = new TextureView(getContext()); michael@0: mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); michael@0: michael@0: // The background is set to this color when the LayerView is michael@0: // created, and it will be shown immediately at startup. Shortly michael@0: // after, the tab's background color will be used before any content michael@0: // is shown. michael@0: mTextureView.setBackgroundColor(Color.WHITE); michael@0: addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); michael@0: } else { michael@0: // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap) michael@0: // from a SurfaceView, which is just not possible (the bitmap will be transparent). michael@0: setWillNotCacheDrawing(false); michael@0: michael@0: mSurfaceView = new LayerSurfaceView(getContext(), this); michael@0: mSurfaceView.setBackgroundColor(Color.WHITE); michael@0: addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); michael@0: michael@0: SurfaceHolder holder = mSurfaceView.getHolder(); michael@0: holder.addCallback(new SurfaceListener()); michael@0: } michael@0: } michael@0: michael@0: // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object michael@0: GeckoLayerClient getLayerClient() { return mLayerClient; } michael@0: public Object getLayerClientObject() { return mLayerClient; } michael@0: michael@0: public PanZoomController getPanZoomController() { return mPanZoomController; } michael@0: public LayerMarginsAnimator getLayerMarginsAnimator() { return mMarginsAnimator; } michael@0: michael@0: public ImmutableViewportMetrics getViewportMetrics() { michael@0: return mLayerClient.getViewportMetrics(); michael@0: } michael@0: michael@0: public void abortPanning() { michael@0: if (mPanZoomController != null) { michael@0: mPanZoomController.abortPanning(); michael@0: } michael@0: } michael@0: michael@0: public PointF convertViewPointToLayerPoint(PointF viewPoint) { michael@0: return mLayerClient.convertViewPointToLayerPoint(viewPoint); michael@0: } michael@0: michael@0: int getBackgroundColor() { michael@0: return mBackgroundColor; michael@0: } michael@0: michael@0: @Override michael@0: public void setBackgroundColor(int newColor) { michael@0: mBackgroundColor = newColor; michael@0: requestRender(); michael@0: } michael@0: michael@0: public void setZoomConstraints(ZoomConstraints constraints) { michael@0: mLayerClient.setZoomConstraints(constraints); michael@0: } michael@0: michael@0: public void setIsRTL(boolean aIsRTL) { michael@0: mLayerClient.setIsRTL(aIsRTL); michael@0: } michael@0: michael@0: public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) { michael@0: mInputConnectionHandler = inputConnectionHandler; michael@0: mLayerClient.forceRedraw(null); michael@0: } michael@0: michael@0: @Override michael@0: public Handler getHandler() { michael@0: if (mInputConnectionHandler != null) michael@0: return mInputConnectionHandler.getHandler(super.getHandler()); michael@0: return super.getHandler(); michael@0: } michael@0: michael@0: @Override michael@0: public InputConnection onCreateInputConnection(EditorInfo outAttrs) { michael@0: if (mInputConnectionHandler != null) michael@0: return mInputConnectionHandler.onCreateInputConnection(outAttrs); michael@0: return null; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyPreIme(int keyCode, KeyEvent event) { michael@0: if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyPreIme(keyCode, event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyDown(int keyCode, KeyEvent event) { michael@0: if (mPanZoomController != null && mPanZoomController.onKeyEvent(event)) { michael@0: return true; michael@0: } michael@0: if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyDown(keyCode, event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyLongPress(int keyCode, KeyEvent event) { michael@0: if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyLongPress(keyCode, event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { michael@0: if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyUp(int keyCode, KeyEvent event) { michael@0: if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyUp(keyCode, event)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: public boolean isIMEEnabled() { michael@0: if (mInputConnectionHandler != null) { michael@0: return mInputConnectionHandler.isIMEEnabled(); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: public void requestRender() { michael@0: if (mListener != null) { michael@0: mListener.renderRequested(); michael@0: } michael@0: } michael@0: michael@0: public void addLayer(Layer layer) { michael@0: mRenderer.addLayer(layer); michael@0: } michael@0: michael@0: public void removeLayer(Layer layer) { michael@0: mRenderer.removeLayer(layer); michael@0: } michael@0: michael@0: public void postRenderTask(RenderTask task) { michael@0: mRenderer.postRenderTask(task); michael@0: } michael@0: michael@0: public void removeRenderTask(RenderTask task) { michael@0: mRenderer.removeRenderTask(task); michael@0: } michael@0: michael@0: public int getMaxTextureSize() { michael@0: return mRenderer.getMaxTextureSize(); michael@0: } michael@0: michael@0: /** Used by robocop for testing purposes. Not for production use! */ michael@0: @RobocopTarget michael@0: public IntBuffer getPixels() { michael@0: return mRenderer.getPixels(); michael@0: } michael@0: michael@0: /* paintState must be a PAINT_xxx constant. */ michael@0: public void setPaintState(int paintState) { michael@0: mPaintState = paintState; michael@0: } michael@0: michael@0: public int getPaintState() { michael@0: return mPaintState; michael@0: } michael@0: michael@0: public LayerRenderer getRenderer() { michael@0: return mRenderer; michael@0: } michael@0: michael@0: public void setListener(Listener listener) { michael@0: mListener = listener; michael@0: } michael@0: michael@0: Listener getListener() { michael@0: return mListener; michael@0: } michael@0: michael@0: public GLController getGLController() { michael@0: return mGLController; michael@0: } michael@0: michael@0: private Bitmap getDrawable(String name) { michael@0: BitmapFactory.Options options = new BitmapFactory.Options(); michael@0: options.inScaled = false; michael@0: Context context = getContext(); michael@0: int resId = context.getResources().getIdentifier(name, "drawable", context.getPackageName()); michael@0: return BitmapUtils.decodeResource(context, resId, options); michael@0: } michael@0: michael@0: Bitmap getScrollbarImage() { michael@0: return getDrawable("scrollbar"); michael@0: } michael@0: michael@0: /* When using a SurfaceView (mSurfaceView != null), resizing happens in two michael@0: * phases. First, the LayerView changes size, then, often some frames later, michael@0: * the SurfaceView changes size. Because of this, we need to split the michael@0: * resize into two phases to avoid jittering. michael@0: * michael@0: * The first phase is the LayerView size change. mListener is notified so michael@0: * that a synchronous draw can be performed (otherwise a blank frame will michael@0: * appear). michael@0: * michael@0: * The second phase is the SurfaceView size change. At this point, the michael@0: * backing GL surface is resized and another synchronous draw is performed. michael@0: * Gecko is also sent the new window size, and this will likely cause an michael@0: * extra draw a few frames later, after it's re-rendered and caught up. michael@0: * michael@0: * In the case that there is no valid GL surface (for example, when michael@0: * resuming, or when coming back from the awesomescreen), or we're using a michael@0: * TextureView instead of a SurfaceView, the first phase is skipped. michael@0: */ michael@0: private void onSizeChanged(int width, int height) { michael@0: if (!mGLController.isCompositorCreated()) { michael@0: return; michael@0: } michael@0: michael@0: surfaceChanged(width, height); michael@0: michael@0: if (mSurfaceView == null) { michael@0: return; michael@0: } michael@0: michael@0: if (mListener != null) { michael@0: mListener.sizeChanged(width, height); michael@0: } michael@0: michael@0: if (mOverscroll != null) { michael@0: mOverscroll.setSize(width, height); michael@0: } michael@0: } michael@0: michael@0: private void surfaceChanged(int width, int height) { michael@0: mGLController.serverSurfaceChanged(width, height); michael@0: michael@0: if (mListener != null) { michael@0: mListener.surfaceChanged(width, height); michael@0: } michael@0: michael@0: if (mOverscroll != null) { michael@0: mOverscroll.setSize(width, height); michael@0: } michael@0: } michael@0: michael@0: private void onDestroyed() { michael@0: mGLController.serverSurfaceDestroyed(); michael@0: } michael@0: michael@0: public Object getNativeWindow() { michael@0: if (mSurfaceView != null) michael@0: return mSurfaceView.getHolder(); michael@0: michael@0: return mTextureView.getSurfaceTexture(); michael@0: } michael@0: michael@0: @WrapElementForJNI(allowMultithread = true, stubName = "RegisterCompositorWrapper") michael@0: public static GLController registerCxxCompositor() { michael@0: try { michael@0: LayerView layerView = GeckoAppShell.getLayerView(); michael@0: GLController controller = layerView.getGLController(); michael@0: controller.compositorCreated(); michael@0: return controller; michael@0: } catch (Exception e) { michael@0: Log.e(LOGTAG, "Error registering compositor!", e); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: public interface Listener { michael@0: void renderRequested(); michael@0: void sizeChanged(int width, int height); michael@0: void surfaceChanged(int width, int height); michael@0: } michael@0: michael@0: private class SurfaceListener implements SurfaceHolder.Callback { michael@0: @Override michael@0: public void surfaceChanged(SurfaceHolder holder, int format, int width, michael@0: int height) { michael@0: onSizeChanged(width, height); michael@0: } michael@0: michael@0: @Override michael@0: public void surfaceCreated(SurfaceHolder holder) { michael@0: } michael@0: michael@0: @Override michael@0: public void surfaceDestroyed(SurfaceHolder holder) { michael@0: onDestroyed(); michael@0: } michael@0: } michael@0: michael@0: /* A subclass of SurfaceView to listen to layout changes, as michael@0: * View.OnLayoutChangeListener requires API level 11. michael@0: */ michael@0: private class LayerSurfaceView extends SurfaceView { michael@0: LayerView mParent; michael@0: michael@0: public LayerSurfaceView(Context aContext, LayerView aParent) { michael@0: super(aContext); michael@0: mParent = aParent; michael@0: } michael@0: michael@0: @Override michael@0: protected void onLayout(boolean changed, int left, int top, int right, int bottom) { michael@0: if (changed) { michael@0: mParent.surfaceChanged(right - left, bottom - top); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { michael@0: @Override michael@0: public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { michael@0: // We don't do this for surfaceCreated above because it is always followed by a surfaceChanged, michael@0: // but that is not the case here. michael@0: onSizeChanged(width, height); michael@0: } michael@0: michael@0: @Override michael@0: public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { michael@0: onDestroyed(); michael@0: return true; // allow Android to call release() on the SurfaceTexture, we are done drawing to it michael@0: } michael@0: michael@0: @Override michael@0: public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { michael@0: onSizeChanged(width, height); michael@0: } michael@0: michael@0: @Override michael@0: public void onSurfaceTextureUpdated(SurfaceTexture surface) { michael@0: michael@0: } michael@0: } michael@0: michael@0: @RobocopTarget michael@0: public void addDrawListener(DrawListener listener) { michael@0: mLayerClient.addDrawListener(listener); michael@0: } michael@0: michael@0: @RobocopTarget michael@0: public void removeDrawListener(DrawListener listener) { michael@0: mLayerClient.removeDrawListener(listener); michael@0: } michael@0: michael@0: @RobocopTarget michael@0: public static interface DrawListener { michael@0: public void drawFinished(); michael@0: } michael@0: michael@0: @Override michael@0: public void setOverScrollMode(int overscrollMode) { michael@0: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { michael@0: super.setOverScrollMode(overscrollMode); michael@0: } michael@0: if (mPanZoomController != null) { michael@0: mPanZoomController.setOverScrollMode(overscrollMode); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public int getOverScrollMode() { michael@0: if (mPanZoomController != null) { michael@0: return mPanZoomController.getOverScrollMode(); michael@0: } michael@0: michael@0: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { michael@0: return super.getOverScrollMode(); michael@0: } michael@0: return View.OVER_SCROLL_ALWAYS; michael@0: } michael@0: michael@0: @Override michael@0: public void onFocusChanged (boolean gainFocus, int direction, Rect previouslyFocusedRect) { michael@0: super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); michael@0: GeckoAccessibility.onLayerViewFocusChanged(this, gainFocus); michael@0: } michael@0: michael@0: public void setFullScreen(boolean fullScreen) { michael@0: mFullScreen = fullScreen; michael@0: } michael@0: michael@0: public boolean isFullScreen() { michael@0: return mFullScreen; michael@0: } michael@0: michael@0: @Override michael@0: public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { michael@0: if (msg == Tabs.TabEvents.VIEWPORT_CHANGE && Tabs.getInstance().isSelectedTab(tab) && mLayerClient != null) { michael@0: setZoomConstraints(tab.getZoomConstraints()); michael@0: setIsRTL(tab.getIsRTL()); michael@0: } michael@0: } michael@0: michael@0: // Public hooks for listening to metrics changing michael@0: michael@0: public interface OnMetricsChangedListener { michael@0: public void onMetricsChanged(ImmutableViewportMetrics viewport); michael@0: public void onPanZoomStopped(); michael@0: } michael@0: michael@0: public void setOnMetricsChangedListener(OnMetricsChangedListener listener) { michael@0: mLayerClient.setOnMetricsChangedListener(listener); michael@0: } michael@0: }