private void computeCoverAndKeepRect(final Rect visibleRect, Rect coverRect, Rect keepRect) {
    visibleRect.copyTo(coverRect);
    visibleRect.copyTo(keepRect);

    // If we cover more that the actual viewport we can be smart about which tiles we choose to
    // render.
    if (mCoverAreaMultiplier > 1) {
      // The initial cover area covers equally in each direction, according to the
      // coverAreaMultiplier.
      coverRect.inflate(
          (int) (visibleRect.getWidth() * (mCoverAreaMultiplier - 1) / 2),
          (int) (visibleRect.getHeight() * (mCoverAreaMultiplier - 1) / 2));
      coverRect.copyTo(keepRect);

      if (mPendingTrajectoryVector.getX() != 0 || mPendingTrajectoryVector.getY() != 0) {
        // A null trajectory vector (no motion) means that tiles for the coverArea will be created.
        // A non-null trajectory vector will shrink the covered rect to visibleRect plus its
        // expansion from its
        // center toward the cover area edges in the direction of the given vector.

        // E.g. if visibleRect == (10,10)5x5 and coverAreaMultiplier == 3.0:
        // a (0,0) trajectory vector will create tiles intersecting (5,5)15x15,
        // a (1,0) trajectory vector will create tiles intersecting (10,10)10x5,
        // and a (1,1) trajectory vector will create tiles intersecting (10,10)10x10.

        // Multiply the vector by the distance to the edge of the cover area.
        float trajectoryVectorMultiplier = (mCoverAreaMultiplier - 1) / 2;

        // Unite the visible rect with a "ghost" of the visible rect moved in the direction of the
        // trajectory vector.
        visibleRect.copyTo(coverRect);
        coverRect.offset(
            (int) (coverRect.getWidth() * mTrajectoryVector.getX() * trajectoryVectorMultiplier),
            (int) (coverRect.getHeight() * mTrajectoryVector.getY() * trajectoryVectorMultiplier));

        coverRect.composite(visibleRect);
      }

      assert (keepRect.contains(coverRect));
    }

    adjustForContentsRect(coverRect);

    // The keep rect is an inflated version of the cover rect, inflated in tile dimensions.
    keepRect.composite(coverRect);
    keepRect.inflate(mTileSize.getWidth() / 2, mTileSize.getHeight() / 2);
    keepRect.intersect(mRect);

    assert (coverRect.isEmpty() || keepRect.contains(coverRect));
  }
  public void coverWithTilesIfNeeded(BSTimerTask task) {
    Rect visibleRect = visibleRect();
    Rect rect = mapFromContents(mClient.tbsGetContentsRect());

    boolean didChange =
        !mTrajectoryVector.equals(mPendingTrajectoryVector)
            || !mVisibleRect.equals(visibleRect)
            || !mRect.equals(rect);
    if (didChange || mPendingTileCreation) createTiles(task);
  }
  public void setTrajectoryVector(final PointF trajectoryVector) {
    if (trajectoryVector == null) return;

    trajectoryVector.copyTo(mPendingTrajectoryVector);
    mPendingTrajectoryVector.normalize();
  }
  private void createTiles(BSTimerTask task) {
    // Guard here as as these can change before the timer fires.
    if (isBackingStoreUpdatesSuspended()) return;

    // Update our backing store geometry.
    final Rect previousRect = mRect.clone();
    mapFromContents(mClient.tbsGetContentsRect()).copyTo(mRect);
    mPendingTrajectoryVector.copyTo(mTrajectoryVector);
    visibleRect().copyTo(mVisibleRect);

    if (mRect.isEmpty()) {
      setCoverRect(new Rect());
      setKeepRect(new Rect());
      return;
    }

    /* We must compute cover and keep rects using the visibleRect, instead of the rect intersecting the visibleRect with m_rect,
     * because TBS can be used as a backing store of GraphicsLayer and the visible rect usually does not intersect with m_rect.
     * In the below case, the intersecting rect is an empty.
     *
     *  +---------------+
     *  |               |
     *  |   m_rect      |
     *  |       +-------|-----------------------+
     *  |       | HERE  |  cover or keep        |
     *  +---------------+      rect             |
     *          |         +---------+           |
     *          |         | visible |           |
     *          |         |  rect   |           |
     *          |         +---------+           |
     *          |                               |
     *          |                               |
     *          +-------------------------------+
     *
     * We must create or keep the tiles in the HERE region.
     */

    Rect coverRect = new Rect();
    Rect keepRect = new Rect();
    computeCoverAndKeepRect(mVisibleRect, coverRect, keepRect);

    setCoverRect(coverRect);
    setKeepRect(keepRect);

    if (coverRect.isEmpty()) return;

    // Resize tiles at the edge in case the contents size has changed, but only do so
    // after having dropped tiles outside the keep rect.
    boolean didResizeTiles = false;
    if (!previousRect.equals(mRect)) didResizeTiles = resizeEdgeTiles();

    // Search for the tile position closest to the viewport center that does not yet contain a tile.
    // Which position is considered the closest depends on the tileDistance function.
    double shortestDistance = Double.POSITIVE_INFINITY;
    List<Coordinate> tilesToCreate = new ArrayList<Coordinate>();
    int requiredTileCount = 0;

    // Cover areas (in tiles) with minimum distance from the visible rect. If the visible rect is
    // not covered already it will be covered first in one go, due to the distance being 0 for tiles
    // inside the visible rect.
    Coordinate topLeft = tileCoordinateForPoint(coverRect.getLeft(), coverRect.getTop());
    Coordinate bottomRight = tileCoordinateForPoint(coverRect.getRight(), coverRect.getBottom());
    for (int yCoordinate = topLeft.getY(); yCoordinate <= bottomRight.getY(); ++yCoordinate) {
      for (int xCoordinate = topLeft.getX(); xCoordinate <= bottomRight.getX(); ++xCoordinate) {
        if (getTileAt(xCoordinate, yCoordinate) != null) continue;
        ++requiredTileCount;
        double distance = tileDistance(mVisibleRect, xCoordinate, yCoordinate);
        if (distance > shortestDistance) continue;
        if (distance < shortestDistance) {
          tilesToCreate.clear();
          shortestDistance = distance;
        }
        tilesToCreate.add(new Coordinate(xCoordinate, yCoordinate));
      }
    }

    // Now construct the tile(s) within the shortest distance.
    int tilesToCreateCount = tilesToCreate.size();
    for (int n = 0; n < tilesToCreateCount; ++n) {
      Coordinate coordinate = tilesToCreate.get(n);
      setTile(coordinate, mBackend.createTile(coordinate));
    }
    requiredTileCount -= tilesToCreateCount;

    // Paint the content of the newly created tiles or resized tiles.
    if (tilesToCreateCount != 0 || didResizeTiles) updateTileBuffers(task);

    // Re-call createTiles on a timer to cover the visible area with the newest shortest distance.
    mPendingTileCreation = requiredTileCount != 0;
    if (mPendingTileCreation) {
      if (!mCommitTileUpdatesOnIdleEventLoop) {
        mClient.tbsHasPendingTileCreation();
        return;
      }

      Log.d(
          "tt",
          "start BSUpdate scheduleTask in createTiles func scale "
              + mContentsScale
              + " pending scale "
              + mPendingScale);
      if (task == null || !task.cancelled()) startBSUpdateTask(TILE_CREATION_DELAY_MS);
    }
  }