/** 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);
    }

    GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
    GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
    GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);

    sendResizeEventIfNecessary();
  }
  public void handleMessage(String event, JSONObject message) {
    if ("Viewport:UpdateAndDraw".equals(event)) {
      mUpdateViewportOnEndDraw = true;

      // Redraw everything.
      Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
      GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
    } else if ("Viewport:UpdateLater".equals(event)) {
      mUpdateViewportOnEndDraw = true;
    } else if ("Checkerboard:Toggle".equals(event)) {
      try {
        boolean showChecks = message.getBoolean("value");
        LayerController controller = getLayerController();
        controller.setCheckerboardShowChecks(showChecks);
        Log.i(LOGTAG, "Showing checks: " + showChecks);
      } catch (JSONException ex) {
        Log.e(LOGTAG, "Error decoding JSON", ex);
      }
    }
  }
  private void updateViewport(final boolean onlyUpdatePageSize) {
    // 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 = getLayerController().getViewportSize();
    mGeckoViewport = mNewGeckoViewport;
    mGeckoViewport.setSize(viewportSize);

    LayerController controller = getLayerController();
    PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
    mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
    mTileLayer.setResolution(mGeckoViewport.getZoomFactor());

    if (onlyUpdatePageSize) {
      // Don't adjust page size when zooming unless zoom levels are
      // approximately equal.
      if (FloatUtils.fuzzyEquals(controller.getZoomFactor(), mGeckoViewport.getZoomFactor()))
        controller.setPageSize(mGeckoViewport.getPageSize());
    } else {
      controller.setViewportMetrics(mGeckoViewport);
      controller.abortPanZoomAnimation();
    }
  }
  public Rect beginDrawing(
      int width,
      int height,
      int tileWidth,
      int tileHeight,
      String metadata,
      boolean hasDirectTexture) {
    setHasDirectTexture(hasDirectTexture);

    // Make sure the tile-size matches. If it doesn't, we could crash trying
    // to access invalid memory.
    if (mHasDirectTexture) {
      if (tileWidth != 0 || tileHeight != 0) {
        Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
        return null;
      }
    } else {
      if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
        Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
        return null;
      }
    }

    LayerController controller = getLayerController();

    try {
      JSONObject viewportObject = new JSONObject(metadata);
      mNewGeckoViewport = new ViewportMetrics(viewportObject);

      // Update the background color, if it's present.
      String backgroundColorString = viewportObject.optString("backgroundColor");
      if (backgroundColorString != null) {
        controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
      }
    } catch (JSONException e) {
      Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
      return null;
    }

    // Make sure we don't spend time painting areas we aren't interested in.
    // Only do this if the Gecko viewport isn't going to override our viewport.
    Rect bufferRect = new Rect(0, 0, width, height);

    if (!mUpdateViewportOnEndDraw) {
      // First, find out our ideal displayport. We do this by taking the
      // clamped viewport origin and taking away the optimum viewport offset.
      // This would be what we would send to Gecko if adjustViewport were
      // called now.
      ViewportMetrics currentMetrics = controller.getViewportMetrics();
      PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
      PointF viewportOffset = currentMetrics.getOptimumViewportOffset(new IntSize(width, height));
      currentBestOrigin.offset(-viewportOffset.x, -viewportOffset.y);

      Rect currentRect =
          RectUtils.round(
              new RectF(
                  currentBestOrigin.x,
                  currentBestOrigin.y,
                  currentBestOrigin.x + width,
                  currentBestOrigin.y + height));

      // Second, store Gecko's displayport.
      PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
      bufferRect =
          RectUtils.round(
              new RectF(
                  currentOrigin.x,
                  currentOrigin.y,
                  currentOrigin.x + width,
                  currentOrigin.y + height));

      // Take the intersection of the two as the area we're interested in rendering.
      if (!bufferRect.intersect(currentRect)) {
        // If there's no intersection, we have no need to render anything,
        // but make sure to update the viewport size.
        beginTransaction(mTileLayer);
        try {
          updateViewport(true);
        } finally {
          endTransaction(mTileLayer);
        }
        return null;
      }
      bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
    }

    beginTransaction(mTileLayer);

    // Synchronise the buffer size with Gecko.
    if (mBufferSize.width != width || mBufferSize.height != height) {
      mBufferSize = new IntSize(width, height);

      // Reallocate the buffer if necessary
      if (mTileLayer instanceof MultiTileLayer) {
        int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
        int size = mBufferSize.getArea() * bpp;
        if (mBuffer == null || mBuffer.capacity() != size) {
          // Free the old buffer
          if (mBuffer != null) {
            GeckoAppShell.freeDirectBuffer(mBuffer);
            mBuffer = null;
          }

          mBuffer = GeckoAppShell.allocateDirectBuffer(size);
        }
      }
    }

    return bufferRect;
  }