package org.mozilla.gecko.gfx; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.Configuration; import android.graphics.RectF; import android.graphics.Region; import android.util.Log; import org.libreoffice.LOKitShell; import org.libreoffice.TileIdentifier; import org.mozilla.gecko.util.FloatUtils; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public abstract class ComposedTileLayer extends Layer implements ComponentCallbacks2 { private static final String LOGTAG = ComposedTileLayer.class.getSimpleName(); protected final List tiles = new ArrayList(); protected final IntSize tileSize; private final ReadWriteLock tilesReadWriteLock = new ReentrantReadWriteLock(); private final Lock tilesReadLock = tilesReadWriteLock.readLock(); private final Lock tilesWriteLock = tilesReadWriteLock.writeLock(); protected RectF currentViewport = new RectF(); protected float currentZoom = 1.0f; protected RectF currentPageRect = new RectF(); private long reevaluationNanoTime = 0; public ComposedTileLayer(Context context) { context.registerComponentCallbacks(this); this.tileSize = new IntSize(256, 256); } protected static RectF roundToTileSize(RectF input, IntSize tileSize) { float minX = ((int) (input.left / tileSize.width)) * tileSize.width; float minY = ((int) (input.top / tileSize.height)) * tileSize.height; float maxX = ((int) (input.right / tileSize.width) + 1) * tileSize.width; float maxY = ((int) (input.bottom / tileSize.height) + 1) * tileSize.height; return new RectF(minX, minY, maxX, maxY); } protected static RectF inflate(RectF rect, IntSize inflateSize) { RectF newRect = new RectF(rect); newRect.left -= inflateSize.width; newRect.left = newRect.left < 0.0f ? 0.0f : newRect.left; newRect.top -= inflateSize.height; newRect.top = newRect.top < 0.0f ? 0.0f : newRect.top; newRect.right += inflateSize.width; newRect.bottom += inflateSize.height; return newRect; } protected static RectF normalizeRect(RectF rect, float sourceFactor, float targetFactor) { return new RectF( (rect.left / sourceFactor) * targetFactor, (rect.top / sourceFactor) * targetFactor, (rect.right / sourceFactor) * targetFactor, (rect.bottom / sourceFactor) * targetFactor); } public void invalidate() { tilesReadLock.lock(); for (SubTile tile : tiles) { tile.invalidate(); } tilesReadLock.unlock(); } @Override public void beginTransaction() { super.beginTransaction(); tilesReadLock.lock(); for (SubTile tile : tiles) { tile.beginTransaction(); } tilesReadLock.unlock(); } @Override public void endTransaction() { tilesReadLock.lock(); for (SubTile tile : tiles) { tile.endTransaction(); } tilesReadLock.unlock(); super.endTransaction(); } @Override public void draw(RenderContext context) { tilesReadLock.lock(); for (SubTile tile : tiles) { if (RectF.intersects(tile.getBounds(context), context.viewport)) { tile.draw(context); } } tilesReadLock.unlock(); } @Override protected void performUpdates(RenderContext context) { super.performUpdates(context); tilesReadLock.lock(); for (SubTile tile : tiles) { tile.beginTransaction(); tile.refreshTileMetrics(); tile.endTransaction(); tile.performUpdates(context); } tilesReadLock.unlock(); } @Override public Region getValidRegion(RenderContext context) { Region validRegion = new Region(); tilesReadLock.lock(); for (SubTile tile : tiles) { validRegion.op(tile.getValidRegion(context), Region.Op.UNION); } tilesReadLock.unlock(); return validRegion; } @Override public void setResolution(float newResolution) { super.setResolution(newResolution); tilesReadLock.lock(); for (SubTile tile : tiles) { tile.setResolution(newResolution); } tilesReadLock.unlock(); } public void reevaluateTiles(ImmutableViewportMetrics viewportMetrics, DisplayPortMetrics mDisplayPort) { RectF newViewPort = getViewPort(viewportMetrics); float newZoom = getZoom(viewportMetrics); // When if (newZoom <= 0.0 || Float.isNaN(newZoom)) { return; } if (currentViewport.equals(newViewPort) && FloatUtils.fuzzyEquals(currentZoom, newZoom)) { return; } long currentReevaluationNanoTime = System.nanoTime(); if ((currentReevaluationNanoTime - reevaluationNanoTime) < 25 * 1000000) { return; } reevaluationNanoTime = currentReevaluationNanoTime; currentViewport = newViewPort; currentZoom = newZoom; currentPageRect = viewportMetrics.getPageRect(); LOKitShell.sendTileReevaluationRequest(this); } protected abstract RectF getViewPort(ImmutableViewportMetrics viewportMetrics); protected abstract float getZoom(ImmutableViewportMetrics viewportMetrics); protected abstract int getTilePriority(); private boolean containsTilesMatching(float x, float y, float currentZoom) { tilesReadLock.lock(); try { for (SubTile tile : tiles) { if (tile.id.x == x && tile.id.y == y && tile.id.zoom == currentZoom) { return true; } } return false; } finally { tilesReadLock.unlock(); } } public void addNewTiles(List newTiles) { for (float y = currentViewport.top; y < currentViewport.bottom; y += tileSize.height) { if (y > currentPageRect.height()) { continue; } for (float x = currentViewport.left; x < currentViewport.right; x += tileSize.width) { if (x > currentPageRect.width()) { continue; } if (!containsTilesMatching(x, y, currentZoom)) { TileIdentifier tileId = new TileIdentifier((int) x, (int) y, currentZoom, tileSize); SubTile tile = createNewTile(tileId); newTiles.add(tile); } } } } public void clearMarkedTiles() { tilesWriteLock.lock(); Iterator iterator = tiles.iterator(); while (iterator.hasNext()) { SubTile tile = iterator.next(); if (tile.markedForRemoval) { tile.destroy(); iterator.remove(); } } tilesWriteLock.unlock(); } public void markTiles() { tilesReadLock.lock(); for (SubTile tile : tiles) { if (FloatUtils.fuzzyEquals(tile.id.zoom, currentZoom)) { RectF tileRect = tile.id.getRectF(); if (!RectF.intersects(currentViewport, tileRect)) { tile.markForRemoval(); } } else { tile.markForRemoval(); } } tilesReadLock.unlock(); } public void clearAndReset() { tilesWriteLock.lock(); tiles.clear(); tilesWriteLock.unlock(); currentViewport = new RectF(); } private SubTile createNewTile(TileIdentifier tileId) { SubTile tile = new SubTile(tileId); tile.beginTransaction(); tilesWriteLock.lock(); tiles.add(tile); tilesWriteLock.unlock(); return tile; } public boolean isStillValid(TileIdentifier tileId) { return RectF.intersects(currentViewport, tileId.getRectF()) || currentViewport.contains(tileId.getRectF()); } /** * Invalidate tiles which intersect the input rect */ public void invalidateTiles(List tilesToInvalidate, RectF cssRect) { RectF zoomedRect = RectUtils.scale(cssRect, currentZoom); tilesReadLock.lock(); for (SubTile tile : tiles) { if (!tile.markedForRemoval && RectF.intersects(zoomedRect, tile.id.getRectF())) { tilesToInvalidate.add(tile); } } tilesReadLock.unlock(); } @Override public void onConfigurationChanged(Configuration newConfig) { } @Override public void onLowMemory() { Log.i(LOGTAG, "onLowMemory"); } @Override public void onTrimMemory(int level) { if (level >= 15 /*TRIM_MEMORY_RUNNING_CRITICAL*/) { Log.i(LOGTAG, "Trimming memory - TRIM_MEMORY_RUNNING_CRITICAL"); } else if (level >= 10 /*TRIM_MEMORY_RUNNING_LOW*/) { Log.i(LOGTAG, "Trimming memory - TRIM_MEMORY_RUNNING_LOW"); } } }