blob: ea6281df21adb6c59ca7cefc02482c4eea69322f [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.printspooler.model;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
import android.print.PrintDocumentInfo;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.printspooler.renderer.IPdfRenderer;
import com.android.printspooler.renderer.PdfManipulationService;
import com.android.printspooler.util.BitmapSerializeUtils;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public final class PageContentRepository {
private static final String LOG_TAG = "PageContentRepository";
private static final boolean DEBUG = false;
private static final int INVALID_PAGE_INDEX = -1;
private static final int STATE_CLOSED = 0;
private static final int STATE_OPENED = 1;
private static final int STATE_DESTROYED = 2;
private static final int BYTES_PER_PIXEL = 4;
private static final int BYTES_PER_MEGABYTE = 1048576;
private final CloseGuard mCloseGuard = CloseGuard.get();
private final AsyncRenderer mRenderer;
private RenderSpec mLastRenderSpec;
private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
private int mState;
public interface OnPageContentAvailableCallback {
public void onPageContentAvailable(BitmapDrawable content);
}
public PageContentRepository(Context context) {
mRenderer = new AsyncRenderer(context);
mState = STATE_CLOSED;
if (DEBUG) {
Log.i(LOG_TAG, "STATE_CLOSED");
}
mCloseGuard.open("destroy");
}
public void open(ParcelFileDescriptor source, final OpenDocumentCallback callback) {
throwIfNotClosed();
mState = STATE_OPENED;
if (DEBUG) {
Log.i(LOG_TAG, "STATE_OPENED");
}
mRenderer.open(source, callback);
}
public void close(Runnable callback) {
throwIfNotOpened();
mState = STATE_CLOSED;
if (DEBUG) {
Log.i(LOG_TAG, "STATE_CLOSED");
}
mRenderer.close(callback);
}
public void destroy(final Runnable callback) {
if (mState == STATE_OPENED) {
close(new Runnable() {
@Override
public void run() {
destroy(callback);
}
});
return;
}
mState = STATE_DESTROYED;
if (DEBUG) {
Log.i(LOG_TAG, "STATE_DESTROYED");
}
mRenderer.destroy();
if (callback != null) {
callback.run();
}
}
public void startPreload(int firstShownPage, int lastShownPage) {
// If we do not have a render spec we have no clue what size the
// preloaded bitmaps should be, so just take a note for what to do.
if (mLastRenderSpec == null) {
mScheduledPreloadFirstShownPage = firstShownPage;
mScheduledPreloadLastShownPage = lastShownPage;
} else if (mState == STATE_OPENED) {
mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
}
}
public void stopPreload() {
mRenderer.stopPreload();
}
public int getFilePageCount() {
return mRenderer.getPageCount();
}
public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) {
throwIfDestroyed();
if (DEBUG) {
Log.i(LOG_TAG, "Acquiring provider for page: " + pageIndex);
}
return new PageContentProvider(pageIndex, owner);
}
public void releasePageContentProvider(PageContentProvider provider) {
throwIfDestroyed();
if (DEBUG) {
Log.i(LOG_TAG, "Releasing provider for page: " + provider.mPageIndex);
}
provider.cancelLoad();
}
@Override
protected void finalize() throws Throwable {
try {
if (mState != STATE_DESTROYED) {
mCloseGuard.warnIfOpen();
destroy(null);
}
} finally {
super.finalize();
}
}
private void throwIfNotOpened() {
if (mState != STATE_OPENED) {
throw new IllegalStateException("Not opened");
}
}
private void throwIfNotClosed() {
if (mState != STATE_CLOSED) {
throw new IllegalStateException("Not closed");
}
}
private void throwIfDestroyed() {
if (mState == STATE_DESTROYED) {
throw new IllegalStateException("Destroyed");
}
}
public final class PageContentProvider {
private final int mPageIndex;
private View mOwner;
public PageContentProvider(int pageIndex, View owner) {
mPageIndex = pageIndex;
mOwner = owner;
}
public View getOwner() {
return mOwner;
}
public int getPageIndex() {
return mPageIndex;
}
public void getPageContent(RenderSpec renderSpec, OnPageContentAvailableCallback callback) {
throwIfDestroyed();
mLastRenderSpec = renderSpec;
// We tired to preload but didn't know the bitmap size, now
// that we know let us do the work.
if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
&& mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
}
if (mState == STATE_OPENED) {
mRenderer.renderPage(mPageIndex, renderSpec, callback);
} else {
mRenderer.getCachedPage(mPageIndex, renderSpec, callback);
}
}
void cancelLoad() {
throwIfDestroyed();
if (mState == STATE_OPENED) {
mRenderer.cancelRendering(mPageIndex);
}
}
}
private static final class PageContentLruCache {
private final LinkedHashMap<Integer, RenderedPage> mRenderedPages =
new LinkedHashMap<>();
private final int mMaxSizeInBytes;
private int mSizeInBytes;
public PageContentLruCache(int maxSizeInBytes) {
mMaxSizeInBytes = maxSizeInBytes;
}
public RenderedPage getRenderedPage(int pageIndex) {
return mRenderedPages.get(pageIndex);
}
public RenderedPage removeRenderedPage(int pageIndex) {
RenderedPage page = mRenderedPages.remove(pageIndex);
if (page != null) {
mSizeInBytes -= page.getSizeInBytes();
}
return page;
}
public RenderedPage putRenderedPage(int pageIndex, RenderedPage renderedPage) {
RenderedPage oldRenderedPage = mRenderedPages.remove(pageIndex);
if (oldRenderedPage != null) {
if (!oldRenderedPage.renderSpec.equals(renderedPage.renderSpec)) {
throw new IllegalStateException("Wrong page size");
}
} else {
final int contentSizeInBytes = renderedPage.getSizeInBytes();
if (mSizeInBytes + contentSizeInBytes > mMaxSizeInBytes) {
throw new IllegalStateException("Client didn't free space");
}
mSizeInBytes += contentSizeInBytes;
}
return mRenderedPages.put(pageIndex, renderedPage);
}
public void invalidate() {
for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
entry.getValue().state = RenderedPage.STATE_SCRAP;
}
}
public RenderedPage removeLeastNeeded() {
if (mRenderedPages.isEmpty()) {
return null;
}
// First try to remove a rendered page that holds invalidated
// or incomplete content, i.e. its render spec is null.
for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
RenderedPage renderedPage = entry.getValue();
if (renderedPage.state == RenderedPage.STATE_SCRAP) {
Integer pageIndex = entry.getKey();
mRenderedPages.remove(pageIndex);
mSizeInBytes -= renderedPage.getSizeInBytes();
return renderedPage;
}
}
// If all rendered pages contain rendered content, then use the oldest.
final int pageIndex = mRenderedPages.eldest().getKey();
RenderedPage renderedPage = mRenderedPages.remove(pageIndex);
mSizeInBytes -= renderedPage.getSizeInBytes();
return renderedPage;
}
public int getSizeInBytes() {
return mSizeInBytes;
}
public int getMaxSizeInBytes() {
return mMaxSizeInBytes;
}
public void clear() {
Iterator<Map.Entry<Integer, RenderedPage>> iterator =
mRenderedPages.entrySet().iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
}
public static final class RenderSpec {
final int bitmapWidth;
final int bitmapHeight;
final PrintAttributes printAttributes = new PrintAttributes.Builder().build();
public RenderSpec(int bitmapWidth, int bitmapHeight,
MediaSize mediaSize, Margins minMargins) {
this.bitmapWidth = bitmapWidth;
this.bitmapHeight = bitmapHeight;
printAttributes.setMediaSize(mediaSize);
printAttributes.setMinMargins(minMargins);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
RenderSpec other = (RenderSpec) obj;
if (bitmapHeight != other.bitmapHeight) {
return false;
}
if (bitmapWidth != other.bitmapWidth) {
return false;
}
if (printAttributes != null) {
if (!printAttributes.equals(other.printAttributes)) {
return false;
}
} else if (other.printAttributes != null) {
return false;
}
return true;
}
public boolean hasSameSize(RenderedPage page) {
Bitmap bitmap = page.content.getBitmap();
return bitmap.getWidth() == bitmapWidth
&& bitmap.getHeight() == bitmapHeight;
}
@Override
public int hashCode() {
int result = bitmapWidth;
result = 31 * result + bitmapHeight;
result = 31 * result + (printAttributes != null ? printAttributes.hashCode() : 0);
return result;
}
}
private static final class RenderedPage {
public static final int STATE_RENDERED = 0;
public static final int STATE_RENDERING = 1;
public static final int STATE_SCRAP = 2;
final BitmapDrawable content;
RenderSpec renderSpec;
int state = STATE_SCRAP;
RenderedPage(BitmapDrawable content) {
this.content = content;
}
public int getSizeInBytes() {
return content.getBitmap().getByteCount();
}
public void erase() {
content.getBitmap().eraseColor(Color.WHITE);
}
}
private static final class AsyncRenderer implements ServiceConnection {
private final Object mLock = new Object();
private final Context mContext;
private final PageContentLruCache mPageContentCache;
private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>();
private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
@GuardedBy("mLock")
private IPdfRenderer mRenderer;
private OpenTask mOpenTask;
private boolean mBoundToService;
private boolean mDestroyed;
public AsyncRenderer(Context context) {
mContext = context;
ActivityManager activityManager = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
final int cacheSizeInBytes = activityManager.getMemoryClass() * BYTES_PER_MEGABYTE / 4;
mPageContentCache = new PageContentLruCache(cacheSizeInBytes);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mRenderer = IPdfRenderer.Stub.asInterface(service);
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
mRenderer = null;
}
}
public void open(ParcelFileDescriptor source, OpenDocumentCallback callback) {
// Opening a new document invalidates the cache as it has pages
// from the last document. We keep the cache even when the document
// is closed to show pages while the other side is writing the new
// document.
mPageContentCache.invalidate();
mOpenTask = new OpenTask(source, callback);
mOpenTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void close(final Runnable callback) {
cancelAllRendering();
if (mOpenTask != null) {
mOpenTask.cancel();
}
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
if (mDestroyed) {
cancel(true);
return;
}
}
@Override
protected Void doInBackground(Void... params) {
synchronized (mLock) {
try {
if (mRenderer != null) {
mRenderer.closeDocument();
}
} catch (RemoteException re) {
/* ignore */
}
}
return null;
}
@Override
public void onPostExecute(Void result) {
mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
if (callback != null) {
callback.run();
}
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void destroy() {
if (mBoundToService) {
mBoundToService = false;
mContext.unbindService(AsyncRenderer.this);
}
mPageContentCache.invalidate();
mPageContentCache.clear();
mDestroyed = true;
}
public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
if (DEBUG) {
Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
+ "-" + lastShownPage + "]");
}
final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
* BYTES_PER_PIXEL;
final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
/ bitmapSizeInBytes;
final int halfPreloadCount = (maxCachedPageCount
- (lastShownPage - firstShownPage)) / 2 - 1;
final int excessFromStart;
if (firstShownPage - halfPreloadCount < 0) {
excessFromStart = halfPreloadCount - firstShownPage;
} else {
excessFromStart = 0;
}
final int excessFromEnd;
if (lastShownPage + halfPreloadCount >= mPageCount) {
excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
} else {
excessFromEnd = 0;
}
final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
mPageCount - 1);
for (int i = fromIndex; i <= toIndex; i++) {
renderPage(i, renderSpec, null);
}
}
public void stopPreload() {
final int taskCount = mPageToRenderTaskMap.size();
for (int i = 0; i < taskCount; i++) {
RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
if (task.isPreload() && !task.isCancelled()) {
task.cancel(true);
}
}
}
public int getPageCount() {
return mPageCount;
}
public void getCachedPage(int pageIndex, RenderSpec renderSpec,
OnPageContentAvailableCallback callback) {
RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED
&& renderedPage.renderSpec.equals(renderSpec)) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
}
// Announce if needed.
if (callback != null) {
callback.onPageContentAvailable(renderedPage.content);
}
}
}
public void renderPage(int pageIndex, RenderSpec renderSpec,
OnPageContentAvailableCallback callback) {
// First, check if we have a rendered page for this index.
RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) {
// If we have rendered page with same constraints - done.
if (renderedPage.renderSpec.equals(renderSpec)) {
if (DEBUG) {
Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
}
// Announce if needed.
if (callback != null) {
callback.onPageContentAvailable(renderedPage.content);
}
return;
} else {
// If the constraints changed, mark the page obsolete.
renderedPage.state = RenderedPage.STATE_SCRAP;
}
}
// Next, check if rendering this page is scheduled.
RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex);
if (renderTask != null && !renderTask.isCancelled()) {
// If not rendered and constraints same....
if (renderTask.mRenderSpec.equals(renderSpec)) {
if (renderTask.mCallback != null) {
// If someone else is already waiting for this page - bad state.
if (callback != null && renderTask.mCallback != callback) {
throw new IllegalStateException("Page rendering not cancelled");
}
} else {
// No callback means we are preloading so just let the argument
// callback be attached to our work in progress.
renderTask.mCallback = callback;
}
return;
} else {
// If not rendered and constraints changed - cancel rendering.
renderTask.cancel(true);
}
}
// Oh well, we will have work to do...
renderTask = new RenderPageTask(pageIndex, renderSpec, callback);
mPageToRenderTaskMap.put(pageIndex, renderTask);
renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void cancelRendering(int pageIndex) {
RenderPageTask task = mPageToRenderTaskMap.get(pageIndex);
if (task != null && !task.isCancelled()) {
task.cancel(true);
}
}
private void cancelAllRendering() {
final int taskCount = mPageToRenderTaskMap.size();
for (int i = 0; i < taskCount; i++) {
RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
if (!task.isCancelled()) {
task.cancel(true);
}
}
}
private final class OpenTask extends AsyncTask<Void, Void, Integer> {
private final ParcelFileDescriptor mSource;
private final OpenDocumentCallback mCallback;
public OpenTask(ParcelFileDescriptor source, OpenDocumentCallback callback) {
mSource = source;
mCallback = callback;
}
@Override
protected void onPreExecute() {
if (mDestroyed) {
cancel(true);
return;
}
Intent intent = new Intent(PdfManipulationService.ACTION_GET_RENDERER);
intent.setClass(mContext, PdfManipulationService.class);
intent.setData(Uri.fromParts("fake-scheme", String.valueOf(
AsyncRenderer.this.hashCode()), null));
mContext.bindService(intent, AsyncRenderer.this, Context.BIND_AUTO_CREATE);
mBoundToService = true;
}
@Override
protected Integer doInBackground(Void... params) {
synchronized (mLock) {
while (mRenderer == null && !isCancelled()) {
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
try {
return mRenderer.openDocument(mSource);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Cannot open PDF document");
return PdfManipulationService.ERROR_MALFORMED_PDF_FILE;
} finally {
// Close the fd as we passed it to another process
// which took ownership.
IoUtils.closeQuietly(mSource);
}
}
}
@Override
public void onPostExecute(Integer pageCount) {
switch (pageCount) {
case PdfManipulationService.ERROR_MALFORMED_PDF_FILE: {
mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
if (mCallback != null) {
mCallback.onFailure(OpenDocumentCallback.ERROR_MALFORMED_PDF_FILE);
}
} break;
case PdfManipulationService.ERROR_SECURE_PDF_FILE: {
mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
if (mCallback != null) {
mCallback.onFailure(OpenDocumentCallback.ERROR_SECURE_PDF_FILE);
}
} break;
default: {
mPageCount = pageCount;
if (mCallback != null) {
mCallback.onSuccess();
}
} break;
}
mOpenTask = null;
}
@Override
protected void onCancelled(Integer integer) {
mOpenTask = null;
}
public void cancel() {
cancel(true);
synchronized(mLock) {
mLock.notifyAll();
}
}
}
private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> {
final int mPageIndex;
final RenderSpec mRenderSpec;
OnPageContentAvailableCallback mCallback;
RenderedPage mRenderedPage;
public RenderPageTask(int pageIndex, RenderSpec renderSpec,
OnPageContentAvailableCallback callback) {
mPageIndex = pageIndex;
mRenderSpec = renderSpec;
mCallback = callback;
}
@Override
protected void onPreExecute() {
mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex);
if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) {
throw new IllegalStateException("Trying to render a rendered page");
}
// Reuse bitmap for the page only if the right size.
if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) {
if (DEBUG) {
Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
+ " with different size.");
}
mPageContentCache.removeRenderedPage(mPageIndex);
mRenderedPage = null;
}
final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
* mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
// Try to find a bitmap to reuse.
while (mRenderedPage == null) {
// Fill the cache greedily.
if (mPageContentCache.getSizeInBytes() <= 0
|| mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
<= mPageContentCache.getMaxSizeInBytes()) {
break;
}
RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
if (!mRenderSpec.hasSameSize(renderedPage)) {
if (DEBUG) {
Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
+ " with different size.");
}
continue;
}
mRenderedPage = renderedPage;
renderedPage.erase();
if (DEBUG) {
Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
+ mPageContentCache.getSizeInBytes() + " bytes");
}
break;
}
if (mRenderedPage == null) {
if (DEBUG) {
Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
+ mPageContentCache.getSizeInBytes() + " bytes");
}
Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth,
mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.WHITE);
BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap);
mRenderedPage = new RenderedPage(content);
}
mRenderedPage.renderSpec = mRenderSpec;
mRenderedPage.state = RenderedPage.STATE_RENDERING;
mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage);
}
@Override
protected RenderedPage doInBackground(Void... params) {
if (isCancelled()) {
return mRenderedPage;
}
Bitmap bitmap = mRenderedPage.content.getBitmap();
ParcelFileDescriptor[] pipe = null;
try {
pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor source = pipe[0];
ParcelFileDescriptor destination = pipe[1];
mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(),
mRenderSpec.printAttributes, destination);
// We passed the file descriptor to the other side which took
// ownership, so close our copy for the write to complete.
destination.close();
BitmapSerializeUtils.readBitmapPixels(bitmap, source);
} catch (IOException|RemoteException e) {
Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e);
} finally {
IoUtils.closeQuietly(pipe[0]);
IoUtils.closeQuietly(pipe[1]);
}
return mRenderedPage;
}
@Override
public void onPostExecute(RenderedPage renderedPage) {
if (DEBUG) {
Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex);
}
// This task is done.
mPageToRenderTaskMap.remove(mPageIndex);
// Take a note that the content is rendered.
renderedPage.state = RenderedPage.STATE_RENDERED;
// Announce success if needed.
if (mCallback != null) {
mCallback.onPageContentAvailable(renderedPage.content);
}
}
@Override
protected void onCancelled(RenderedPage renderedPage) {
if (DEBUG) {
Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex);
}
// This task is done.
mPageToRenderTaskMap.remove(mPageIndex);
// If canceled before on pre-execute.
if (renderedPage == null) {
return;
}
// Take a note that the content is not rendered.
renderedPage.state = RenderedPage.STATE_SCRAP;
}
public boolean isPreload() {
return mCallback == null;
}
}
}
}