diff options
Diffstat (limited to 'platform/android')
16 files changed, 3225 insertions, 3225 deletions
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java index 5ab52646..95eb4cdb 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java @@ -23,440 +23,440 @@ import com.artifex.mupdf.fitz.Page; public class DocPageView extends View implements Callback { - private final Document mDoc; - private int mPageNum = -1; - private Page mPage; - private boolean mFinished = false; - - private float mScale = 1.0f; - private float mZoom = 1.0f; - - // drawing bitmap - private Bitmap mDrawBitmap = null; - private Rect mDrawSrcRect = new Rect(); - private final Rect mDrawDstRect = new Rect(); - private final Rect sourceRect = new Rect(); - private final Rect renderRect = new Rect(); - private float renderScale; - - // current size of this view - private Point mSize; - - // for drawing - private final Paint mPainter; - private final Rect mSrcRect = new Rect(); - private final Rect mDstRect = new Rect(); - private float drawScale; - - private static final boolean DEBUG_PAGE_RENDERING = false; - - private static final float mResolution = 160f; - - DisplayList pageContents = null; - DisplayList annotContents = null; - - public DocPageView(Context context, Document theDoc) - { - super(context); - setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - mDoc = theDoc; - mPainter = new Paint(); - - setFocusable(true); - setFocusableInTouchMode(true); - } - - public void setupPage(final int thePageNum, int w, int h) - { - // if the page number has not yet been set, or has changed, - // make a new page object. - if (thePageNum != mPageNum) - { - mPageNum = thePageNum; - - // de-cache contents and annotations - if (pageContents != null) { - pageContents.destroy(); - pageContents = null; - } - if (annotContents != null) { - annotContents.destroy(); - annotContents = null; - } - - // destroy the page before making a new one. - if (mPage!=null) - mPage.destroy(); - mPage = mDoc.loadPage(mPageNum); - } - - // calculate zoom that makes page fit - - com.artifex.mupdf.fitz.Rect pageBounds = mPage.getBounds(); - - float pagew = (pageBounds.x1 - pageBounds.x0)*mResolution/72f; - float pageH = (pageBounds.y1 - pageBounds.y0)*mResolution/72f; - - mZoom = w/pagew; - mSize = new Point((int)(pagew*mZoom), (int)(pageH*mZoom)); - } - - public void setNewScale(float scale) { - mScale = scale; - } - - public int getCalculatedWidth() - { - return (int)(mSize.x * mScale); - } - - public int getCalculatedHeight() - { - return (int)(mSize.y * mScale); - } - - // a test for real visibility - private static final Rect visRect = new Rect(); - public boolean isReallyVisible() { - return getLocalVisibleRect(visRect); - } - - // This function renders colored rectangles and text in place of the page. - // Use it to test layouts. - private void renderNoPage(Bitmap bitmap, final RenderListener listener, Rect localVisRect, Rect globalVisRect) - { - // specify where to draw to and from - mDrawBitmap = bitmap; - mDrawSrcRect.set(globalVisRect); - mDrawDstRect.set(localVisRect); - - // make a rect representing the entire page in screen coordinates - int[] locations = new int[2]; - getLocationOnScreen(locations); - Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getWidth(), locations[1]+getHeight()); - - // draw a yellow page with a red border containing the page number - - Paint p = new Paint(); - Canvas c = new Canvas(bitmap); - p.setColor(Color.RED); - p.setStyle(Paint.Style.FILL); - c.drawRect(pageRect,p); - - Rect smaller = new Rect(pageRect); - int inset = (int)(40*mScale); - smaller.inset(inset, inset); - p.setColor(Color.YELLOW); - p.setStyle(Paint.Style.FILL); - c.drawRect(smaller,p); - - String s = "" + (mPageNum+1); - p.setColor(Color.BLACK); - p.setTextSize(200.0f*mScale); - c.drawText(s, pageRect.left+(90*mScale), pageRect.top+(290*mScale), p); - - invalidate(); - listener.progress(0); - } - - public void render(Bitmap bitmap, final RenderListener listener) - { - if (mFinished) - return; - - // get local visible rect - Rect localVisRect = new Rect(); - if (!getLocalVisibleRect(localVisRect)) { - listener.progress(0); - return; // not visible - } - - // get global visible rect - Rect globalVisRect = new Rect(); - if (!getGlobalVisibleRect(globalVisRect)) { - listener.progress(0); - return; // not visible - } - - // do the render. - if (DEBUG_PAGE_RENDERING) - renderNoPage(bitmap, listener, localVisRect, globalVisRect); - else - renderPage(bitmap, listener, localVisRect, globalVisRect); - } - - // This function renders the document's page. - private void renderPage(final Bitmap bitmap, final RenderListener listener, final Rect localVisRect, final Rect globalVisRect) - { - // make a rect representing the entire page - // This might be outside the bounds of the bitmap - int[] locations = new int[2]; - getLocationOnScreen(locations); - Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getCalculatedWidth(), locations[1]+getCalculatedHeight()); - - // make a rect representing the patch - // clip this to the bitmap - Rect patchRect = new Rect(pageRect); - patchRect.left = Math.max(patchRect.left,0); - patchRect.top = Math.max(patchRect.top,0); - patchRect.right = Math.min(patchRect.right,bitmap.getWidth()); - patchRect.bottom = Math.min(patchRect.bottom,bitmap.getHeight()); - - // set up the page and patch coordinates for the device - int pageX0 = pageRect.left; - int pageY0 = pageRect.top; - int pageX1 = pageRect.right; - int pageY1 = pageRect.bottom; - - int patchX0 = patchRect.left; - int patchY0 = patchRect.top; - int patchX1 = patchRect.right; - int patchY1 = patchRect.bottom; - - // set up a matrix for scaling - Matrix ctm = Matrix.Identity(); - ctm.scale(mScale*mZoom*mResolution/72f); - - // cache this page's display and annotation lists - cachePage(); - - sourceRect.set(globalVisRect); - renderRect.set(localVisRect); - renderScale = mScale; - - // Render the page in the background - DrawTaskParams params = new DrawTaskParams(new RenderListener() { - @Override - public void progress(int error) - { - // specify where to draw to and from - mDrawBitmap = bitmap; - mDrawSrcRect.set(sourceRect); - mDrawDstRect.set(renderRect); - drawScale = renderScale; - - invalidate(); - listener.progress(0); - } - }, ctm, bitmap, pageX0, pageY0, pageX1, pageY1, patchX0, patchY0, patchX1, patchY1); - - new DrawTask().execute(params, null, null); - } - - private void cachePage() - { - Cookie cookie = new Cookie(); - - if (pageContents==null) - { - // run the display list - pageContents = new DisplayList(); - DisplayListDevice dispDev = new DisplayListDevice(pageContents); - try { - mPage.run(dispDev, new Matrix(1, 0, 0, 1, 0, 0), cookie); - } - catch (RuntimeException e) { - pageContents.destroy(); - dispDev.destroy(); - throw(e); - } - finally { - dispDev.destroy(); - } - } - - if (annotContents==null) - { - // run the annotation list - annotContents = new DisplayList(); - DisplayListDevice annotDev = new DisplayListDevice(annotContents); - try { - mPage.run(annotDev, new Matrix(1, 0, 0, 1, 0, 0), cookie); - } - catch (RuntimeException e) { - annotContents.destroy(); - annotDev.destroy(); - throw(e); - } - finally { - annotDev.destroy(); - } - } - } - - @Override - public void onDraw(Canvas canvas) { - - if (mFinished) - return; - - // get bitmap to draw - Bitmap bitmap = mDrawBitmap; - if (bitmap==null) - return; // not yet rendered - - // set rectangles for drawing - mSrcRect.set(mDrawSrcRect); - mDstRect.set(mDrawDstRect); - - // if the scale has changed, adjust the destination - if (drawScale != mScale) - { - mDstRect.left *= (mScale/drawScale); - mDstRect.top *= (mScale/drawScale); - mDstRect.right *= (mScale/drawScale); - mDstRect.bottom *= (mScale/drawScale); - } - - // draw - canvas.drawBitmap(bitmap, mSrcRect, mDstRect, mPainter); - } - - public boolean onSingleTap(int x, int y) { - // NOTE: when double-tapping, a single-tap will also happen first. - // so that must be safe to do. - return false; - } - - public void onDoubleTap(int x, int y) { - } - - private Point screenToPage(Point p) - { - return screenToPage(p.x, p.y); - } - - private Point screenToPage(int screenX, int screenY) - { - // convert to view-relative - int viewX = screenX; - int viewY = screenY; - int loc[] = new int[2]; - getLocationOnScreen(loc); - viewX -= loc[0]; - viewY -= loc[1]; - - // convert to page-relative - double factor = mZoom * mScale; - int pageX = (int)(((double)viewX)/factor); - int pageY = (int)(((double)viewY)/factor); - - return new Point(pageX,pageY); - } - - public Point pageToView(int pageX, int pageY) - { - double factor = mZoom * mScale; - - int viewX = (int)(((double)pageX)*factor); - int viewY = (int)(((double)pageY)*factor); - - return new Point(viewX, viewY); - } - - public Point viewToPage(int viewX, int viewY) - { - double factor = mZoom * mScale; - - int pageX = (int)(((double)viewX)/factor); - int pageY = (int)(((double)viewY)/factor); - - return new Point(pageX, pageY); - } - - public void finish() - { - mFinished = true; - - // destroy the page - if (mPage!=null) { - mPage.destroy(); - mPage = null; - } - } - - // during layout, a DocView-relative rect is calculated and stashed here. - private final Rect mChildRect = new Rect(); - public void setChildRect(Rect r) {mChildRect.set(r);} - public Rect getChildRect() {return mChildRect;} - - private class DrawTaskParams { - DrawTaskParams (RenderListener listener, Matrix ctm, Bitmap bitmap, - int pageX0, int pageY0, int pageX1, int pageY1, - int patchX0, int patchY0, int patchX1, int patchY1) - { - this.listener = listener; - this.ctm = ctm; - this.bitmap = bitmap; - this.pageX0 = pageX0; - this.pageY0 = pageY0; - this.pageX1 = pageX1; - this.pageY1 = pageY1; - this.patchX0 = patchX0; - this.patchY0 = patchY0; - this.patchX1 = patchX1; - this.patchY1 = patchY1; - } - - public RenderListener listener; - public Matrix ctm; - public Bitmap bitmap; - public int pageX0; - public int pageY0; - public int pageX1; - public int pageY1; - public int patchX0; - public int patchY0; - public int patchX1; - public int patchY1; - } - - // The definition of our task class - private class DrawTask extends AsyncTask<DrawTaskParams, Void, Void> - { - private DrawTaskParams params = null; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Void doInBackground(DrawTaskParams... paramList) { - params = paramList[0]; - - Cookie cookie = new Cookie(); - AndroidDrawDevice dev = new AndroidDrawDevice(params.bitmap, params.pageX0, params.pageY0, params.pageX1, params.pageY1, params.patchX0, params.patchY0, params.patchX1, params.patchY1); - try { - if (pageContents != null) { - pageContents.run(dev, params.ctm, cookie); - } - if (annotContents != null) { - annotContents.run(dev, params.ctm, cookie); - } - } - catch (Exception e) - { - Log.e("mupdf", e.getMessage()); - } - finally { - dev.destroy(); - } - - return null; - } - - @Override - protected void onProgressUpdate(Void... values) { - super.onProgressUpdate(values); - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - params.listener.progress(0); - } - } + private final Document mDoc; + private int mPageNum = -1; + private Page mPage; + private boolean mFinished = false; + + private float mScale = 1.0f; + private float mZoom = 1.0f; + + // drawing bitmap + private Bitmap mDrawBitmap = null; + private Rect mDrawSrcRect = new Rect(); + private final Rect mDrawDstRect = new Rect(); + private final Rect sourceRect = new Rect(); + private final Rect renderRect = new Rect(); + private float renderScale; + + // current size of this view + private Point mSize; + + // for drawing + private final Paint mPainter; + private final Rect mSrcRect = new Rect(); + private final Rect mDstRect = new Rect(); + private float drawScale; + + private static final boolean DEBUG_PAGE_RENDERING = false; + + private static final float mResolution = 160f; + + DisplayList pageContents = null; + DisplayList annotContents = null; + + public DocPageView(Context context, Document theDoc) + { + super(context); + setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + mDoc = theDoc; + mPainter = new Paint(); + + setFocusable(true); + setFocusableInTouchMode(true); + } + + public void setupPage(final int thePageNum, int w, int h) + { + // if the page number has not yet been set, or has changed, + // make a new page object. + if (thePageNum != mPageNum) + { + mPageNum = thePageNum; + + // de-cache contents and annotations + if (pageContents != null) { + pageContents.destroy(); + pageContents = null; + } + if (annotContents != null) { + annotContents.destroy(); + annotContents = null; + } + + // destroy the page before making a new one. + if (mPage!=null) + mPage.destroy(); + mPage = mDoc.loadPage(mPageNum); + } + + // calculate zoom that makes page fit + + com.artifex.mupdf.fitz.Rect pageBounds = mPage.getBounds(); + + float pagew = (pageBounds.x1 - pageBounds.x0)*mResolution/72f; + float pageH = (pageBounds.y1 - pageBounds.y0)*mResolution/72f; + + mZoom = w/pagew; + mSize = new Point((int)(pagew*mZoom), (int)(pageH*mZoom)); + } + + public void setNewScale(float scale) { + mScale = scale; + } + + public int getCalculatedWidth() + { + return (int)(mSize.x * mScale); + } + + public int getCalculatedHeight() + { + return (int)(mSize.y * mScale); + } + + // a test for real visibility + private static final Rect visRect = new Rect(); + public boolean isReallyVisible() { + return getLocalVisibleRect(visRect); + } + + // This function renders colored rectangles and text in place of the page. + // Use it to test layouts. + private void renderNoPage(Bitmap bitmap, final RenderListener listener, Rect localVisRect, Rect globalVisRect) + { + // specify where to draw to and from + mDrawBitmap = bitmap; + mDrawSrcRect.set(globalVisRect); + mDrawDstRect.set(localVisRect); + + // make a rect representing the entire page in screen coordinates + int[] locations = new int[2]; + getLocationOnScreen(locations); + Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getWidth(), locations[1]+getHeight()); + + // draw a yellow page with a red border containing the page number + + Paint p = new Paint(); + Canvas c = new Canvas(bitmap); + p.setColor(Color.RED); + p.setStyle(Paint.Style.FILL); + c.drawRect(pageRect,p); + + Rect smaller = new Rect(pageRect); + int inset = (int)(40*mScale); + smaller.inset(inset, inset); + p.setColor(Color.YELLOW); + p.setStyle(Paint.Style.FILL); + c.drawRect(smaller,p); + + String s = "" + (mPageNum+1); + p.setColor(Color.BLACK); + p.setTextSize(200.0f*mScale); + c.drawText(s, pageRect.left+(90*mScale), pageRect.top+(290*mScale), p); + + invalidate(); + listener.progress(0); + } + + public void render(Bitmap bitmap, final RenderListener listener) + { + if (mFinished) + return; + + // get local visible rect + Rect localVisRect = new Rect(); + if (!getLocalVisibleRect(localVisRect)) { + listener.progress(0); + return; // not visible + } + + // get global visible rect + Rect globalVisRect = new Rect(); + if (!getGlobalVisibleRect(globalVisRect)) { + listener.progress(0); + return; // not visible + } + + // do the render. + if (DEBUG_PAGE_RENDERING) + renderNoPage(bitmap, listener, localVisRect, globalVisRect); + else + renderPage(bitmap, listener, localVisRect, globalVisRect); + } + + // This function renders the document's page. + private void renderPage(final Bitmap bitmap, final RenderListener listener, final Rect localVisRect, final Rect globalVisRect) + { + // make a rect representing the entire page + // This might be outside the bounds of the bitmap + int[] locations = new int[2]; + getLocationOnScreen(locations); + Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getCalculatedWidth(), locations[1]+getCalculatedHeight()); + + // make a rect representing the patch + // clip this to the bitmap + Rect patchRect = new Rect(pageRect); + patchRect.left = Math.max(patchRect.left,0); + patchRect.top = Math.max(patchRect.top,0); + patchRect.right = Math.min(patchRect.right,bitmap.getWidth()); + patchRect.bottom = Math.min(patchRect.bottom,bitmap.getHeight()); + + // set up the page and patch coordinates for the device + int pageX0 = pageRect.left; + int pageY0 = pageRect.top; + int pageX1 = pageRect.right; + int pageY1 = pageRect.bottom; + + int patchX0 = patchRect.left; + int patchY0 = patchRect.top; + int patchX1 = patchRect.right; + int patchY1 = patchRect.bottom; + + // set up a matrix for scaling + Matrix ctm = Matrix.Identity(); + ctm.scale(mScale*mZoom*mResolution/72f); + + // cache this page's display and annotation lists + cachePage(); + + sourceRect.set(globalVisRect); + renderRect.set(localVisRect); + renderScale = mScale; + + // Render the page in the background + DrawTaskParams params = new DrawTaskParams(new RenderListener() { + @Override + public void progress(int error) + { + // specify where to draw to and from + mDrawBitmap = bitmap; + mDrawSrcRect.set(sourceRect); + mDrawDstRect.set(renderRect); + drawScale = renderScale; + + invalidate(); + listener.progress(0); + } + }, ctm, bitmap, pageX0, pageY0, pageX1, pageY1, patchX0, patchY0, patchX1, patchY1); + + new DrawTask().execute(params, null, null); + } + + private void cachePage() + { + Cookie cookie = new Cookie(); + + if (pageContents==null) + { + // run the display list + pageContents = new DisplayList(); + DisplayListDevice dispDev = new DisplayListDevice(pageContents); + try { + mPage.run(dispDev, new Matrix(1, 0, 0, 1, 0, 0), cookie); + } + catch (RuntimeException e) { + pageContents.destroy(); + dispDev.destroy(); + throw(e); + } + finally { + dispDev.destroy(); + } + } + + if (annotContents==null) + { + // run the annotation list + annotContents = new DisplayList(); + DisplayListDevice annotDev = new DisplayListDevice(annotContents); + try { + mPage.run(annotDev, new Matrix(1, 0, 0, 1, 0, 0), cookie); + } + catch (RuntimeException e) { + annotContents.destroy(); + annotDev.destroy(); + throw(e); + } + finally { + annotDev.destroy(); + } + } + } + + @Override + public void onDraw(Canvas canvas) { + + if (mFinished) + return; + + // get bitmap to draw + Bitmap bitmap = mDrawBitmap; + if (bitmap==null) + return; // not yet rendered + + // set rectangles for drawing + mSrcRect.set(mDrawSrcRect); + mDstRect.set(mDrawDstRect); + + // if the scale has changed, adjust the destination + if (drawScale != mScale) + { + mDstRect.left *= (mScale/drawScale); + mDstRect.top *= (mScale/drawScale); + mDstRect.right *= (mScale/drawScale); + mDstRect.bottom *= (mScale/drawScale); + } + + // draw + canvas.drawBitmap(bitmap, mSrcRect, mDstRect, mPainter); + } + + public boolean onSingleTap(int x, int y) { + // NOTE: when double-tapping, a single-tap will also happen first. + // so that must be safe to do. + return false; + } + + public void onDoubleTap(int x, int y) { + } + + private Point screenToPage(Point p) + { + return screenToPage(p.x, p.y); + } + + private Point screenToPage(int screenX, int screenY) + { + // convert to view-relative + int viewX = screenX; + int viewY = screenY; + int loc[] = new int[2]; + getLocationOnScreen(loc); + viewX -= loc[0]; + viewY -= loc[1]; + + // convert to page-relative + double factor = mZoom * mScale; + int pageX = (int)(((double)viewX)/factor); + int pageY = (int)(((double)viewY)/factor); + + return new Point(pageX,pageY); + } + + public Point pageToView(int pageX, int pageY) + { + double factor = mZoom * mScale; + + int viewX = (int)(((double)pageX)*factor); + int viewY = (int)(((double)pageY)*factor); + + return new Point(viewX, viewY); + } + + public Point viewToPage(int viewX, int viewY) + { + double factor = mZoom * mScale; + + int pageX = (int)(((double)viewX)/factor); + int pageY = (int)(((double)viewY)/factor); + + return new Point(pageX, pageY); + } + + public void finish() + { + mFinished = true; + + // destroy the page + if (mPage!=null) { + mPage.destroy(); + mPage = null; + } + } + + // during layout, a DocView-relative rect is calculated and stashed here. + private final Rect mChildRect = new Rect(); + public void setChildRect(Rect r) {mChildRect.set(r);} + public Rect getChildRect() {return mChildRect;} + + private class DrawTaskParams { + DrawTaskParams (RenderListener listener, Matrix ctm, Bitmap bitmap, + int pageX0, int pageY0, int pageX1, int pageY1, + int patchX0, int patchY0, int patchX1, int patchY1) + { + this.listener = listener; + this.ctm = ctm; + this.bitmap = bitmap; + this.pageX0 = pageX0; + this.pageY0 = pageY0; + this.pageX1 = pageX1; + this.pageY1 = pageY1; + this.patchX0 = patchX0; + this.patchY0 = patchY0; + this.patchX1 = patchX1; + this.patchY1 = patchY1; + } + + public RenderListener listener; + public Matrix ctm; + public Bitmap bitmap; + public int pageX0; + public int pageY0; + public int pageX1; + public int pageY1; + public int patchX0; + public int patchY0; + public int patchX1; + public int patchY1; + } + + // The definition of our task class + private class DrawTask extends AsyncTask<DrawTaskParams, Void, Void> + { + private DrawTaskParams params = null; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected Void doInBackground(DrawTaskParams... paramList) { + params = paramList[0]; + + Cookie cookie = new Cookie(); + AndroidDrawDevice dev = new AndroidDrawDevice(params.bitmap, params.pageX0, params.pageY0, params.pageX1, params.pageY1, params.patchX0, params.patchY0, params.patchX1, params.patchY1); + try { + if (pageContents != null) { + pageContents.run(dev, params.ctm, cookie); + } + if (annotContents != null) { + annotContents.run(dev, params.ctm, cookie); + } + } + catch (Exception e) + { + Log.e("mupdf", e.getMessage()); + } + finally { + dev.destroy(); + } + + return null; + } + + @Override + protected void onProgressUpdate(Void... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + params.listener.progress(0); + } + } } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java index 34613dc3..da6d7677 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java @@ -22,897 +22,897 @@ import android.widget.Scroller; import com.artifex.mupdf.fitz.Document; public class DocView - extends AdapterView<Adapter> - implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable + extends AdapterView<Adapter> + implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable { - private static final int GAP = 20; - - private static final float MIN_SCALE = .15f; - private static final float MAX_SCALE = 5.0f; - - private PageAdapter mAdapter; - private boolean mFinished = false; - - private final SparseArray<View> mChildViews = new SparseArray<View>(3); - - private boolean mScaling; // Whether the user is currently pinch zooming - private float mScale = 1.0f; - private int mXScroll; // Scroll amounts recorded from events. - private int mYScroll; // and then accounted for in onLayout - - private GestureDetector mGestureDetector; - private ScaleGestureDetector mScaleGestureDetector; - - // bitmaps for rendering - // these are created by the activity and set using setBitmaps() - private final static double OVERSIZE_FACTOR = 1.1; - private final Bitmap[] bitmaps = {null,null}; - - private int bitmapIndex = 0; - private boolean renderRequested = false; - private int renderCount = 0; - - // used during layout - private final Rect mChildRect = new Rect(); - private final Rect mViewport = new Rect(); - private final Point mViewportOrigin = new Point(); - private final Rect mBlockRect = new Rect(); - private final Rect mLastBlockRect = new Rect(); - private int mLastLayoutColumns = 1; - protected int mPageCollectionHeight; - private int mPageCollectionWidth; - - // for flinging - private static final int MOVING_DIAGONALLY = 0; - private static final int MOVING_LEFT = 1; - private static final int MOVING_RIGHT = 2; - private static final int MOVING_UP = 3; - private static final int MOVING_DOWN = 4; - - private static final float MIN_FLING_VELOCITY = 1500.0f; - private static final long FLING_THROTTLE_TIME = 20; - - private Scroller mScroller; - private Stepper mStepper; - private int mScrollerLastX; - private int mScrollerLastY; - private long mFlingStartTime; - - // for single- and double-tapping - private long mLastTapTime = 0; - private float lastTapX; - private float lastTapY; - private int mTapStatus = 0; - - private static final int DOUBLE_TAP_TIME = 300; - private static final int SHOW_KEYBOARD_TIME = 500; - - // the document. - private Document mDoc; - - private boolean mStarted = false; - - public DocView(Context context) { - super(context); - initialize(context); - } - - public DocView(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(context); - } - - public DocView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initialize(context); - } - - protected Context mContext = null; - - protected void initialize(Context context) - { - mContext = context; - mGestureDetector = new GestureDetector(context, this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - mStepper = new Stepper(this, this); - - // create bitmaps - makeBitmaps(); - } - - private void makeBitmaps() - { - // get current screen size - WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - int screenW = metrics.widthPixels; - int screenH = metrics.heightPixels; - - // make two bitmaps. - // make them large enough for both screen orientations, so we don't have to - // change them when the orientation changes. - - int w = (int)(screenW*OVERSIZE_FACTOR); - int h = (int)(screenH*OVERSIZE_FACTOR); - int size = Math.max(w,h); - for (int i=0;i<bitmaps.length;i++) - bitmaps[i] = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - } - - public void start(final String path) - { - // wait for the layout to finish - ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() - { - @Override - public void onGlobalLayout() - { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - - mAdapter = new PageAdapter(mContext); - mAdapter.setWidth(getWidth()); - mDoc = new Document(path); - mAdapter.setDocument(mDoc); - mScale = 1.0f; - mStarted = true; - triggerRender(); - } - }); - } - - private void onScaleChild(View v, Float scale) - { - ((DocPageView)v).setNewScale(scale); - } - - public void onOrientationChange() - { - triggerRender(); - } - - private void onSizeChange(float factor) - { - mScale *= factor; - scaleChildren(); - requestLayout(); - } - - public boolean onDown(MotionEvent arg0) { - mScroller.forceFinished(true); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - - // not while we're scaling - if (mScaling) - return true; - - // not while a previous fling is underway - if (!mScroller.isFinished()) - return true; - - // must be really flinging - float vel = Math.max(Math.abs(velocityX),Math.abs(velocityY)); - if (vel<MIN_FLING_VELOCITY) - return false; - - // what direction? - int direction = directionOfTravel(velocityX,velocityY); - - mFlingStartTime = System.currentTimeMillis(); - - switch (direction) - { - case MOVING_DOWN: - smoothScrollBy(0, getHeight()/2); - break; - - case MOVING_UP: - smoothScrollBy(0, -getHeight()/2); - break; - - default: - break; - } - - return true; - } - - private static int directionOfTravel(float vx, float vy) { - if (Math.abs(vx) > 2 * Math.abs(vy)) - return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; - else if (Math.abs(vy) > 2 * Math.abs(vx)) - return (vy > 0) ? MOVING_DOWN : MOVING_UP; - else - return MOVING_DIAGONALLY; - } - - public void onLongPress(MotionEvent e) { - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - - // not if we're scaling - if (mScaling) - return true; - - // not if a previous fling is underway - if (!mScroller.isFinished()) - return true; - - // accumulate scrolling amount. - mXScroll -= distanceX; - mYScroll -= distanceY; - - requestLayout(); - - return true; - } - - public void onShowPress(MotionEvent e) { - } - - protected DocPageView findPageViewContainingPoint(int x, int y, boolean includeMargin) - { - for (int i = 0; i < getChildCount(); i++) - { - // get the rect for the page - View child = getChildAt(i); - Rect childRect = new Rect(); - child.getGlobalVisibleRect(childRect); - - // add in the margin - if (includeMargin) - { - childRect.left -= GAP/2; - childRect.right += GAP/2; - childRect.top -= GAP/2; - childRect.bottom += GAP/2; - } - - // see if the rect contains the point - if (childRect.contains(x,y)) - return (DocPageView)child; - } - - return null; - } - - protected Point eventToScreen(float fx, float fy) - { - int x = Math.round(fx); - int y = Math.round(fy); - Rect docRect = new Rect(); - getGlobalVisibleRect(docRect); - x += docRect.left; - y += docRect.top; - - return new Point(x,y); - } - - protected void doSingleTap(float fx, float fy) - { - // find the page view that was tapped. - Point p = eventToScreen(fx,fy); - final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); - if (dpv==null) - return; - - // see if the age wants to handle the single tap - boolean handled = dpv.onSingleTap(p.x,p.y); - - // if not, ... - if (!handled) - { - // schedule a task in the near future to check if we're still a single-tap. - final Handler handler = new Handler(); - final Point tappedPoint = p; - handler.postDelayed(new Runnable() - { - @Override - public void run() - { - if (mTapStatus==1) - { - // still single - } - else - { - // double - } - mTapStatus = 0; - } - }, SHOW_KEYBOARD_TIME); - } - } - - protected void doDoubleTap(float fx, float fy) - { - Point p = eventToScreen(fx,fy); - DocPageView v = findPageViewContainingPoint(p.x, p.y, false); - if (v != null) { - v.onDoubleTap(p.x,p.y); - } - } - - public boolean onSingleTapUp(final MotionEvent e) - { - long now = System.currentTimeMillis(); - if (mLastTapTime!=0 && ((now-mLastTapTime)<DOUBLE_TAP_TIME)) - { - mTapStatus = 2; - doDoubleTap(lastTapX,lastTapY); - mLastTapTime = 0; - } - else - { - mLastTapTime = now; - lastTapX = e.getX(); - lastTapY = e.getY(); - doSingleTap(lastTapX, lastTapY); - mTapStatus = 1; - } - - return false; - } - - private void scaleChildren() - { - // scale children - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - cv.setNewScale(mScale); - } - } - - public boolean onScale(ScaleGestureDetector detector) - { - // new scale factor - float previousScale = mScale; - mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); - - // did we really scale? - if (mScale == previousScale) - return true; - - // scale children - scaleChildren(); - - // maintain focus while scaling - float currentFocusX = detector.getFocusX(); - float currentFocusY = detector.getFocusY(); - int viewFocusX = (int)currentFocusX + getScrollX(); - int viewFocusY = (int)currentFocusY + getScrollY(); - mXScroll += viewFocusX - viewFocusX * detector.getScaleFactor(); - mYScroll += viewFocusY - viewFocusY * detector.getScaleFactor(); - - requestLayout(); - - return true; - } - - public boolean onScaleBegin(ScaleGestureDetector detector) { - - mScaling = true; - - // Ignore any scroll amounts yet to be accounted for: the - // screen is not showing the effect of them, so they can - // only confuse the user - mXScroll = mYScroll = 0; - - return true; - } - - public void onScaleEnd(ScaleGestureDetector detector) - { - // When a pinch-scale is done, we want to get n-across - // to fit properly. - - // get current viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - - // if we're at one column and wider than the viewport, - // leave it alone. - if (mLastLayoutColumns==0 && mPageCollectionWidth>=viewport.width()) - { - mScaling = false; - return; - } - - // ratio of the viewport width to layout width - float ratio = ((float)(viewport.width()))/((float)(mPageCollectionWidth)); - - // set a new scale factor - mScale *= ratio; - scaleChildren(); - - // scroll so the left edged is flush to the viewport. - mXScroll +=getScrollX(); - - requestLayout(); - - mScaling = false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - // do something when user interaction begins - } - - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { - // do something when user interaction ends - triggerRender(); - } - - mScaleGestureDetector.onTouchEvent(event); - mGestureDetector.onTouchEvent(event); - - return true; - } - - protected int getPageCount() - { - return getAdapter().getCount(); - } - - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - - super.onLayout(changed, left, top, right, bottom); - - if (!mStarted) - return; - - // not if there are no pages - if (getPageCount()==0) - return; - - int numDocPages = getPageCount(); - - // not if we've been finished - if (finished()) - return; - - // perform any pending scrolling - scrollBy(-mXScroll, -mYScroll); - mXScroll = mYScroll = 0; - - // get current viewport - mViewportOrigin.set(getScrollX(), getScrollY()); - getGlobalVisibleRect(mViewport); - mViewport.offsetTo(mViewportOrigin.x, mViewportOrigin.y); - - // find the widest child - int maxw = 0; - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - - int childWidth = cv.getCalculatedWidth(); - if (childWidth>maxw) - maxw = childWidth; - } - - // how many columns? - double dcol = (double)(mViewport.width()+GAP)/(double)(maxw+GAP); - int columns = (int) dcol; - - // lay them out - int mostVisibleChildHeight = -1; - int childTop = 0; - mPageCollectionHeight = 0; - mPageCollectionWidth = 0; - int column = 0; - mBlockRect.setEmpty(); - - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - int childWidth = cv.getCalculatedWidth(); - int childHeight = cv.getCalculatedHeight(); - int childLeft = column * (maxw + GAP); - int childRight = childLeft + childWidth; - int childBottom = childTop + childHeight; - mChildRect.set(childLeft, childTop, childRight, childBottom); - - // stash the rect in the page view for later use. - cv.setChildRect(mChildRect); - - // at each layout, we remember the entire width and height of the laid-out - // pages. This is used in applying constraints to scrolling amounts. - if (childBottom> mPageCollectionHeight) - mPageCollectionHeight = childBottom; - if (childRight>mPageCollectionWidth) - mPageCollectionWidth = childRight; - - if (mBlockRect.isEmpty()) - mBlockRect.set(mChildRect); - else - mBlockRect.union(mChildRect); - - if (mChildRect.intersect(mViewport) && i<numDocPages) - { - // visible, so include in layout - if (cv.getParent()==null) - addChildToLayout(cv); - cv.layout(childLeft, childTop, childRight, childBottom); - cv.invalidate(); - } - else - { - // not visible, so remove from layout - removeViewInLayout(cv); - } - - column++; - if (column >= columns) { - column = 0; - childTop += childHeight; - childTop += GAP; - } - } - - // if the number of columns has changed, do some scrolling to adjust - if (mScaling && columns>=1 && mLastLayoutColumns>=1 && mLastLayoutColumns!=columns) - { - // x - center in the viewport - int dx = mBlockRect.centerX() - mViewport.centerX(); - scrollBy(dx,0); - - // y - attempt to keep what's in the center of the viewport in view. - int oldy = mViewport.centerY() - mLastBlockRect.top; - int newy = (int)((float)oldy*mBlockRect.height()/mLastBlockRect.height()); - scrollBy(0,newy-oldy); - } - mLastLayoutColumns = columns; - mLastBlockRect.set(mBlockRect); - - // see if we're handling a start page - handleStartPage(); - - triggerRender(); - } - - // start page, get and set. - private int mStartPage = 0; - public void setStartPage(int page) { - mStartPage = page; - } - protected int getStartPage() {return mStartPage;} - - // handle start page - public void handleStartPage() - { - // if we've been given a start page, go there. - final int start = getStartPage(); - if (start>0) - { - setStartPage(0); // but just once - - // post all of this so that we get an additional layout request - final Handler handler = new Handler(); - handler.post(new Runnable() { - @Override - public void run() { - DocPageView cv = (DocPageView)getOrCreateChild(start-1); - Rect r = cv.getChildRect(); - scrollBy(0,r.top); - requestLayout(); - } - }); - } - } - - // override the view's scrollBy() function so we can - // take the opportunity to apply some constraints - - @Override - public void scrollBy(int dx, int dy) - { - Point p = constrainScrollBy(dx, dy); - super.scrollBy(p.x, p.y); - } - - // apply contraints to every scroll request. - - protected Point constrainScrollBy(int dx, int dy) - { - int vph; - int vpw; - { - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - vph = viewport.height(); - vpw = viewport.width(); - } - int sx = getScrollX(); - int sy = getScrollY(); - - if (mPageCollectionWidth <= vpw) - { - // not too far to the right - if (mPageCollectionWidth-sx-dx > vpw) - dx = 0; - - // not too far to the left - if (sx+dx>0) - dx = -sx; - } - else - { - // not too far to the right - if (mPageCollectionWidth < sx+vpw+dx) - dx = 0; - - // not too far to the left - if (sx+dx < 0) - dx = -sx; - } - - if (mPageCollectionHeight <= vph) - { - // not too far down - if (mPageCollectionHeight-sy-dy > vph) - dy = 0; - - // not too far up - if (sy+dy>0) - dy = -sy; - } - else - { - // not too far down - if (sy+dy < 0) - dy = -sy; - - // not too far up. - if (mPageCollectionHeight+2*vph/3 < sy+vph+dy) - dy = 0; - } - - return new Point(dx, dy); - } - - @Override - public Adapter getAdapter() { - return mAdapter; - } - - @Override - public View getSelectedView() { - return null; - } - - @Override - public void setAdapter(Adapter adapter) { - mAdapter = (PageAdapter)adapter; - requestLayout(); - } - - @Override - public void setSelection(int arg0) { - throw new UnsupportedOperationException("setSelection is not supported"); - } - - private View getCached() { - return null; - } - - protected View getOrCreateChild(int i) { - View v = mChildViews.get(i); - if (v == null) { - v = getViewFromAdapter(i); - mChildViews.append(i, v); // Record the view against it's adapter index - onScaleChild(v, mScale); - } - - return v; - } - - protected View getViewFromAdapter(int index) - { - return getAdapter().getView(index, getCached(), this); - } - - private void addChildToLayout(View v) { - LayoutParams params = v.getLayoutParams(); - if (params == null) { - params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - } - addViewInLayout(v, 0, params, true); - } - - private void triggerRender() - { - // Note that a render is needed - renderRequested = true; - - // If the previous render has completed, start a new one. Otherwise - // a new one will start as soon as the previous one completes. - if (renderCount == 0) - renderPages(); - } - - private void renderPages() - { - renderRequested = false; - - if (mFinished) - return; - - if (bitmaps == null) - return; - - // Rotate to the next bitmap - bitmapIndex++; - if (bitmapIndex>=bitmaps.length) - bitmapIndex = 0; - - // iterate through the children - for (int i = 0; i < getPageCount(); i++) { - - if (mFinished) - return; - - final DocPageView cv = (DocPageView) getOrCreateChild(i); - if (cv.getParent()!=null && cv.isReallyVisible()) { - // Count up as we kick off rendering of each visible page - renderCount++; - cv.render(bitmaps[bitmapIndex], new RenderListener() { - @Override - public void progress(int error) { - - if (error==0) - cv.invalidate(); - - // Count down as they complete - renderCount--; - - if (renderCount==0) { - if (renderRequested) { - // If this phase of rendering has completed and another has - // been requested, start it now - renderPages(); - } - } - } - }); - } - } - } - - @Override - public void run() - { - if (!mScroller.isFinished()) - { - mScroller.computeScrollOffset(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - mXScroll += x - mScrollerLastX; - mYScroll += y - mScrollerLastY; - mScrollerLastX = x; - mScrollerLastY = y; - - // limit the amount of repeated layouts. - long tNow = System.currentTimeMillis(); - long diff = tNow - mFlingStartTime; - if (diff>FLING_THROTTLE_TIME) - { - requestLayout(); - mFlingStartTime = tNow; - } - - mStepper.prod(); - } - else - { - // one more - long tNow = System.currentTimeMillis(); - if (tNow != mFlingStartTime) - requestLayout(); - } - } - - public void finish() - { - // we're done with this view. - mFinished = true; - - // first, hide and remove all the children - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - cv.setVisibility(GONE); - removeViewInLayout(cv); - cv.finish(); - } - } - - public boolean finished() {return mFinished;} - - protected void smoothScrollBy(int dx, int dy) - { - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, dx, dy, 400); - mStepper.prod(); - } - - public void scrollToPage(int pageNumber) - { - // scroll to bring the page into view - - // get current viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - - // offset it based on current scroll position - Point viewportOrigin = new Point(); - viewportOrigin.set(getScrollX(), getScrollY()); - viewport.offsetTo(viewportOrigin.x, viewportOrigin.y); - - // get page rect from last layout - DocPageView cv = (DocPageView)getOrCreateChild(pageNumber); - Rect childRect = cv.getChildRect(); - - // scroll - if ((childRect.height()) > viewport.height()) - { - // put the top of the page at the top and the left at 0 - smoothScrollBy(getScrollX(),getScrollY()-childRect.top); - } - else - { - // if the whole page is not visible, move the center of the page at the center - if (childRect.top < viewport.top || childRect.bottom > viewport.bottom) - { - if (childRect.top==0) - smoothScrollBy(0, getScrollY()); - else - smoothScrollBy(0, getScrollY() + viewport.height() / 2 - (childRect.bottom + childRect.top) / 2); - } - } - } - - private Point viewToScreen(Point p) - { - Point newPoint = new Point(p); - - Rect r = new Rect(); - this.getGlobalVisibleRect(r); - - newPoint.offset(r.left, r.top); - - return newPoint; - } - - public void scrollBoxIntoView (int pageNum, RectF box) - { - // get our viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - viewport.offset(0,-viewport.top); - - // get the location of the box's lower left corner, - // relative to the viewport - DocPageView cv = (DocPageView)getOrCreateChild(pageNum); - Point point = cv.pageToView((int)box.left,(int)box.bottom); - Rect childRect = cv.getChildRect(); - point.y += childRect.top; - point.y -= getScrollY(); - - // if the point is outside the viewport, scroll so it is. - if (point.y<viewport.top || point.y>viewport.bottom) - { - int diff = (viewport.top + viewport.bottom)/2 - point.y; - smoothScrollBy(0,diff); - } - } + private static final int GAP = 20; + + private static final float MIN_SCALE = .15f; + private static final float MAX_SCALE = 5.0f; + + private PageAdapter mAdapter; + private boolean mFinished = false; + + private final SparseArray<View> mChildViews = new SparseArray<View>(3); + + private boolean mScaling; // Whether the user is currently pinch zooming + private float mScale = 1.0f; + private int mXScroll; // Scroll amounts recorded from events. + private int mYScroll; // and then accounted for in onLayout + + private GestureDetector mGestureDetector; + private ScaleGestureDetector mScaleGestureDetector; + + // bitmaps for rendering + // these are created by the activity and set using setBitmaps() + private final static double OVERSIZE_FACTOR = 1.1; + private final Bitmap[] bitmaps = {null,null}; + + private int bitmapIndex = 0; + private boolean renderRequested = false; + private int renderCount = 0; + + // used during layout + private final Rect mChildRect = new Rect(); + private final Rect mViewport = new Rect(); + private final Point mViewportOrigin = new Point(); + private final Rect mBlockRect = new Rect(); + private final Rect mLastBlockRect = new Rect(); + private int mLastLayoutColumns = 1; + protected int mPageCollectionHeight; + private int mPageCollectionWidth; + + // for flinging + private static final int MOVING_DIAGONALLY = 0; + private static final int MOVING_LEFT = 1; + private static final int MOVING_RIGHT = 2; + private static final int MOVING_UP = 3; + private static final int MOVING_DOWN = 4; + + private static final float MIN_FLING_VELOCITY = 1500.0f; + private static final long FLING_THROTTLE_TIME = 20; + + private Scroller mScroller; + private Stepper mStepper; + private int mScrollerLastX; + private int mScrollerLastY; + private long mFlingStartTime; + + // for single- and double-tapping + private long mLastTapTime = 0; + private float lastTapX; + private float lastTapY; + private int mTapStatus = 0; + + private static final int DOUBLE_TAP_TIME = 300; + private static final int SHOW_KEYBOARD_TIME = 500; + + // the document. + private Document mDoc; + + private boolean mStarted = false; + + public DocView(Context context) { + super(context); + initialize(context); + } + + public DocView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(context); + } + + public DocView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(context); + } + + protected Context mContext = null; + + protected void initialize(Context context) + { + mContext = context; + mGestureDetector = new GestureDetector(context, this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + mStepper = new Stepper(this, this); + + // create bitmaps + makeBitmaps(); + } + + private void makeBitmaps() + { + // get current screen size + WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + int screenW = metrics.widthPixels; + int screenH = metrics.heightPixels; + + // make two bitmaps. + // make them large enough for both screen orientations, so we don't have to + // change them when the orientation changes. + + int w = (int)(screenW*OVERSIZE_FACTOR); + int h = (int)(screenH*OVERSIZE_FACTOR); + int size = Math.max(w,h); + for (int i=0;i<bitmaps.length;i++) + bitmaps[i] = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + } + + public void start(final String path) + { + // wait for the layout to finish + ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + + mAdapter = new PageAdapter(mContext); + mAdapter.setWidth(getWidth()); + mDoc = new Document(path); + mAdapter.setDocument(mDoc); + mScale = 1.0f; + mStarted = true; + triggerRender(); + } + }); + } + + private void onScaleChild(View v, Float scale) + { + ((DocPageView)v).setNewScale(scale); + } + + public void onOrientationChange() + { + triggerRender(); + } + + private void onSizeChange(float factor) + { + mScale *= factor; + scaleChildren(); + requestLayout(); + } + + public boolean onDown(MotionEvent arg0) { + mScroller.forceFinished(true); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + + // not while we're scaling + if (mScaling) + return true; + + // not while a previous fling is underway + if (!mScroller.isFinished()) + return true; + + // must be really flinging + float vel = Math.max(Math.abs(velocityX),Math.abs(velocityY)); + if (vel<MIN_FLING_VELOCITY) + return false; + + // what direction? + int direction = directionOfTravel(velocityX,velocityY); + + mFlingStartTime = System.currentTimeMillis(); + + switch (direction) + { + case MOVING_DOWN: + smoothScrollBy(0, getHeight()/2); + break; + + case MOVING_UP: + smoothScrollBy(0, -getHeight()/2); + break; + + default: + break; + } + + return true; + } + + private static int directionOfTravel(float vx, float vy) { + if (Math.abs(vx) > 2 * Math.abs(vy)) + return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; + else if (Math.abs(vy) > 2 * Math.abs(vx)) + return (vy > 0) ? MOVING_DOWN : MOVING_UP; + else + return MOVING_DIAGONALLY; + } + + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + + // not if we're scaling + if (mScaling) + return true; + + // not if a previous fling is underway + if (!mScroller.isFinished()) + return true; + + // accumulate scrolling amount. + mXScroll -= distanceX; + mYScroll -= distanceY; + + requestLayout(); + + return true; + } + + public void onShowPress(MotionEvent e) { + } + + protected DocPageView findPageViewContainingPoint(int x, int y, boolean includeMargin) + { + for (int i = 0; i < getChildCount(); i++) + { + // get the rect for the page + View child = getChildAt(i); + Rect childRect = new Rect(); + child.getGlobalVisibleRect(childRect); + + // add in the margin + if (includeMargin) + { + childRect.left -= GAP/2; + childRect.right += GAP/2; + childRect.top -= GAP/2; + childRect.bottom += GAP/2; + } + + // see if the rect contains the point + if (childRect.contains(x,y)) + return (DocPageView)child; + } + + return null; + } + + protected Point eventToScreen(float fx, float fy) + { + int x = Math.round(fx); + int y = Math.round(fy); + Rect docRect = new Rect(); + getGlobalVisibleRect(docRect); + x += docRect.left; + y += docRect.top; + + return new Point(x,y); + } + + protected void doSingleTap(float fx, float fy) + { + // find the page view that was tapped. + Point p = eventToScreen(fx,fy); + final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); + if (dpv==null) + return; + + // see if the age wants to handle the single tap + boolean handled = dpv.onSingleTap(p.x,p.y); + + // if not, ... + if (!handled) + { + // schedule a task in the near future to check if we're still a single-tap. + final Handler handler = new Handler(); + final Point tappedPoint = p; + handler.postDelayed(new Runnable() + { + @Override + public void run() + { + if (mTapStatus==1) + { + // still single + } + else + { + // double + } + mTapStatus = 0; + } + }, SHOW_KEYBOARD_TIME); + } + } + + protected void doDoubleTap(float fx, float fy) + { + Point p = eventToScreen(fx,fy); + DocPageView v = findPageViewContainingPoint(p.x, p.y, false); + if (v != null) { + v.onDoubleTap(p.x,p.y); + } + } + + public boolean onSingleTapUp(final MotionEvent e) + { + long now = System.currentTimeMillis(); + if (mLastTapTime!=0 && ((now-mLastTapTime)<DOUBLE_TAP_TIME)) + { + mTapStatus = 2; + doDoubleTap(lastTapX,lastTapY); + mLastTapTime = 0; + } + else + { + mLastTapTime = now; + lastTapX = e.getX(); + lastTapY = e.getY(); + doSingleTap(lastTapX, lastTapY); + mTapStatus = 1; + } + + return false; + } + + private void scaleChildren() + { + // scale children + for (int i=0; i<getPageCount(); i++) + { + DocPageView cv = (DocPageView)getOrCreateChild(i); + cv.setNewScale(mScale); + } + } + + public boolean onScale(ScaleGestureDetector detector) + { + // new scale factor + float previousScale = mScale; + mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); + + // did we really scale? + if (mScale == previousScale) + return true; + + // scale children + scaleChildren(); + + // maintain focus while scaling + float currentFocusX = detector.getFocusX(); + float currentFocusY = detector.getFocusY(); + int viewFocusX = (int)currentFocusX + getScrollX(); + int viewFocusY = (int)currentFocusY + getScrollY(); + mXScroll += viewFocusX - viewFocusX * detector.getScaleFactor(); + mYScroll += viewFocusY - viewFocusY * detector.getScaleFactor(); + + requestLayout(); + + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + + mScaling = true; + + // Ignore any scroll amounts yet to be accounted for: the + // screen is not showing the effect of them, so they can + // only confuse the user + mXScroll = mYScroll = 0; + + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) + { + // When a pinch-scale is done, we want to get n-across + // to fit properly. + + // get current viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + + // if we're at one column and wider than the viewport, + // leave it alone. + if (mLastLayoutColumns==0 && mPageCollectionWidth>=viewport.width()) + { + mScaling = false; + return; + } + + // ratio of the viewport width to layout width + float ratio = ((float)(viewport.width()))/((float)(mPageCollectionWidth)); + + // set a new scale factor + mScale *= ratio; + scaleChildren(); + + // scroll so the left edged is flush to the viewport. + mXScroll +=getScrollX(); + + requestLayout(); + + mScaling = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + // do something when user interaction begins + } + + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { + // do something when user interaction ends + triggerRender(); + } + + mScaleGestureDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + + return true; + } + + protected int getPageCount() + { + return getAdapter().getCount(); + } + + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + + super.onLayout(changed, left, top, right, bottom); + + if (!mStarted) + return; + + // not if there are no pages + if (getPageCount()==0) + return; + + int numDocPages = getPageCount(); + + // not if we've been finished + if (finished()) + return; + + // perform any pending scrolling + scrollBy(-mXScroll, -mYScroll); + mXScroll = mYScroll = 0; + + // get current viewport + mViewportOrigin.set(getScrollX(), getScrollY()); + getGlobalVisibleRect(mViewport); + mViewport.offsetTo(mViewportOrigin.x, mViewportOrigin.y); + + // find the widest child + int maxw = 0; + for (int i=0; i<getPageCount(); i++) + { + DocPageView cv = (DocPageView)getOrCreateChild(i); + + int childWidth = cv.getCalculatedWidth(); + if (childWidth>maxw) + maxw = childWidth; + } + + // how many columns? + double dcol = (double)(mViewport.width()+GAP)/(double)(maxw+GAP); + int columns = (int) dcol; + + // lay them out + int mostVisibleChildHeight = -1; + int childTop = 0; + mPageCollectionHeight = 0; + mPageCollectionWidth = 0; + int column = 0; + mBlockRect.setEmpty(); + + for (int i=0; i<getPageCount(); i++) + { + DocPageView cv = (DocPageView)getOrCreateChild(i); + int childWidth = cv.getCalculatedWidth(); + int childHeight = cv.getCalculatedHeight(); + int childLeft = column * (maxw + GAP); + int childRight = childLeft + childWidth; + int childBottom = childTop + childHeight; + mChildRect.set(childLeft, childTop, childRight, childBottom); + + // stash the rect in the page view for later use. + cv.setChildRect(mChildRect); + + // at each layout, we remember the entire width and height of the laid-out + // pages. This is used in applying constraints to scrolling amounts. + if (childBottom> mPageCollectionHeight) + mPageCollectionHeight = childBottom; + if (childRight>mPageCollectionWidth) + mPageCollectionWidth = childRight; + + if (mBlockRect.isEmpty()) + mBlockRect.set(mChildRect); + else + mBlockRect.union(mChildRect); + + if (mChildRect.intersect(mViewport) && i<numDocPages) + { + // visible, so include in layout + if (cv.getParent()==null) + addChildToLayout(cv); + cv.layout(childLeft, childTop, childRight, childBottom); + cv.invalidate(); + } + else + { + // not visible, so remove from layout + removeViewInLayout(cv); + } + + column++; + if (column >= columns) { + column = 0; + childTop += childHeight; + childTop += GAP; + } + } + + // if the number of columns has changed, do some scrolling to adjust + if (mScaling && columns>=1 && mLastLayoutColumns>=1 && mLastLayoutColumns!=columns) + { + // x - center in the viewport + int dx = mBlockRect.centerX() - mViewport.centerX(); + scrollBy(dx,0); + + // y - attempt to keep what's in the center of the viewport in view. + int oldy = mViewport.centerY() - mLastBlockRect.top; + int newy = (int)((float)oldy*mBlockRect.height()/mLastBlockRect.height()); + scrollBy(0,newy-oldy); + } + mLastLayoutColumns = columns; + mLastBlockRect.set(mBlockRect); + + // see if we're handling a start page + handleStartPage(); + + triggerRender(); + } + + // start page, get and set. + private int mStartPage = 0; + public void setStartPage(int page) { + mStartPage = page; + } + protected int getStartPage() {return mStartPage;} + + // handle start page + public void handleStartPage() + { + // if we've been given a start page, go there. + final int start = getStartPage(); + if (start>0) + { + setStartPage(0); // but just once + + // post all of this so that we get an additional layout request + final Handler handler = new Handler(); + handler.post(new Runnable() { + @Override + public void run() { + DocPageView cv = (DocPageView)getOrCreateChild(start-1); + Rect r = cv.getChildRect(); + scrollBy(0,r.top); + requestLayout(); + } + }); + } + } + + // override the view's scrollBy() function so we can + // take the opportunity to apply some constraints + + @Override + public void scrollBy(int dx, int dy) + { + Point p = constrainScrollBy(dx, dy); + super.scrollBy(p.x, p.y); + } + + // apply contraints to every scroll request. + + protected Point constrainScrollBy(int dx, int dy) + { + int vph; + int vpw; + { + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + vph = viewport.height(); + vpw = viewport.width(); + } + int sx = getScrollX(); + int sy = getScrollY(); + + if (mPageCollectionWidth <= vpw) + { + // not too far to the right + if (mPageCollectionWidth-sx-dx > vpw) + dx = 0; + + // not too far to the left + if (sx+dx>0) + dx = -sx; + } + else + { + // not too far to the right + if (mPageCollectionWidth < sx+vpw+dx) + dx = 0; + + // not too far to the left + if (sx+dx < 0) + dx = -sx; + } + + if (mPageCollectionHeight <= vph) + { + // not too far down + if (mPageCollectionHeight-sy-dy > vph) + dy = 0; + + // not too far up + if (sy+dy>0) + dy = -sy; + } + else + { + // not too far down + if (sy+dy < 0) + dy = -sy; + + // not too far up. + if (mPageCollectionHeight+2*vph/3 < sy+vph+dy) + dy = 0; + } + + return new Point(dx, dy); + } + + @Override + public Adapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + return null; + } + + @Override + public void setAdapter(Adapter adapter) { + mAdapter = (PageAdapter)adapter; + requestLayout(); + } + + @Override + public void setSelection(int arg0) { + throw new UnsupportedOperationException("setSelection is not supported"); + } + + private View getCached() { + return null; + } + + protected View getOrCreateChild(int i) { + View v = mChildViews.get(i); + if (v == null) { + v = getViewFromAdapter(i); + mChildViews.append(i, v); // Record the view against it's adapter index + onScaleChild(v, mScale); + } + + return v; + } + + protected View getViewFromAdapter(int index) + { + return getAdapter().getView(index, getCached(), this); + } + + private void addChildToLayout(View v) { + LayoutParams params = v.getLayoutParams(); + if (params == null) { + params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + addViewInLayout(v, 0, params, true); + } + + private void triggerRender() + { + // Note that a render is needed + renderRequested = true; + + // If the previous render has completed, start a new one. Otherwise + // a new one will start as soon as the previous one completes. + if (renderCount == 0) + renderPages(); + } + + private void renderPages() + { + renderRequested = false; + + if (mFinished) + return; + + if (bitmaps == null) + return; + + // Rotate to the next bitmap + bitmapIndex++; + if (bitmapIndex>=bitmaps.length) + bitmapIndex = 0; + + // iterate through the children + for (int i = 0; i < getPageCount(); i++) { + + if (mFinished) + return; + + final DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.getParent()!=null && cv.isReallyVisible()) { + // Count up as we kick off rendering of each visible page + renderCount++; + cv.render(bitmaps[bitmapIndex], new RenderListener() { + @Override + public void progress(int error) { + + if (error==0) + cv.invalidate(); + + // Count down as they complete + renderCount--; + + if (renderCount==0) { + if (renderRequested) { + // If this phase of rendering has completed and another has + // been requested, start it now + renderPages(); + } + } + } + }); + } + } + } + + @Override + public void run() + { + if (!mScroller.isFinished()) + { + mScroller.computeScrollOffset(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + mXScroll += x - mScrollerLastX; + mYScroll += y - mScrollerLastY; + mScrollerLastX = x; + mScrollerLastY = y; + + // limit the amount of repeated layouts. + long tNow = System.currentTimeMillis(); + long diff = tNow - mFlingStartTime; + if (diff>FLING_THROTTLE_TIME) + { + requestLayout(); + mFlingStartTime = tNow; + } + + mStepper.prod(); + } + else + { + // one more + long tNow = System.currentTimeMillis(); + if (tNow != mFlingStartTime) + requestLayout(); + } + } + + public void finish() + { + // we're done with this view. + mFinished = true; + + // first, hide and remove all the children + for (int i=0; i<getPageCount(); i++) + { + DocPageView cv = (DocPageView)getOrCreateChild(i); + cv.setVisibility(GONE); + removeViewInLayout(cv); + cv.finish(); + } + } + + public boolean finished() {return mFinished;} + + protected void smoothScrollBy(int dx, int dy) + { + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, dx, dy, 400); + mStepper.prod(); + } + + public void scrollToPage(int pageNumber) + { + // scroll to bring the page into view + + // get current viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + + // offset it based on current scroll position + Point viewportOrigin = new Point(); + viewportOrigin.set(getScrollX(), getScrollY()); + viewport.offsetTo(viewportOrigin.x, viewportOrigin.y); + + // get page rect from last layout + DocPageView cv = (DocPageView)getOrCreateChild(pageNumber); + Rect childRect = cv.getChildRect(); + + // scroll + if ((childRect.height()) > viewport.height()) + { + // put the top of the page at the top and the left at 0 + smoothScrollBy(getScrollX(),getScrollY()-childRect.top); + } + else + { + // if the whole page is not visible, move the center of the page at the center + if (childRect.top < viewport.top || childRect.bottom > viewport.bottom) + { + if (childRect.top==0) + smoothScrollBy(0, getScrollY()); + else + smoothScrollBy(0, getScrollY() + viewport.height() / 2 - (childRect.bottom + childRect.top) / 2); + } + } + } + + private Point viewToScreen(Point p) + { + Point newPoint = new Point(p); + + Rect r = new Rect(); + this.getGlobalVisibleRect(r); + + newPoint.offset(r.left, r.top); + + return newPoint; + } + + public void scrollBoxIntoView (int pageNum, RectF box) + { + // get our viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + viewport.offset(0,-viewport.top); + + // get the location of the box's lower left corner, + // relative to the viewport + DocPageView cv = (DocPageView)getOrCreateChild(pageNum); + Point point = cv.pageToView((int)box.left,(int)box.bottom); + Rect childRect = cv.getChildRect(); + point.y += childRect.top; + point.y -= getScrollY(); + + // if the point is outside the viewport, scroll so it is. + if (point.y<viewport.top || point.y>viewport.bottom) + { + int diff = (viewport.top + viewport.bottom)/2 - point.y; + smoothScrollBy(0,diff); + } + } } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java index 46a93068..0a1d8c05 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java @@ -10,52 +10,52 @@ import com.artifex.mupdf.fitz.Document; public class PageAdapter extends BaseAdapter { - private final Context mContext; - private Document mDoc; - - public PageAdapter(Context c) { - mContext = c; - } - - public void setDocument(Document doc) { - mDoc = doc; - } - private int mWidth; - public void setWidth(int w) {mWidth=w;} - - @Override - public int getCount() { - return mDoc.countPages(); - } - - public Object getItem(int position) { - return null; // not used - } - - public long getItemId(int position) { - return 0; // not used - } - - public View getView(final int position, View convertView, ViewGroup parent) - { - // make or reuse a view - DocPageView pageView; - - final Activity activity = (Activity)mContext; - if (convertView == null) - { - // make a new one - pageView = new DocPageView(activity, mDoc); - } - else - { - // reuse an existing one - pageView = (DocPageView) convertView; - } - - // set up the page - pageView.setupPage(position, mWidth, 1); - - return pageView; - } + private final Context mContext; + private Document mDoc; + + public PageAdapter(Context c) { + mContext = c; + } + + public void setDocument(Document doc) { + mDoc = doc; + } + private int mWidth; + public void setWidth(int w) {mWidth=w;} + + @Override + public int getCount() { + return mDoc.countPages(); + } + + public Object getItem(int position) { + return null; // not used + } + + public long getItemId(int position) { + return 0; // not used + } + + public View getView(final int position, View convertView, ViewGroup parent) + { + // make or reuse a view + DocPageView pageView; + + final Activity activity = (Activity)mContext; + if (convertView == null) + { + // make a new one + pageView = new DocPageView(activity, mDoc); + } + else + { + // reuse an existing one + pageView = (DocPageView) convertView; + } + + // set up the page + pageView.setupPage(position, mWidth, 1); + + return pageView; + } } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java index 7030ef8d..37a3432b 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java @@ -2,5 +2,5 @@ package com.artifex.mupdf.android; public interface RenderListener { - void progress(int error); -}
\ No newline at end of file + void progress(int error); +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java index 8a6b29a5..9275cf99 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java @@ -5,38 +5,38 @@ import android.os.Build; import android.view.View; public class Stepper { - private final View mPoster; - private final Runnable mTask; - private boolean mPending; + private final View mPoster; + private final Runnable mTask; + private boolean mPending; - public Stepper(View v, Runnable r) { - mPoster = v; - mTask = r; - mPending = false; - } + public Stepper(View v, Runnable r) { + mPoster = v; + mTask = r; + mPending = false; + } - @SuppressLint("NewApi") - public void prod() { - if (!mPending) { - mPending = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mPoster.postOnAnimation(new Runnable() { - @Override - public void run() { - mPending = false; - mTask.run(); - } - }); - } else { - mPoster.post(new Runnable() { - @Override - public void run() { - mPending = false; - mTask.run(); - } - }); + @SuppressLint("NewApi") + public void prod() { + if (!mPending) { + mPending = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mPoster.postOnAnimation(new Runnable() { + @Override + public void run() { + mPending = false; + mTask.run(); + } + }); + } else { + mPoster.post(new Runnable() { + @Override + public void run() { + mPending = false; + mTask.run(); + } + }); - } - } - } + } + } + } } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java index 4f06ea41..c8549058 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java @@ -62,794 +62,794 @@ import java.util.Stack; * @param <E> the type of elements held in this collection */ public class ArrayDeque<E> extends AbstractCollection<E> - implements Deque<E>, Cloneable, java.io.Serializable + implements Deque<E>, Cloneable, java.io.Serializable { - /** - * The array in which the elements of the deque are stored. - * The capacity of the deque is the length of this array, which is - * always a power of two. The array is never allowed to become - * full, except transiently within an addX method where it is - * resized (see doubleCapacity) immediately upon becoming full, - * thus avoiding head and tail wrapping around to equal each - * other. We also guarantee that all array cells not holding - * deque elements are always null. - */ - private transient Object[] elements; - - /** - * The index of the element at the head of the deque (which is the - * element that would be removed by remove() or pop()); or an - * arbitrary number equal to tail if the deque is empty. - */ - private transient int head; - - /** - * The index at which the next element would be added to the tail - * of the deque (via addLast(E), add(E), or push(E)). - */ - private transient int tail; - - /** - * The minimum capacity that we'll use for a newly created deque. - * Must be a power of 2. - */ - private static final int MIN_INITIAL_CAPACITY = 8; - - // ****** Array allocation and resizing utilities ****** - - /** - * Allocate empty array to hold the given number of elements. - * - * @param numElements the number of elements to hold - */ - private void allocateElements(int numElements) { - int initialCapacity = MIN_INITIAL_CAPACITY; - // Find the best power of two to hold elements. - // Tests "<=" because arrays aren't kept full. - if (numElements >= initialCapacity) { - initialCapacity = numElements; - initialCapacity |= (initialCapacity >>> 1); - initialCapacity |= (initialCapacity >>> 2); - initialCapacity |= (initialCapacity >>> 4); - initialCapacity |= (initialCapacity >>> 8); - initialCapacity |= (initialCapacity >>> 16); - initialCapacity++; - - if (initialCapacity < 0) // Too many elements, must back off - initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements - } - elements = new Object[initialCapacity]; - } - - /** - * Double the capacity of this deque. Call only when full, i.e., - * when head and tail have wrapped around to become equal. - */ - private void doubleCapacity() { - // assert head == tail; - int p = head; - int n = elements.length; - int r = n - p; // number of elements to the right of p - int newCapacity = n << 1; - if (newCapacity < 0) - throw new IllegalStateException("Sorry, deque too big"); - Object[] a = new Object[newCapacity]; - System.arraycopy(elements, p, a, 0, r); - System.arraycopy(elements, 0, a, r, p); - elements = a; - head = 0; - tail = n; - } - - /** - * Copies the elements from our element array into the specified array, - * in order (from first to last element in the deque). It is assumed - * that the array is large enough to hold all elements in the deque. - * - * @return its argument - */ - private <T> T[] copyElements(T[] a) { - if (head < tail) { - System.arraycopy(elements, head, a, 0, size()); - } else if (head > tail) { - int headPortionLen = elements.length - head; - System.arraycopy(elements, head, a, 0, headPortionLen); - System.arraycopy(elements, 0, a, headPortionLen, tail); - } - return a; - } - - /** - * Constructs an empty array deque with an initial capacity - * sufficient to hold 16 elements. - */ - public ArrayDeque() { - elements = new Object[16]; - } - - /** - * Constructs an empty array deque with an initial capacity - * sufficient to hold the specified number of elements. - * - * @param numElements lower bound on initial capacity of the deque - */ - public ArrayDeque(int numElements) { - allocateElements(numElements); - } - - /** - * Constructs a deque containing the elements of the specified - * collection, in the order they are returned by the collection's - * iterator. (The first element returned by the collection's - * iterator becomes the first element, or <i>front</i> of the - * deque.) - * - * @param c the collection whose elements are to be placed into the deque - * @throws NullPointerException if the specified collection is null - */ - public ArrayDeque(Collection<? extends E> c) { - allocateElements(c.size()); - addAll(c); - } - - // The main insertion and extraction methods are addFirst, - // addLast, pollFirst, pollLast. The other methods are defined in - // terms of these. - - /** - * Inserts the specified element at the front of this deque. - * - * @param e the element to add - * @throws NullPointerException if the specified element is null - */ - public void addFirst(E e) { - if (e == null) - throw new NullPointerException("e == null"); - elements[head = (head - 1) & (elements.length - 1)] = e; - if (head == tail) - doubleCapacity(); - } - - /** - * Inserts the specified element at the end of this deque. - * - * <p>This method is equivalent to {@link #add}. - * - * @param e the element to add - * @throws NullPointerException if the specified element is null - */ - public void addLast(E e) { - if (e == null) - throw new NullPointerException("e == null"); - elements[tail] = e; - if ( (tail = (tail + 1) & (elements.length - 1)) == head) - doubleCapacity(); - } - - /** - * Inserts the specified element at the front of this deque. - * - * @param e the element to add - * @return <tt>true</tt> (as specified by {@link Deque#offerFirst}) - * @throws NullPointerException if the specified element is null - */ - public boolean offerFirst(E e) { - addFirst(e); - return true; - } - - /** - * Inserts the specified element at the end of this deque. - * - * @param e the element to add - * @return <tt>true</tt> (as specified by {@link Deque#offerLast}) - * @throws NullPointerException if the specified element is null - */ - public boolean offerLast(E e) { - addLast(e); - return true; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E removeFirst() { - E x = pollFirst(); - if (x == null) - throw new NoSuchElementException(); - return x; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E removeLast() { - E x = pollLast(); - if (x == null) - throw new NoSuchElementException(); - return x; - } - - public E pollFirst() { - int h = head; - @SuppressWarnings("unchecked") E result = (E) elements[h]; - // Element is null if deque empty - if (result == null) - return null; - elements[h] = null; // Must null out slot - head = (h + 1) & (elements.length - 1); - return result; - } - - public E pollLast() { - int t = (tail - 1) & (elements.length - 1); - @SuppressWarnings("unchecked") E result = (E) elements[t]; - if (result == null) - return null; - elements[t] = null; - tail = t; - return result; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E getFirst() { - @SuppressWarnings("unchecked") E result = (E) elements[head]; - if (result == null) - throw new NoSuchElementException(); - return result; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E getLast() { - @SuppressWarnings("unchecked") - E result = (E) elements[(tail - 1) & (elements.length - 1)]; - if (result == null) - throw new NoSuchElementException(); - return result; - } - - public E peekFirst() { - @SuppressWarnings("unchecked") E result = (E) elements[head]; - // elements[head] is null if deque empty - return result; - } - - public E peekLast() { - @SuppressWarnings("unchecked") - E result = (E) elements[(tail - 1) & (elements.length - 1)]; - return result; - } - - /** - * Removes the first occurrence of the specified element in this - * deque (when traversing the deque from head to tail). - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element <tt>e</tt> such that - * <tt>o.equals(e)</tt> (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if the deque contained the specified element - */ - public boolean removeFirstOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; - } - i = (i + 1) & mask; - } - return false; - } - - /** - * Removes the last occurrence of the specified element in this - * deque (when traversing the deque from head to tail). - * If the deque does not contain the element, it is unchanged. - * More formally, removes the last element <tt>e</tt> such that - * <tt>o.equals(e)</tt> (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if the deque contained the specified element - */ - public boolean removeLastOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = (tail - 1) & mask; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; - } - i = (i - 1) & mask; - } - return false; - } - - // *** Queue methods *** - - /** - * Inserts the specified element at the end of this deque. - * - * <p>This method is equivalent to {@link #addLast}. - * - * @param e the element to add - * @return <tt>true</tt> (as specified by {@link Collection#add}) - * @throws NullPointerException if the specified element is null - */ - public boolean add(E e) { - addLast(e); - return true; - } - - /** - * Inserts the specified element at the end of this deque. - * - * <p>This method is equivalent to {@link #offerLast}. - * - * @param e the element to add - * @return <tt>true</tt> (as specified by {@link Queue#offer}) - * @throws NullPointerException if the specified element is null - */ - public boolean offer(E e) { - return offerLast(e); - } - - /** - * Retrieves and removes the head of the queue represented by this deque. - * - * This method differs from {@link #poll poll} only in that it throws an - * exception if this deque is empty. - * - * <p>This method is equivalent to {@link #removeFirst}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException {@inheritDoc} - */ - public E remove() { - return removeFirst(); - } - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque), or returns - * <tt>null</tt> if this deque is empty. - * - * <p>This method is equivalent to {@link #pollFirst}. - * - * @return the head of the queue represented by this deque, or - * <tt>null</tt> if this deque is empty - */ - public E poll() { - return pollFirst(); - } - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque. This method differs from {@link #peek peek} only in - * that it throws an exception if this deque is empty. - * - * <p>This method is equivalent to {@link #getFirst}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException {@inheritDoc} - */ - public E element() { - return getFirst(); - } - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque, or returns <tt>null</tt> if this deque is empty. - * - * <p>This method is equivalent to {@link #peekFirst}. - * - * @return the head of the queue represented by this deque, or - * <tt>null</tt> if this deque is empty - */ - public E peek() { - return peekFirst(); - } - - // *** Stack methods *** - - /** - * Pushes an element onto the stack represented by this deque. In other - * words, inserts the element at the front of this deque. - * - * <p>This method is equivalent to {@link #addFirst}. - * - * @param e the element to push - * @throws NullPointerException if the specified element is null - */ - public void push(E e) { - addFirst(e); - } - - /** - * Pops an element from the stack represented by this deque. In other - * words, removes and returns the first element of this deque. - * - * <p>This method is equivalent to {@link #removeFirst()}. - * - * @return the element at the front of this deque (which is the top - * of the stack represented by this deque) - * @throws NoSuchElementException {@inheritDoc} - */ - public E pop() { - return removeFirst(); - } - - private void checkInvariants() { - // assert elements[tail] == null; - // assert head == tail ? elements[head] == null : - // (elements[head] != null && - // elements[(tail - 1) & (elements.length - 1)] != null); - // assert elements[(head - 1) & (elements.length - 1)] == null; - } - - /** - * Removes the element at the specified position in the elements array, - * adjusting head and tail as necessary. This can result in motion of - * elements backwards or forwards in the array. - * - * <p>This method is called delete rather than remove to emphasize - * that its semantics differ from those of {@link List#remove(int)}. - * - * @return true if elements moved backwards - */ - private boolean delete(int i) { - //checkInvariants(); - final Object[] elements = this.elements; - final int mask = elements.length - 1; - final int h = head; - final int t = tail; - final int front = (i - h) & mask; - final int back = (t - i) & mask; - - // Invariant: head <= i < tail mod circularity - if (front >= ((t - h) & mask)) - throw new ConcurrentModificationException(); - - // Optimize for least element motion - if (front < back) { - if (h <= i) { - System.arraycopy(elements, h, elements, h + 1, front); - } else { // Wrap around - System.arraycopy(elements, 0, elements, 1, i); - elements[0] = elements[mask]; - System.arraycopy(elements, h, elements, h + 1, mask - h); - } - elements[h] = null; - head = (h + 1) & mask; - return false; - } else { - if (i < t) { // Copy the null tail as well - System.arraycopy(elements, i + 1, elements, i, back); - tail = t - 1; - } else { // Wrap around - System.arraycopy(elements, i + 1, elements, i, mask - i); - elements[mask] = elements[0]; - System.arraycopy(elements, 1, elements, 0, t); - tail = (t - 1) & mask; - } - return true; - } - } - - // *** Collection Methods *** - - /** - * Returns the number of elements in this deque. - * - * @return the number of elements in this deque - */ - public int size() { - return (tail - head) & (elements.length - 1); - } - - /** - * Returns <tt>true</tt> if this deque contains no elements. - * - * @return <tt>true</tt> if this deque contains no elements - */ - public boolean isEmpty() { - return head == tail; - } - - /** - * Returns an iterator over the elements in this deque. The elements - * will be ordered from first (head) to last (tail). This is the same - * order that elements would be dequeued (via successive calls to - * {@link #remove} or popped (via successive calls to {@link #pop}). - * - * @return an iterator over the elements in this deque - */ - public Iterator<E> iterator() { - return new DeqIterator(); - } - - public Iterator<E> descendingIterator() { - return new DescendingIterator(); - } - - private class DeqIterator implements Iterator<E> { - /** - * Index of element to be returned by subsequent call to next. - */ - private int cursor = head; - - /** - * Tail recorded at construction (also in remove), to stop - * iterator and also to check for comodification. - */ - private int fence = tail; - - /** - * Index of element returned by most recent call to next. - * Reset to -1 if element is deleted by a call to remove. - */ - private int lastRet = -1; - - public boolean hasNext() { - return cursor != fence; - } - - public E next() { - if (cursor == fence) - throw new NoSuchElementException(); - @SuppressWarnings("unchecked") E result = (E) elements[cursor]; - // This check doesn't catch all possible comodifications, - // but does catch the ones that corrupt traversal - if (tail != fence || result == null) - throw new ConcurrentModificationException(); - lastRet = cursor; - cursor = (cursor + 1) & (elements.length - 1); - return result; - } - - public void remove() { - if (lastRet < 0) - throw new IllegalStateException(); - if (delete(lastRet)) { // if left-shifted, undo increment in next() - cursor = (cursor - 1) & (elements.length - 1); - fence = tail; - } - lastRet = -1; - } - } - - private class DescendingIterator implements Iterator<E> { - /* - * This class is nearly a mirror-image of DeqIterator, using - * tail instead of head for initial cursor, and head instead of - * tail for fence. - */ - private int cursor = tail; - private int fence = head; - private int lastRet = -1; - - public boolean hasNext() { - return cursor != fence; - } - - public E next() { - if (cursor == fence) - throw new NoSuchElementException(); - cursor = (cursor - 1) & (elements.length - 1); - @SuppressWarnings("unchecked") E result = (E) elements[cursor]; - if (head != fence || result == null) - throw new ConcurrentModificationException(); - lastRet = cursor; - return result; - } - - public void remove() { - if (lastRet < 0) - throw new IllegalStateException(); - if (!delete(lastRet)) { - cursor = (cursor + 1) & (elements.length - 1); - fence = head; - } - lastRet = -1; - } - } - - /** - * Returns <tt>true</tt> if this deque contains the specified element. - * More formally, returns <tt>true</tt> if and only if this deque contains - * at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>. - * - * @param o object to be checked for containment in this deque - * @return <tt>true</tt> if this deque contains the specified element - */ - public boolean contains(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) - return true; - i = (i + 1) & mask; - } - return false; - } - - /** - * Removes a single instance of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element <tt>e</tt> such that - * <tt>o.equals(e)</tt> (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * <p>This method is equivalent to {@link #removeFirstOccurrence}. - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if this deque contained the specified element - */ - public boolean remove(Object o) { - return removeFirstOccurrence(o); - } - - /** - * Removes all of the elements from this deque. - * The deque will be empty after this call returns. - */ - public void clear() { - int h = head; - int t = tail; - if (h != t) { // clear all cells - head = tail = 0; - int i = h; - int mask = elements.length - 1; - do { - elements[i] = null; - i = (i + 1) & mask; - } while (i != t); - } - } - - /** - * Returns an array containing all of the elements in this deque - * in proper sequence (from first to last element). - * - * <p>The returned array will be "safe" in that no references to it are - * maintained by this deque. (In other words, this method must allocate - * a new array). The caller is thus free to modify the returned array. - * - * <p>This method acts as bridge between array-based and collection-based - * APIs. - * - * @return an array containing all of the elements in this deque - */ - public Object[] toArray() { - return copyElements(new Object[size()]); - } - - /** - * Returns an array containing all of the elements in this deque in - * proper sequence (from first to last element); the runtime type of the - * returned array is that of the specified array. If the deque fits in - * the specified array, it is returned therein. Otherwise, a new array - * is allocated with the runtime type of the specified array and the - * size of this deque. - * - * <p>If this deque fits in the specified array with room to spare - * (i.e., the array has more elements than this deque), the element in - * the array immediately following the end of the deque is set to - * <tt>null</tt>. - * - * <p>Like the {@link #toArray()} method, this method acts as bridge between - * array-based and collection-based APIs. Further, this method allows - * precise control over the runtime type of the output array, and may, - * under certain circumstances, be used to save allocation costs. - * - * <p>Suppose <tt>x</tt> is a deque known to contain only strings. - * The following code can be used to dump the deque into a newly - * allocated array of <tt>String</tt>: - * - * <pre> {@code String[] y = x.toArray(new String[0]);}</pre> - * - * Note that <tt>toArray(new Object[0])</tt> is identical in function to - * <tt>toArray()</tt>. - * - * @param a the array into which the elements of the deque are to - * be stored, if it is big enough; otherwise, a new array of the - * same runtime type is allocated for this purpose - * @return an array containing all of the elements in this deque - * @throws ArrayStoreException if the runtime type of the specified array - * is not a supertype of the runtime type of every element in - * this deque - * @throws NullPointerException if the specified array is null - */ - @SuppressWarnings("unchecked") - public <T> T[] toArray(T[] a) { - int size = size(); - if (a.length < size) - a = (T[])java.lang.reflect.Array.newInstance( - a.getClass().getComponentType(), size); - copyElements(a); - if (a.length > size) - a[size] = null; - return a; - } - - // *** Object methods *** - - /** - * Returns a copy of this deque. - * - * @return a copy of this deque - */ - public ArrayDeque<E> clone() { - try { - @SuppressWarnings("unchecked") - ArrayDeque<E> result = (ArrayDeque<E>) super.clone(); - result.elements = Arrays.copyOf(elements, elements.length); - return result; - - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - - /** - * Appease the serialization gods. - */ - private static final long serialVersionUID = 2340985798034038923L; - - /** - * Serialize this deque. - * - * @serialData The current size (<tt>int</tt>) of the deque, - * followed by all of its elements (each an object reference) in - * first-to-last order. - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - - // Write out size - s.writeInt(size()); - - // Write out elements in order. - int mask = elements.length - 1; - for (int i = head; i != tail; i = (i + 1) & mask) - s.writeObject(elements[i]); - } - - /** - * Deserialize this deque. - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - - // Read in size and allocate array - int size = s.readInt(); - allocateElements(size); - head = 0; - tail = size; - - // Read in all elements in the proper order. - for (int i = 0; i < size; i++) - elements[i] = s.readObject(); - } + /** + * The array in which the elements of the deque are stored. + * The capacity of the deque is the length of this array, which is + * always a power of two. The array is never allowed to become + * full, except transiently within an addX method where it is + * resized (see doubleCapacity) immediately upon becoming full, + * thus avoiding head and tail wrapping around to equal each + * other. We also guarantee that all array cells not holding + * deque elements are always null. + */ + private transient Object[] elements; + + /** + * The index of the element at the head of the deque (which is the + * element that would be removed by remove() or pop()); or an + * arbitrary number equal to tail if the deque is empty. + */ + private transient int head; + + /** + * The index at which the next element would be added to the tail + * of the deque (via addLast(E), add(E), or push(E)). + */ + private transient int tail; + + /** + * The minimum capacity that we'll use for a newly created deque. + * Must be a power of 2. + */ + private static final int MIN_INITIAL_CAPACITY = 8; + + // ****** Array allocation and resizing utilities ****** + + /** + * Allocate empty array to hold the given number of elements. + * + * @param numElements the number of elements to hold + */ + private void allocateElements(int numElements) { + int initialCapacity = MIN_INITIAL_CAPACITY; + // Find the best power of two to hold elements. + // Tests "<=" because arrays aren't kept full. + if (numElements >= initialCapacity) { + initialCapacity = numElements; + initialCapacity |= (initialCapacity >>> 1); + initialCapacity |= (initialCapacity >>> 2); + initialCapacity |= (initialCapacity >>> 4); + initialCapacity |= (initialCapacity >>> 8); + initialCapacity |= (initialCapacity >>> 16); + initialCapacity++; + + if (initialCapacity < 0) // Too many elements, must back off + initialCapacity >>>= 1; // Good luck allocating 2 ^ 30 elements + } + elements = new Object[initialCapacity]; + } + + /** + * Double the capacity of this deque. Call only when full, i.e., + * when head and tail have wrapped around to become equal. + */ + private void doubleCapacity() { + // assert head == tail; + int p = head; + int n = elements.length; + int r = n - p; // number of elements to the right of p + int newCapacity = n << 1; + if (newCapacity < 0) + throw new IllegalStateException("Sorry, deque too big"); + Object[] a = new Object[newCapacity]; + System.arraycopy(elements, p, a, 0, r); + System.arraycopy(elements, 0, a, r, p); + elements = a; + head = 0; + tail = n; + } + + /** + * Copies the elements from our element array into the specified array, + * in order (from first to last element in the deque). It is assumed + * that the array is large enough to hold all elements in the deque. + * + * @return its argument + */ + private <T> T[] copyElements(T[] a) { + if (head < tail) { + System.arraycopy(elements, head, a, 0, size()); + } else if (head > tail) { + int headPortionLen = elements.length - head; + System.arraycopy(elements, head, a, 0, headPortionLen); + System.arraycopy(elements, 0, a, headPortionLen, tail); + } + return a; + } + + /** + * Constructs an empty array deque with an initial capacity + * sufficient to hold 16 elements. + */ + public ArrayDeque() { + elements = new Object[16]; + } + + /** + * Constructs an empty array deque with an initial capacity + * sufficient to hold the specified number of elements. + * + * @param numElements lower bound on initial capacity of the deque + */ + public ArrayDeque(int numElements) { + allocateElements(numElements); + } + + /** + * Constructs a deque containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. (The first element returned by the collection's + * iterator becomes the first element, or <i>front</i> of the + * deque.) + * + * @param c the collection whose elements are to be placed into the deque + * @throws NullPointerException if the specified collection is null + */ + public ArrayDeque(Collection<? extends E> c) { + allocateElements(c.size()); + addAll(c); + } + + // The main insertion and extraction methods are addFirst, + // addLast, pollFirst, pollLast. The other methods are defined in + // terms of these. + + /** + * Inserts the specified element at the front of this deque. + * + * @param e the element to add + * @throws NullPointerException if the specified element is null + */ + public void addFirst(E e) { + if (e == null) + throw new NullPointerException("e == null"); + elements[head = (head - 1) & (elements.length - 1)] = e; + if (head == tail) + doubleCapacity(); + } + + /** + * Inserts the specified element at the end of this deque. + * + * <p>This method is equivalent to {@link #add}. + * + * @param e the element to add + * @throws NullPointerException if the specified element is null + */ + public void addLast(E e) { + if (e == null) + throw new NullPointerException("e == null"); + elements[tail] = e; + if ( (tail = (tail + 1) & (elements.length - 1)) == head) + doubleCapacity(); + } + + /** + * Inserts the specified element at the front of this deque. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Deque#offerFirst}) + * @throws NullPointerException if the specified element is null + */ + public boolean offerFirst(E e) { + addFirst(e); + return true; + } + + /** + * Inserts the specified element at the end of this deque. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Deque#offerLast}) + * @throws NullPointerException if the specified element is null + */ + public boolean offerLast(E e) { + addLast(e); + return true; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeFirst() { + E x = pollFirst(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeLast() { + E x = pollLast(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + public E pollFirst() { + int h = head; + @SuppressWarnings("unchecked") E result = (E) elements[h]; + // Element is null if deque empty + if (result == null) + return null; + elements[h] = null; // Must null out slot + head = (h + 1) & (elements.length - 1); + return result; + } + + public E pollLast() { + int t = (tail - 1) & (elements.length - 1); + @SuppressWarnings("unchecked") E result = (E) elements[t]; + if (result == null) + return null; + elements[t] = null; + tail = t; + return result; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getFirst() { + @SuppressWarnings("unchecked") E result = (E) elements[head]; + if (result == null) + throw new NoSuchElementException(); + return result; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getLast() { + @SuppressWarnings("unchecked") + E result = (E) elements[(tail - 1) & (elements.length - 1)]; + if (result == null) + throw new NoSuchElementException(); + return result; + } + + public E peekFirst() { + @SuppressWarnings("unchecked") E result = (E) elements[head]; + // elements[head] is null if deque empty + return result; + } + + public E peekLast() { + @SuppressWarnings("unchecked") + E result = (E) elements[(tail - 1) & (elements.length - 1)]; + return result; + } + + /** + * Removes the first occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if the deque contained the specified element + */ + public boolean removeFirstOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i + 1) & mask; + } + return false; + } + + /** + * Removes the last occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if the deque contained the specified element + */ + public boolean removeLastOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = (tail - 1) & mask; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i - 1) & mask; + } + return false; + } + + // *** Queue methods *** + + /** + * Inserts the specified element at the end of this deque. + * + * <p>This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws NullPointerException if the specified element is null + */ + public boolean add(E e) { + addLast(e); + return true; + } + + /** + * Inserts the specified element at the end of this deque. + * + * <p>This method is equivalent to {@link #offerLast}. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Queue#offer}) + * @throws NullPointerException if the specified element is null + */ + public boolean offer(E e) { + return offerLast(e); + } + + /** + * Retrieves and removes the head of the queue represented by this deque. + * + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #removeFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} + */ + public E remove() { + return removeFirst(); + } + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #pollFirst}. + * + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty + */ + public E poll() { + return pollFirst(); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque. This method differs from {@link #peek peek} only in + * that it throws an exception if this deque is empty. + * + * <p>This method is equivalent to {@link #getFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} + */ + public E element() { + return getFirst(); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque, or returns <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #peekFirst}. + * + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty + */ + public E peek() { + return peekFirst(); + } + + // *** Stack methods *** + + /** + * Pushes an element onto the stack represented by this deque. In other + * words, inserts the element at the front of this deque. + * + * <p>This method is equivalent to {@link #addFirst}. + * + * @param e the element to push + * @throws NullPointerException if the specified element is null + */ + public void push(E e) { + addFirst(e); + } + + /** + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. + * + * <p>This method is equivalent to {@link #removeFirst()}. + * + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException {@inheritDoc} + */ + public E pop() { + return removeFirst(); + } + + private void checkInvariants() { + // assert elements[tail] == null; + // assert head == tail ? elements[head] == null : + // (elements[head] != null && + // elements[(tail - 1) & (elements.length - 1)] != null); + // assert elements[(head - 1) & (elements.length - 1)] == null; + } + + /** + * Removes the element at the specified position in the elements array, + * adjusting head and tail as necessary. This can result in motion of + * elements backwards or forwards in the array. + * + * <p>This method is called delete rather than remove to emphasize + * that its semantics differ from those of {@link List#remove(int)}. + * + * @return true if elements moved backwards + */ + private boolean delete(int i) { + //checkInvariants(); + final Object[] elements = this.elements; + final int mask = elements.length - 1; + final int h = head; + final int t = tail; + final int front = (i - h) & mask; + final int back = (t - i) & mask; + + // Invariant: head <= i < tail mod circularity + if (front >= ((t - h) & mask)) + throw new ConcurrentModificationException(); + + // Optimize for least element motion + if (front < back) { + if (h <= i) { + System.arraycopy(elements, h, elements, h + 1, front); + } else { // Wrap around + System.arraycopy(elements, 0, elements, 1, i); + elements[0] = elements[mask]; + System.arraycopy(elements, h, elements, h + 1, mask - h); + } + elements[h] = null; + head = (h + 1) & mask; + return false; + } else { + if (i < t) { // Copy the null tail as well + System.arraycopy(elements, i + 1, elements, i, back); + tail = t - 1; + } else { // Wrap around + System.arraycopy(elements, i + 1, elements, i, mask - i); + elements[mask] = elements[0]; + System.arraycopy(elements, 1, elements, 0, t); + tail = (t - 1) & mask; + } + return true; + } + } + + // *** Collection Methods *** + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size() { + return (tail - head) & (elements.length - 1); + } + + /** + * Returns <tt>true</tt> if this deque contains no elements. + * + * @return <tt>true</tt> if this deque contains no elements + */ + public boolean isEmpty() { + return head == tail; + } + + /** + * Returns an iterator over the elements in this deque. The elements + * will be ordered from first (head) to last (tail). This is the same + * order that elements would be dequeued (via successive calls to + * {@link #remove} or popped (via successive calls to {@link #pop}). + * + * @return an iterator over the elements in this deque + */ + public Iterator<E> iterator() { + return new DeqIterator(); + } + + public Iterator<E> descendingIterator() { + return new DescendingIterator(); + } + + private class DeqIterator implements Iterator<E> { + /** + * Index of element to be returned by subsequent call to next. + */ + private int cursor = head; + + /** + * Tail recorded at construction (also in remove), to stop + * iterator and also to check for comodification. + */ + private int fence = tail; + + /** + * Index of element returned by most recent call to next. + * Reset to -1 if element is deleted by a call to remove. + */ + private int lastRet = -1; + + public boolean hasNext() { + return cursor != fence; + } + + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + @SuppressWarnings("unchecked") E result = (E) elements[cursor]; + // This check doesn't catch all possible comodifications, + // but does catch the ones that corrupt traversal + if (tail != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + cursor = (cursor + 1) & (elements.length - 1); + return result; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (delete(lastRet)) { // if left-shifted, undo increment in next() + cursor = (cursor - 1) & (elements.length - 1); + fence = tail; + } + lastRet = -1; + } + } + + private class DescendingIterator implements Iterator<E> { + /* + * This class is nearly a mirror-image of DeqIterator, using + * tail instead of head for initial cursor, and head instead of + * tail for fence. + */ + private int cursor = tail; + private int fence = head; + private int lastRet = -1; + + public boolean hasNext() { + return cursor != fence; + } + + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + cursor = (cursor - 1) & (elements.length - 1); + @SuppressWarnings("unchecked") E result = (E) elements[cursor]; + if (head != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + return result; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (!delete(lastRet)) { + cursor = (cursor + 1) & (elements.length - 1); + fence = head; + } + lastRet = -1; + } + } + + /** + * Returns <tt>true</tt> if this deque contains the specified element. + * More formally, returns <tt>true</tt> if and only if this deque contains + * at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>. + * + * @param o object to be checked for containment in this deque + * @return <tt>true</tt> if this deque contains the specified element + */ + public boolean contains(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) + return true; + i = (i + 1) & mask; + } + return false; + } + + /** + * Removes a single instance of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * <p>This method is equivalent to {@link #removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if this deque contained the specified element + */ + public boolean remove(Object o) { + return removeFirstOccurrence(o); + } + + /** + * Removes all of the elements from this deque. + * The deque will be empty after this call returns. + */ + public void clear() { + int h = head; + int t = tail; + if (h != t) { // clear all cells + head = tail = 0; + int i = h; + int mask = elements.length - 1; + do { + elements[i] = null; + i = (i + 1) & mask; + } while (i != t); + } + } + + /** + * Returns an array containing all of the elements in this deque + * in proper sequence (from first to last element). + * + * <p>The returned array will be "safe" in that no references to it are + * maintained by this deque. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + * <p>This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this deque + */ + public Object[] toArray() { + return copyElements(new Object[size()]); + } + + /** + * Returns an array containing all of the elements in this deque in + * proper sequence (from first to last element); the runtime type of the + * returned array is that of the specified array. If the deque fits in + * the specified array, it is returned therein. Otherwise, a new array + * is allocated with the runtime type of the specified array and the + * size of this deque. + * + * <p>If this deque fits in the specified array with room to spare + * (i.e., the array has more elements than this deque), the element in + * the array immediately following the end of the deque is set to + * <tt>null</tt>. + * + * <p>Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + * <p>Suppose <tt>x</tt> is a deque known to contain only strings. + * The following code can be used to dump the deque into a newly + * allocated array of <tt>String</tt>: + * + * <pre> {@code String[] y = x.toArray(new String[0]);}</pre> + * + * Note that <tt>toArray(new Object[0])</tt> is identical in function to + * <tt>toArray()</tt>. + * + * @param a the array into which the elements of the deque are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this deque + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this deque + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public <T> T[] toArray(T[] a) { + int size = size(); + if (a.length < size) + a = (T[])java.lang.reflect.Array.newInstance( + a.getClass().getComponentType(), size); + copyElements(a); + if (a.length > size) + a[size] = null; + return a; + } + + // *** Object methods *** + + /** + * Returns a copy of this deque. + * + * @return a copy of this deque + */ + public ArrayDeque<E> clone() { + try { + @SuppressWarnings("unchecked") + ArrayDeque<E> result = (ArrayDeque<E>) super.clone(); + result.elements = Arrays.copyOf(elements, elements.length); + return result; + + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + * Appease the serialization gods. + */ + private static final long serialVersionUID = 2340985798034038923L; + + /** + * Serialize this deque. + * + * @serialData The current size (<tt>int</tt>) of the deque, + * followed by all of its elements (each an object reference) in + * first-to-last order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + + // Write out size + s.writeInt(size()); + + // Write out elements in order. + int mask = elements.length - 1; + for (int i = head; i != tail; i = (i + 1) & mask) + s.writeObject(elements[i]); + } + + /** + * Deserialize this deque. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Read in size and allocate array + int size = s.readInt(); + allocateElements(size); + head = 0; + tail = size; + + // Read in all elements in the proper order. + for (int i = 0; i < size; i++) + elements[i] = s.readObject(); + } } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java b/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java index b370794c..f2dcf70a 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -67,25 +67,25 @@ import android.os.Message; * <p>Here is an example of subclassing:</p> * <pre class="prettyprint"> * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { - * protected Long doInBackground(URL... urls) { - * int count = urls.length; - * long totalSize = 0; - * for (int i = 0; i < count; i++) { - * totalSize += Downloader.downloadFile(urls[i]); - * publishProgress((int) ((i / (float) count) * 100)); - * // Escape early if cancel() is called - * if (isCancelled()) break; - * } - * return totalSize; - * } + * protected Long doInBackground(URL... urls) { + * int count = urls.length; + * long totalSize = 0; + * for (int i = 0; i < count; i++) { + * totalSize += Downloader.downloadFile(urls[i]); + * publishProgress((int) ((i / (float) count) * 100)); + * // Escape early if cancel() is called + * if (isCancelled()) break; + * } + * return totalSize; + * } * - * protected void onProgressUpdate(Integer... progress) { - * setProgressPercent(progress[0]); - * } + * protected void onProgressUpdate(Integer... progress) { + * setProgressPercent(progress[0]); + * } * - * protected void onPostExecute(Long result) { - * showDialog("Downloaded " + result + " bytes"); - * } + * protected void onPostExecute(Long result) { + * showDialog("Downloaded " + result + " bytes"); + * } * } * </pre> * @@ -97,12 +97,12 @@ import android.os.Message; * <h2>AsyncTask's generic types</h2> * <p>The three types used by an asynchronous task are the following:</p> * <ol> - * <li><code>Params</code>, the type of the parameters sent to the task upon - * execution.</li> - * <li><code>Progress</code>, the type of the progress units published during - * the background computation.</li> - * <li><code>Result</code>, the type of the result of the background - * computation.</li> + * <li><code>Params</code>, the type of the parameters sent to the task upon + * execution.</li> + * <li><code>Progress</code>, the type of the progress units published during + * the background computation.</li> + * <li><code>Result</code>, the type of the result of the background + * computation.</li> * </ol> * <p>Not all types are always used by an asynchronous task. To mark a type as unused, * simply use the type {@link Void}:</p> @@ -113,25 +113,25 @@ import android.os.Message; * <h2>The 4 steps</h2> * <p>When an asynchronous task is executed, the task goes through 4 steps:</p> * <ol> - * <li>{@link #onPreExecute()}, invoked on the UI thread before the task - * is executed. This step is normally used to setup the task, for instance by - * showing a progress bar in the user interface.</li> - * <li>{@link #doInBackground}, invoked on the background thread - * immediately after {@link #onPreExecute()} finishes executing. This step is used - * to perform background computation that can take a long time. The parameters - * of the asynchronous task are passed to this step. The result of the computation must - * be returned by this step and will be passed back to the last step. This step - * can also use {@link #publishProgress} to publish one or more units - * of progress. These values are published on the UI thread, in the - * {@link #onProgressUpdate} step.</li> - * <li>{@link #onProgressUpdate}, invoked on the UI thread after a - * call to {@link #publishProgress}. The timing of the execution is - * undefined. This method is used to display any form of progress in the user - * interface while the background computation is still executing. For instance, - * it can be used to animate a progress bar or show logs in a text field.</li> - * <li>{@link #onPostExecute}, invoked on the UI thread after the background - * computation finishes. The result of the background computation is passed to - * this step as a parameter.</li> + * <li>{@link #onPreExecute()}, invoked on the UI thread before the task + * is executed. This step is normally used to setup the task, for instance by + * showing a progress bar in the user interface.</li> + * <li>{@link #doInBackground}, invoked on the background thread + * immediately after {@link #onPreExecute()} finishes executing. This step is used + * to perform background computation that can take a long time. The parameters + * of the asynchronous task are passed to this step. The result of the computation must + * be returned by this step and will be passed back to the last step. This step + * can also use {@link #publishProgress} to publish one or more units + * of progress. These values are published on the UI thread, in the + * {@link #onProgressUpdate} step.</li> + * <li>{@link #onProgressUpdate}, invoked on the UI thread after a + * call to {@link #publishProgress}. The timing of the execution is + * undefined. This method is used to display any form of progress in the user + * interface while the background computation is still executing. For instance, + * it can be used to animate a progress bar or show logs in a text field.</li> + * <li>{@link #onPostExecute}, invoked on the UI thread after the background + * computation finishes. The result of the background computation is passed to + * this step as a parameter.</li> * </ol> * * <h2>Cancelling a task</h2> @@ -147,24 +147,24 @@ import android.os.Message; * <p>There are a few threading rules that must be followed for this class to * work properly:</p> * <ul> - * <li>The AsyncTask class must be loaded on the UI thread. This is done - * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li> - * <li>The task instance must be created on the UI thread.</li> - * <li>{@link #execute} must be invoked on the UI thread.</li> - * <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute}, - * {@link #doInBackground}, {@link #onProgressUpdate} manually.</li> - * <li>The task can be executed only once (an exception will be thrown if - * a second execution is attempted.)</li> + * <li>The AsyncTask class must be loaded on the UI thread. This is done + * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li> + * <li>The task instance must be created on the UI thread.</li> + * <li>{@link #execute} must be invoked on the UI thread.</li> + * <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute}, + * {@link #doInBackground}, {@link #onProgressUpdate} manually.</li> + * <li>The task can be executed only once (an exception will be thrown if + * a second execution is attempted.)</li> * </ul> * * <h2>Memory observability</h2> * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following * operations are safe without explicit synchronizations.</p> * <ul> - * <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them - * in {@link #doInBackground}. - * <li>Set member fields in {@link #doInBackground}, and refer to them in - * {@link #onProgressUpdate} and {@link #onPostExecute}. + * <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them + * in {@link #doInBackground}. + * <li>Set member fields in {@link #doInBackground}, and refer to them in + * {@link #onProgressUpdate} and {@link #onPostExecute}. * </ul> * * <h2>Order of execution</h2> @@ -178,493 +178,493 @@ import android.os.Message; * {@link #THREAD_POOL_EXECUTOR}.</p> */ public abstract class AsyncTask<Params, Progress, Result> { - private static final String LOG_TAG = "AsyncTask"; - - private static final int CORE_POOL_SIZE = 5; - private static final int MAXIMUM_POOL_SIZE = 128; - private static final int KEEP_ALIVE = 1; - - private static final ThreadFactory sThreadFactory = new ThreadFactory() { - private final AtomicInteger mCount = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); - } - }; - - private static final BlockingQueue<Runnable> sPoolWorkQueue = - new LinkedBlockingQueue<Runnable>(10); - - /** - * An {@link Executor} that can be used to execute tasks in parallel. - */ - public static final Executor THREAD_POOL_EXECUTOR - = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, - TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); - - /** - * An {@link Executor} that executes tasks one at a time in serial - * order. This serialization is global to a particular process. - */ - public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); - - private static final int MESSAGE_POST_RESULT = 0x1; - private static final int MESSAGE_POST_PROGRESS = 0x2; - - private static final InternalHandler sHandler = new InternalHandler(); - - private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; - private final WorkerRunnable<Params, Result> mWorker; - private final FutureTask<Result> mFuture; - - private volatile Status mStatus = Status.PENDING; - - private final AtomicBoolean mCancelled = new AtomicBoolean(); - private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); - - private static class SerialExecutor implements Executor { - final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); - Runnable mActive; - - public synchronized void execute(final Runnable r) { - mTasks.offer(new Runnable() { - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - - protected synchronized void scheduleNext() { - if ((mActive = mTasks.poll()) != null) { - THREAD_POOL_EXECUTOR.execute(mActive); - } - } - } - - /** - * Indicates the current status of the task. Each status will be set only once - * during the lifetime of a task. - */ - public enum Status { - /** - * Indicates that the task has not been executed yet. - */ - PENDING, - /** - * Indicates that the task is running. - */ - RUNNING, - /** - * Indicates that {@link AsyncTask#onPostExecute} has finished. - */ - FINISHED, - } - - /** @hide Used to force static handler to be created. */ - public static void init() { - sHandler.getLooper(); - } - - /** @hide */ - public static void setDefaultExecutor(Executor exec) { - sDefaultExecutor = exec; - } - - /** - * Creates a new asynchronous task. This constructor must be invoked on the UI thread. - */ - public AsyncTask() { - mWorker = new WorkerRunnable<Params, Result>() { - public Result call() throws Exception { - mTaskInvoked.set(true); - - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - //noinspection unchecked - return postResult(doInBackground(mParams)); - } - }; - - mFuture = new FutureTask<Result>(mWorker) { - @Override - protected void done() { - try { - postResultIfNotInvoked(get()); - } catch (InterruptedException e) { - android.util.Log.w(LOG_TAG, e); - } catch (ExecutionException e) { - throw new RuntimeException("An error occured while executing doInBackground()", - e.getCause()); - } catch (CancellationException e) { - postResultIfNotInvoked(null); - } - } - }; - } - - private void postResultIfNotInvoked(Result result) { - final boolean wasTaskInvoked = mTaskInvoked.get(); - if (!wasTaskInvoked) { - postResult(result); - } - } - - private Result postResult(Result result) { - @SuppressWarnings("unchecked") - Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, - new AsyncTaskResult<Result>(this, result)); - message.sendToTarget(); - return result; - } - - /** - * Returns the current status of this task. - * - * @return The current status. - */ - public final Status getStatus() { - return mStatus; - } - - /** - * Override this method to perform a computation on a background thread. The - * specified parameters are the parameters passed to {@link #execute} - * by the caller of this task. - * - * This method can call {@link #publishProgress} to publish updates - * on the UI thread. - * - * @param params The parameters of the task. - * - * @return A result, defined by the subclass of this task. - * - * @see #onPreExecute() - * @see #onPostExecute - * @see #publishProgress - */ - protected abstract Result doInBackground(Params... params); - - /** - * Runs on the UI thread before {@link #doInBackground}. - * - * @see #onPostExecute - * @see #doInBackground - */ - protected void onPreExecute() { - } - - /** - * <p>Runs on the UI thread after {@link #doInBackground}. The - * specified result is the value returned by {@link #doInBackground}.</p> - * - * <p>This method won't be invoked if the task was cancelled.</p> - * - * @param result The result of the operation computed by {@link #doInBackground}. - * - * @see #onPreExecute - * @see #doInBackground - * @see #onCancelled(Object) - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onPostExecute(Result result) { - } - - /** - * Runs on the UI thread after {@link #publishProgress} is invoked. - * The specified values are the values passed to {@link #publishProgress}. - * - * @param values The values indicating progress. - * - * @see #publishProgress - * @see #doInBackground - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onProgressUpdate(Progress... values) { - } - - /** - * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.</p> - * - * <p>The default implementation simply invokes {@link #onCancelled()} and - * ignores the result. If you write your own implementation, do not call - * <code>super.onCancelled(result)</code>.</p> - * - * @param result The result, if any, computed in - * {@link #doInBackground(Object[])}, can be null - * - * @see #cancel(boolean) - * @see #isCancelled() - */ - @SuppressWarnings({"UnusedParameters"}) - protected void onCancelled(Result result) { - onCancelled(); - } - - /** - * <p>Applications should preferably override {@link #onCancelled(Object)}. - * This method is invoked by the default implementation of - * {@link #onCancelled(Object)}.</p> - * - * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.</p> - * - * @see #onCancelled(Object) - * @see #cancel(boolean) - * @see #isCancelled() - */ - protected void onCancelled() { - } - - /** - * Returns <tt>true</tt> if this task was cancelled before it completed - * normally. If you are calling {@link #cancel(boolean)} on the task, - * the value returned by this method should be checked periodically from - * {@link #doInBackground(Object[])} to end the task as soon as possible. - * - * @return <tt>true</tt> if task was cancelled before it completed - * - * @see #cancel(boolean) - */ - public final boolean isCancelled() { - return mCancelled.get(); - } - - /** - * <p>Attempts to cancel execution of this task. This attempt will - * fail if the task has already completed, already been cancelled, - * or could not be cancelled for some other reason. If successful, - * and this task has not started when <tt>cancel</tt> is called, - * this task should never run. If the task has already started, - * then the <tt>mayInterruptIfRunning</tt> parameter determines - * whether the thread executing this task should be interrupted in - * an attempt to stop the task.</p> - * - * <p>Calling this method will result in {@link #onCancelled(Object)} being - * invoked on the UI thread after {@link #doInBackground(Object[])} - * returns. Calling this method guarantees that {@link #onPostExecute(Object)} - * is never invoked. After invoking this method, you should check the - * value returned by {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])} to finish the task as early as - * possible.</p> - * - * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this - * task should be interrupted; otherwise, in-progress tasks are allowed - * to complete. - * - * @return <tt>false</tt> if the task could not be cancelled, - * typically because it has already completed normally; - * <tt>true</tt> otherwise - * - * @see #isCancelled() - * @see #onCancelled(Object) - */ - public final boolean cancel(boolean mayInterruptIfRunning) { - mCancelled.set(true); - return mFuture.cancel(mayInterruptIfRunning); - } - - /** - * Waits if necessary for the computation to complete, and then - * retrieves its result. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - */ - public final Result get() throws InterruptedException, ExecutionException { - return mFuture.get(); - } - - /** - * Waits if necessary for at most the given time for the computation - * to complete, and then retrieves its result. - * - * @param timeout Time to wait before cancelling the operation. - * @param unit The time unit for the timeout. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - * @throws TimeoutException If the wait timed out. - */ - public final Result get(long timeout, TimeUnit unit) throws InterruptedException, - ExecutionException, TimeoutException { - return mFuture.get(timeout, unit); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - * <p>Note: this function schedules the task on a queue for a single background - * thread or pool of threads depending on the platform version. When first - * introduced, AsyncTasks were executed serially on a single background thread. - * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. Starting - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being - * executed on a single thread to avoid common application errors caused - * by parallel execution. If you truly want parallel execution, you can use - * the {@link #executeOnExecutor} version of this method - * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings - * on its use. - * - * <p>This method must be invoked on the UI thread. - * - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - * @see #execute(Runnable) - */ - public final AsyncTask<Params, Progress, Result> execute(Params... params) { - return executeOnExecutor(sDefaultExecutor, params); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to - * allow multiple tasks to run in parallel on a pool of threads managed by - * AsyncTask, however you can also use your own {@link Executor} for custom - * behavior. - * - * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from - * a thread pool is generally <em>not</em> what one wants, because the order - * of their operation is not defined. For example, if these tasks are used - * to modify any state in common (such as writing a file due to a button click), - * there are no guarantees on the order of the modifications. - * Without careful work it is possible in rare cases for the newer version - * of the data to be over-written by an older one, leading to obscure data - * loss and stability issues. Such changes are best - * executed in serial; to guarantee such work is serialized regardless of - * platform version you can use this function with {@link #SERIAL_EXECUTOR}. - * - * <p>This method must be invoked on the UI thread. - * - * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a - * convenient process-wide thread pool for tasks that are loosely coupled. - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #execute(Object[]) - */ - public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, - Params... params) { - if (mStatus != Status.PENDING) { - switch (mStatus) { - case RUNNING: - throw new IllegalStateException("Cannot execute task:" - + " the task is already running."); - case FINISHED: - throw new IllegalStateException("Cannot execute task:" - + " the task has already been executed " - + "(a task can be executed only once)"); - } - } - - mStatus = Status.RUNNING; - - onPreExecute(); - - mWorker.mParams = params; - exec.execute(mFuture); - - return this; - } - - /** - * Convenience version of {@link #execute(Object...)} for use with - * a simple Runnable object. See {@link #execute(Object[])} for more - * information on the order of execution. - * - * @see #execute(Object[]) - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - */ - public static void execute(Runnable runnable) { - sDefaultExecutor.execute(runnable); - } - - /** - * This method can be invoked from {@link #doInBackground} to - * publish updates on the UI thread while the background computation is - * still running. Each call to this method will trigger the execution of - * {@link #onProgressUpdate} on the UI thread. - * - * {@link #onProgressUpdate} will note be called if the task has been - * canceled. - * - * @param values The progress values to update the UI with. - * - * @see #onProgressUpdate - * @see #doInBackground - */ - protected final void publishProgress(Progress... values) { - if (!isCancelled()) { - sHandler.obtainMessage(MESSAGE_POST_PROGRESS, - new AsyncTaskResult<Progress>(this, values)).sendToTarget(); - } - } - - private void finish(Result result) { - if (isCancelled()) { - onCancelled(result); - } else { - onPostExecute(result); - } - mStatus = Status.FINISHED; - } - - private static class InternalHandler extends Handler { - @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) - @Override - public void handleMessage(Message msg) { - AsyncTaskResult result = (AsyncTaskResult) msg.obj; - switch (msg.what) { - case MESSAGE_POST_RESULT: - // There is only one result - result.mTask.finish(result.mData[0]); - break; - case MESSAGE_POST_PROGRESS: - result.mTask.onProgressUpdate(result.mData); - break; - } - } - } - - private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { - Params[] mParams; - } - - @SuppressWarnings({"RawUseOfParameterizedType"}) - private static class AsyncTaskResult<Data> { - final AsyncTask mTask; - final Data[] mData; - - AsyncTaskResult(AsyncTask task, Data... data) { - mTask = task; - mData = data; - } - } + private static final String LOG_TAG = "AsyncTask"; + + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 128; + private static final int KEEP_ALIVE = 1; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue<Runnable> sPoolWorkQueue = + new LinkedBlockingQueue<Runnable>(10); + + /** + * An {@link Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR + = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); + + /** + * An {@link Executor} that executes tasks one at a time in serial + * order. This serialization is global to a particular process. + */ + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + private static final int MESSAGE_POST_RESULT = 0x1; + private static final int MESSAGE_POST_PROGRESS = 0x2; + + private static final InternalHandler sHandler = new InternalHandler(); + + private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + private final WorkerRunnable<Params, Result> mWorker; + private final FutureTask<Result> mFuture; + + private volatile Status mStatus = Status.PENDING; + + private final AtomicBoolean mCancelled = new AtomicBoolean(); + private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); + + private static class SerialExecutor implements Executor { + final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + + /** + * Indicates the current status of the task. Each status will be set only once + * during the lifetime of a task. + */ + public enum Status { + /** + * Indicates that the task has not been executed yet. + */ + PENDING, + /** + * Indicates that the task is running. + */ + RUNNING, + /** + * Indicates that {@link AsyncTask#onPostExecute} has finished. + */ + FINISHED, + } + + /** @hide Used to force static handler to be created. */ + public static void init() { + sHandler.getLooper(); + } + + /** @hide */ + public static void setDefaultExecutor(Executor exec) { + sDefaultExecutor = exec; + } + + /** + * Creates a new asynchronous task. This constructor must be invoked on the UI thread. + */ + public AsyncTask() { + mWorker = new WorkerRunnable<Params, Result>() { + public Result call() throws Exception { + mTaskInvoked.set(true); + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + return postResult(doInBackground(mParams)); + } + }; + + mFuture = new FutureTask<Result>(mWorker) { + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(LOG_TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException("An error occured while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + }; + } + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + + private Result postResult(Result result) { + @SuppressWarnings("unchecked") + Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + new AsyncTaskResult<Result>(this, result)); + message.sendToTarget(); + return result; + } + + /** + * Returns the current status of this task. + * + * @return The current status. + */ + public final Status getStatus() { + return mStatus; + } + + /** + * Override this method to perform a computation on a background thread. The + * specified parameters are the parameters passed to {@link #execute} + * by the caller of this task. + * + * This method can call {@link #publishProgress} to publish updates + * on the UI thread. + * + * @param params The parameters of the task. + * + * @return A result, defined by the subclass of this task. + * + * @see #onPreExecute() + * @see #onPostExecute + * @see #publishProgress + */ + protected abstract Result doInBackground(Params... params); + + /** + * Runs on the UI thread before {@link #doInBackground}. + * + * @see #onPostExecute + * @see #doInBackground + */ + protected void onPreExecute() { + } + + /** + * <p>Runs on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.</p> + * + * <p>This method won't be invoked if the task was cancelled.</p> + * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onPostExecute(Result result) { + } + + /** + * Runs on the UI thread after {@link #publishProgress} is invoked. + * The specified values are the values passed to {@link #publishProgress}. + * + * @param values The values indicating progress. + * + * @see #publishProgress + * @see #doInBackground + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onProgressUpdate(Progress... values) { + } + + /** + * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.</p> + * + * <p>The default implementation simply invokes {@link #onCancelled()} and + * ignores the result. If you write your own implementation, do not call + * <code>super.onCancelled(result)</code>.</p> + * + * @param result The result, if any, computed in + * {@link #doInBackground(Object[])}, can be null + * + * @see #cancel(boolean) + * @see #isCancelled() + */ + @SuppressWarnings({"UnusedParameters"}) + protected void onCancelled(Result result) { + onCancelled(); + } + + /** + * <p>Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.</p> + * + * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.</p> + * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + protected void onCancelled() { + } + + /** + * Returns <tt>true</tt> if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground(Object[])} to end the task as soon as possible. + * + * @return <tt>true</tt> if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + * <p>Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when <tt>cancel</tt> is called, + * this task should never run. If the task has already started, + * then the <tt>mayInterruptIfRunning</tt> parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.</p> + * + * <p>Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground(Object[])} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])} to finish the task as early as + * possible.</p> + * + * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return <tt>false</tt> if the task could not be cancelled, + * typically because it has already completed normally; + * <tt>true</tt> otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + public final Result get() throws InterruptedException, ExecutionException { + return mFuture.get(); + } + + /** + * Waits if necessary for at most the given time for the computation + * to complete, and then retrieves its result. + * + * @param timeout Time to wait before cancelling the operation. + * @param unit The time unit for the timeout. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + * @throws TimeoutException If the wait timed out. + */ + public final Result get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return mFuture.get(timeout, unit); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + * <p>Note: this function schedules the task on a queue for a single background + * thread or pool of threads depending on the platform version. When first + * introduced, AsyncTasks were executed serially on a single background thread. + * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused + * by parallel execution. If you truly want parallel execution, you can use + * the {@link #executeOnExecutor} version of this method + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. + * + * <p>This method must be invoked on the UI thread. + * + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + * @see #execute(Runnable) + */ + public final AsyncTask<Params, Progress, Result> execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to + * allow multiple tasks to run in parallel on a pool of threads managed by + * AsyncTask, however you can also use your own {@link Executor} for custom + * behavior. + * + * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from + * a thread pool is generally <em>not</em> what one wants, because the order + * of their operation is not defined. For example, if these tasks are used + * to modify any state in common (such as writing a file due to a button click), + * there are no guarantees on the order of the modifications. + * Without careful work it is possible in rare cases for the newer version + * of the data to be over-written by an older one, leading to obscure data + * loss and stability issues. Such changes are best + * executed in serial; to guarantee such work is serialized regardless of + * platform version you can use this function with {@link #SERIAL_EXECUTOR}. + * + * <p>This method must be invoked on the UI thread. + * + * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a + * convenient process-wide thread pool for tasks that are loosely coupled. + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #execute(Object[]) + */ + public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, + Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + mWorker.mParams = params; + exec.execute(mFuture); + + return this; + } + + /** + * Convenience version of {@link #execute(Object...)} for use with + * a simple Runnable object. See {@link #execute(Object[])} for more + * information on the order of execution. + * + * @see #execute(Object[]) + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + */ + public static void execute(Runnable runnable) { + sDefaultExecutor.execute(runnable); + } + + /** + * This method can be invoked from {@link #doInBackground} to + * publish updates on the UI thread while the background computation is + * still running. Each call to this method will trigger the execution of + * {@link #onProgressUpdate} on the UI thread. + * + * {@link #onProgressUpdate} will note be called if the task has been + * canceled. + * + * @param values The progress values to update the UI with. + * + * @see #onProgressUpdate + * @see #doInBackground + */ + protected final void publishProgress(Progress... values) { + if (!isCancelled()) { + sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + new AsyncTaskResult<Progress>(this, values)).sendToTarget(); + } + } + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + + private static class InternalHandler extends Handler { + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) + @Override + public void handleMessage(Message msg) { + AsyncTaskResult result = (AsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + } + } + } + + private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { + Params[] mParams; + } + + @SuppressWarnings({"RawUseOfParameterizedType"}) + private static class AsyncTaskResult<Data> { + final AsyncTask mTask; + final Data[] mData; + + AsyncTaskResult(AsyncTask task, Data... data) { + mTask = task; + mData = data; + } + } } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java b/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java index ad17bcc0..97aefeab 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java @@ -41,37 +41,37 @@ import java.util.Stack; * <p> * <table BORDER CELLPADDING=3 CELLSPACING=1> * <tr> - * <td></td> - * <td ALIGN=CENTER COLSPAN = 2> <b>First Element (Head)</b></td> - * <td ALIGN=CENTER COLSPAN = 2> <b>Last Element (Tail)</b></td> + * <td></td> + * <td ALIGN=CENTER COLSPAN = 2> <b>First Element (Head)</b></td> + * <td ALIGN=CENTER COLSPAN = 2> <b>Last Element (Tail)</b></td> * </tr> * <tr> - * <td></td> - * <td ALIGN=CENTER><em>Throws exception</em></td> - * <td ALIGN=CENTER><em>Special value</em></td> - * <td ALIGN=CENTER><em>Throws exception</em></td> - * <td ALIGN=CENTER><em>Special value</em></td> + * <td></td> + * <td ALIGN=CENTER><em>Throws exception</em></td> + * <td ALIGN=CENTER><em>Special value</em></td> + * <td ALIGN=CENTER><em>Throws exception</em></td> + * <td ALIGN=CENTER><em>Special value</em></td> * </tr> * <tr> - * <td><b>Insert</b></td> - * <td>{@link #addFirst addFirst(e)}</td> - * <td>{@link #offerFirst offerFirst(e)}</td> - * <td>{@link #addLast addLast(e)}</td> - * <td>{@link #offerLast offerLast(e)}</td> + * <td><b>Insert</b></td> + * <td>{@link #addFirst addFirst(e)}</td> + * <td>{@link #offerFirst offerFirst(e)}</td> + * <td>{@link #addLast addLast(e)}</td> + * <td>{@link #offerLast offerLast(e)}</td> * </tr> * <tr> - * <td><b>Remove</b></td> - * <td>{@link #removeFirst removeFirst()}</td> - * <td>{@link #pollFirst pollFirst()}</td> - * <td>{@link #removeLast removeLast()}</td> - * <td>{@link #pollLast pollLast()}</td> + * <td><b>Remove</b></td> + * <td>{@link #removeFirst removeFirst()}</td> + * <td>{@link #pollFirst pollFirst()}</td> + * <td>{@link #removeLast removeLast()}</td> + * <td>{@link #pollLast pollLast()}</td> * </tr> * <tr> - * <td><b>Examine</b></td> - * <td>{@link #getFirst getFirst()}</td> - * <td>{@link #peekFirst peekFirst()}</td> - * <td>{@link #getLast getLast()}</td> - * <td>{@link #peekLast peekLast()}</td> + * <td><b>Examine</b></td> + * <td>{@link #getFirst getFirst()}</td> + * <td>{@link #peekFirst peekFirst()}</td> + * <td>{@link #getLast getLast()}</td> + * <td>{@link #peekLast peekLast()}</td> * </tr> * </table> * @@ -84,32 +84,32 @@ import java.util.Stack; * <p> * <table BORDER CELLPADDING=3 CELLSPACING=1> * <tr> - * <td ALIGN=CENTER> <b><tt>Queue</tt> Method</b></td> - * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> + * <td ALIGN=CENTER> <b><tt>Queue</tt> Method</b></td> + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> * </tr> * <tr> - * <td>{@link java.util.Queue#add add(e)}</td> - * <td>{@link #addLast addLast(e)}</td> + * <td>{@link java.util.Queue#add add(e)}</td> + * <td>{@link #addLast addLast(e)}</td> * </tr> * <tr> - * <td>{@link java.util.Queue#offer offer(e)}</td> - * <td>{@link #offerLast offerLast(e)}</td> + * <td>{@link java.util.Queue#offer offer(e)}</td> + * <td>{@link #offerLast offerLast(e)}</td> * </tr> * <tr> - * <td>{@link java.util.Queue#remove remove()}</td> - * <td>{@link #removeFirst removeFirst()}</td> + * <td>{@link java.util.Queue#remove remove()}</td> + * <td>{@link #removeFirst removeFirst()}</td> * </tr> * <tr> - * <td>{@link java.util.Queue#poll poll()}</td> - * <td>{@link #pollFirst pollFirst()}</td> + * <td>{@link java.util.Queue#poll poll()}</td> + * <td>{@link #pollFirst pollFirst()}</td> * </tr> * <tr> - * <td>{@link java.util.Queue#element element()}</td> - * <td>{@link #getFirst getFirst()}</td> + * <td>{@link java.util.Queue#element element()}</td> + * <td>{@link #getFirst getFirst()}</td> * </tr> * <tr> - * <td>{@link java.util.Queue#peek peek()}</td> - * <td>{@link #peek peekFirst()}</td> + * <td>{@link java.util.Queue#peek peek()}</td> + * <td>{@link #peek peekFirst()}</td> * </tr> * </table> * @@ -122,20 +122,20 @@ import java.util.Stack; * <p> * <table BORDER CELLPADDING=3 CELLSPACING=1> * <tr> - * <td ALIGN=CENTER> <b>Stack Method</b></td> - * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> + * <td ALIGN=CENTER> <b>Stack Method</b></td> + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> * </tr> * <tr> - * <td>{@link #push push(e)}</td> - * <td>{@link #addFirst addFirst(e)}</td> + * <td>{@link #push push(e)}</td> + * <td>{@link #addFirst addFirst(e)}</td> * </tr> * <tr> - * <td>{@link #pop pop()}</td> - * <td>{@link #removeFirst removeFirst()}</td> + * <td>{@link #pop pop()}</td> + * <td>{@link #removeFirst removeFirst()}</td> * </tr> * <tr> - * <td>{@link #peek peek()}</td> - * <td>{@link #peekFirst peekFirst()}</td> + * <td>{@link #peek peek()}</td> + * <td>{@link #peekFirst peekFirst()}</td> * </tr> * </table> * @@ -170,383 +170,383 @@ import java.util.Stack; */ public interface Deque<E> extends Queue<E> { - /** - * Inserts the specified element at the front of this deque if it is - * possible to do so immediately without violating capacity restrictions. - * When using a capacity-restricted deque, it is generally preferable to - * use method {@link #offerFirst}. - * - * @param e the element to add - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void addFirst(E e); - - /** - * Inserts the specified element at the end of this deque if it is - * possible to do so immediately without violating capacity restrictions. - * When using a capacity-restricted deque, it is generally preferable to - * use method {@link #offerLast}. - * - * <p>This method is equivalent to {@link #add}. - * - * @param e the element to add - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void addLast(E e); - - /** - * Inserts the specified element at the front of this deque unless it would - * violate capacity restrictions. When using a capacity-restricted deque, - * this method is generally preferable to the {@link #addFirst} method, - * which can fail to insert an element only by throwing an exception. - * - * @param e the element to add - * @return <tt>true</tt> if the element was added to this deque, else - * <tt>false</tt> - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offerFirst(E e); - - /** - * Inserts the specified element at the end of this deque unless it would - * violate capacity restrictions. When using a capacity-restricted deque, - * this method is generally preferable to the {@link #addLast} method, - * which can fail to insert an element only by throwing an exception. - * - * @param e the element to add - * @return <tt>true</tt> if the element was added to this deque, else - * <tt>false</tt> - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offerLast(E e); - - /** - * Retrieves and removes the first element of this deque. This method - * differs from {@link #pollFirst pollFirst} only in that it throws an - * exception if this deque is empty. - * - * @return the head of this deque - * @throws NoSuchElementException if this deque is empty - */ - E removeFirst(); - - /** - * Retrieves and removes the last element of this deque. This method - * differs from {@link #pollLast pollLast} only in that it throws an - * exception if this deque is empty. - * - * @return the tail of this deque - * @throws NoSuchElementException if this deque is empty - */ - E removeLast(); - - /** - * Retrieves and removes the first element of this deque, - * or returns <tt>null</tt> if this deque is empty. - * - * @return the head of this deque, or <tt>null</tt> if this deque is empty - */ - E pollFirst(); - - /** - * Retrieves and removes the last element of this deque, - * or returns <tt>null</tt> if this deque is empty. - * - * @return the tail of this deque, or <tt>null</tt> if this deque is empty - */ - E pollLast(); - - /** - * Retrieves, but does not remove, the first element of this deque. - * - * This method differs from {@link #peekFirst peekFirst} only in that it - * throws an exception if this deque is empty. - * - * @return the head of this deque - * @throws NoSuchElementException if this deque is empty - */ - E getFirst(); - - /** - * Retrieves, but does not remove, the last element of this deque. - * This method differs from {@link #peekLast peekLast} only in that it - * throws an exception if this deque is empty. - * - * @return the tail of this deque - * @throws NoSuchElementException if this deque is empty - */ - E getLast(); - - /** - * Retrieves, but does not remove, the first element of this deque, - * or returns <tt>null</tt> if this deque is empty. - * - * @return the head of this deque, or <tt>null</tt> if this deque is empty - */ - E peekFirst(); - - /** - * Retrieves, but does not remove, the last element of this deque, - * or returns <tt>null</tt> if this deque is empty. - * - * @return the tail of this deque, or <tt>null</tt> if this deque is empty - */ - E peekLast(); - - /** - * Removes the first occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element <tt>e</tt> such that - * <tt>(o==null ? e==null : o.equals(e))</tt> - * (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean removeFirstOccurrence(Object o); - - /** - * Removes the last occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the last element <tt>e</tt> such that - * <tt>(o==null ? e==null : o.equals(e))</tt> - * (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean removeLastOccurrence(Object o); - - // *** Queue methods *** - - /** - * Inserts the specified element into the queue represented by this deque - * (in other words, at the tail of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * <tt>true</tt> upon success and throwing an - * <tt>IllegalStateException</tt> if no space is currently available. - * When using a capacity-restricted deque, it is generally preferable to - * use {@link #offer(Object) offer}. - * - * <p>This method is equivalent to {@link #addLast}. - * - * @param e the element to add - * @return <tt>true</tt> (as specified by {@link Collection#add}) - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean add(E e); - - /** - * Inserts the specified element into the queue represented by this deque - * (in other words, at the tail of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * <tt>true</tt> upon success and <tt>false</tt> if no space is currently - * available. When using a capacity-restricted deque, this method is - * generally preferable to the {@link #add} method, which can fail to - * insert an element only by throwing an exception. - * - * <p>This method is equivalent to {@link #offerLast}. - * - * @param e the element to add - * @return <tt>true</tt> if the element was added to this deque, else - * <tt>false</tt> - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offer(E e); - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque). - * This method differs from {@link #poll poll} only in that it throws an - * exception if this deque is empty. - * - * <p>This method is equivalent to {@link #removeFirst()}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException if this deque is empty - */ - E remove(); - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque), or returns - * <tt>null</tt> if this deque is empty. - * - * <p>This method is equivalent to {@link #pollFirst()}. - * - * @return the first element of this deque, or <tt>null</tt> if - * this deque is empty - */ - E poll(); - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque (in other words, the first element of this deque). - * This method differs from {@link #peek peek} only in that it throws an - * exception if this deque is empty. - * - * <p>This method is equivalent to {@link #getFirst()}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException if this deque is empty - */ - E element(); - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque (in other words, the first element of this deque), or - * returns <tt>null</tt> if this deque is empty. - * - * <p>This method is equivalent to {@link #peekFirst()}. - * - * @return the head of the queue represented by this deque, or - * <tt>null</tt> if this deque is empty - */ - E peek(); - - // *** Stack methods *** - - /** - * Pushes an element onto the stack represented by this deque (in other - * words, at the head of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * <tt>true</tt> upon success and throwing an - * <tt>IllegalStateException</tt> if no space is currently available. - * - * <p>This method is equivalent to {@link #addFirst}. - * - * @param e the element to push - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void push(E e); - - /** - * Pops an element from the stack represented by this deque. In other - * words, removes and returns the first element of this deque. - * - * <p>This method is equivalent to {@link #removeFirst()}. - * - * @return the element at the front of this deque (which is the top - * of the stack represented by this deque) - * @throws NoSuchElementException if this deque is empty - */ - E pop(); - - // *** Collection methods *** - - /** - * Removes the first occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element <tt>e</tt> such that - * <tt>(o==null ? e==null : o.equals(e))</tt> - * (if such an element exists). - * Returns <tt>true</tt> if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * <p>This method is equivalent to {@link #removeFirstOccurrence}. - * - * @param o element to be removed from this deque, if present - * @return <tt>true</tt> if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean remove(Object o); - - /** - * Returns <tt>true</tt> if this deque contains the specified element. - * More formally, returns <tt>true</tt> if and only if this deque contains - * at least one element <tt>e</tt> such that - * <tt>(o==null ? e==null : o.equals(e))</tt>. - * - * @param o element whose presence in this deque is to be tested - * @return <tt>true</tt> if this deque contains the specified element - * @throws ClassCastException if the type of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean contains(Object o); - - /** - * Returns the number of elements in this deque. - * - * @return the number of elements in this deque - */ - public int size(); - - /** - * Returns an iterator over the elements in this deque in proper sequence. - * The elements will be returned in order from first (head) to last (tail). - * - * @return an iterator over the elements in this deque in proper sequence - */ - Iterator<E> iterator(); - - /** - * Returns an iterator over the elements in this deque in reverse - * sequential order. The elements will be returned in order from - * last (tail) to first (head). - * - * @return an iterator over the elements in this deque in reverse - * sequence - */ - Iterator<E> descendingIterator(); + /** + * Inserts the specified element at the front of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerFirst}. + * + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void addFirst(E e); + + /** + * Inserts the specified element at the end of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerLast}. + * + * <p>This method is equivalent to {@link #add}. + * + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void addLast(E e); + + /** + * Inserts the specified element at the front of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addFirst} method, + * which can fail to insert an element only by throwing an exception. + * + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offerFirst(E e); + + /** + * Inserts the specified element at the end of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addLast} method, + * which can fail to insert an element only by throwing an exception. + * + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offerLast(E e); + + /** + * Retrieves and removes the first element of this deque. This method + * differs from {@link #pollFirst pollFirst} only in that it throws an + * exception if this deque is empty. + * + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty + */ + E removeFirst(); + + /** + * Retrieves and removes the last element of this deque. This method + * differs from {@link #pollLast pollLast} only in that it throws an + * exception if this deque is empty. + * + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty + */ + E removeLast(); + + /** + * Retrieves and removes the first element of this deque, + * or returns <tt>null</tt> if this deque is empty. + * + * @return the head of this deque, or <tt>null</tt> if this deque is empty + */ + E pollFirst(); + + /** + * Retrieves and removes the last element of this deque, + * or returns <tt>null</tt> if this deque is empty. + * + * @return the tail of this deque, or <tt>null</tt> if this deque is empty + */ + E pollLast(); + + /** + * Retrieves, but does not remove, the first element of this deque. + * + * This method differs from {@link #peekFirst peekFirst} only in that it + * throws an exception if this deque is empty. + * + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty + */ + E getFirst(); + + /** + * Retrieves, but does not remove, the last element of this deque. + * This method differs from {@link #peekLast peekLast} only in that it + * throws an exception if this deque is empty. + * + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty + */ + E getLast(); + + /** + * Retrieves, but does not remove, the first element of this deque, + * or returns <tt>null</tt> if this deque is empty. + * + * @return the head of this deque, or <tt>null</tt> if this deque is empty + */ + E peekFirst(); + + /** + * Retrieves, but does not remove, the last element of this deque, + * or returns <tt>null</tt> if this deque is empty. + * + * @return the tail of this deque, or <tt>null</tt> if this deque is empty + */ + E peekLast(); + + /** + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean removeFirstOccurrence(Object o); + + /** + * Removes the last occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean removeLastOccurrence(Object o); + + // *** Queue methods *** + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an + * <tt>IllegalStateException</tt> if no space is currently available. + * When using a capacity-restricted deque, it is generally preferable to + * use {@link #offer(Object) offer}. + * + * <p>This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean add(E e); + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and <tt>false</tt> if no space is currently + * available. When using a capacity-restricted deque, this method is + * generally preferable to the {@link #add} method, which can fail to + * insert an element only by throwing an exception. + * + * <p>This method is equivalent to {@link #offerLast}. + * + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offer(E e); + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque). + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #removeFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E remove(); + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #pollFirst()}. + * + * @return the first element of this deque, or <tt>null</tt> if + * this deque is empty + */ + E poll(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque). + * This method differs from {@link #peek peek} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #getFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E element(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque), or + * returns <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #peekFirst()}. + * + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty + */ + E peek(); + + // *** Stack methods *** + + /** + * Pushes an element onto the stack represented by this deque (in other + * words, at the head of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an + * <tt>IllegalStateException</tt> if no space is currently available. + * + * <p>This method is equivalent to {@link #addFirst}. + * + * @param e the element to push + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void push(E e); + + /** + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. + * + * <p>This method is equivalent to {@link #removeFirst()}. + * + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException if this deque is empty + */ + E pop(); + + // *** Collection methods *** + + /** + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * <p>This method is equivalent to {@link #removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean remove(Object o); + + /** + * Returns <tt>true</tt> if this deque contains the specified element. + * More formally, returns <tt>true</tt> if and only if this deque contains + * at least one element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt>. + * + * @param o element whose presence in this deque is to be tested + * @return <tt>true</tt> if this deque contains the specified element + * @throws ClassCastException if the type of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean contains(Object o); + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size(); + + /** + * Returns an iterator over the elements in this deque in proper sequence. + * The elements will be returned in order from first (head) to last (tail). + * + * @return an iterator over the elements in this deque in proper sequence + */ + Iterator<E> iterator(); + + /** + * Returns an iterator over the elements in this deque in reverse + * sequential order. The elements will be returned in order from + * last (tail) to first (head). + * + * @return an iterator over the elements in this deque in reverse + * sequence + */ + Iterator<E> descendingIterator(); } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java index 91b1e8fe..08d32be9 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java @@ -274,7 +274,7 @@ public class MuPDFActivity extends Activity implements FilePicker.FilePickerSupp super.onCreate(savedInstanceState); mAlertBuilder = new AlertDialog.Builder(this); - gAlertBuilder = mAlertBuilder; // keep a static copy of this that other classes can use + gAlertBuilder = mAlertBuilder; // keep a static copy of this that other classes can use if (core == null) { core = (MuPDFCore)getLastNonConfigurationInstance(); diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java index a703e61e..be1386e6 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java @@ -2,7 +2,7 @@ package com.artifex.mupdfdemo; public class OutlineActivityData { public OutlineItem items[]; - public int position; + public int position; static private OutlineActivityData singleton; static public void set(OutlineActivityData d) { diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java index 4251ed8e..c1b22021 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java @@ -7,11 +7,11 @@ import android.widget.BaseAdapter; import android.widget.TextView; public class OutlineAdapter extends BaseAdapter { - private final OutlineItem mItems[]; + private final OutlineItem mItems[]; private final LayoutInflater mInflater; public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) { mInflater = inflater; - mItems = items; + mItems = items; } public int getCount() { diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineItem.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineItem.java index 7730991e..0ed041c2 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineItem.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineItem.java @@ -1,14 +1,14 @@ package com.artifex.mupdfdemo; public class OutlineItem { - public final int level; + public final int level; public final String title; - public final int page; + public final int page; OutlineItem(int _level, String _title, int _page) { level = _level; title = _title; - page = _page; + page = _page; } } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/PageView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/PageView.java index c897f414..502e0c2b 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/PageView.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/PageView.java @@ -94,7 +94,7 @@ public abstract class PageView extends ViewGroup { private static final float INK_THICKNESS = 10.0f; private static final int BACKGROUND_COLOR = 0xFFFFFFFF; private static final int PROGRESS_DIALOG_DELAY = 200; - protected final Context mContext; + protected final Context mContext; protected int mPageNumber; private Point mParentSize; protected Point mSize; // Size of page at minimum zoom @@ -127,7 +127,7 @@ public abstract class PageView extends ViewGroup { public PageView(Context c, Point parentSize, Bitmap sharedHqBm) { super(c); - mContext = c; + mContext = c; mParentSize = parentSize; setBackgroundColor(BACKGROUND_COLOR); mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888); @@ -201,7 +201,7 @@ public abstract class PageView extends ViewGroup { public void releaseBitmaps() { reinit(); - // recycle bitmaps before releasing them. + // recycle bitmaps before releasing them. if (mEntireBm!=null) mEntireBm.recycle(); @@ -320,16 +320,16 @@ public abstract class PageView extends ViewGroup { paint.setColor(HIGHLIGHT_COLOR); for (RectF rect : mSearchBoxes) canvas.drawRect(rect.left*scale, rect.top*scale, - rect.right*scale, rect.bottom*scale, - paint); + rect.right*scale, rect.bottom*scale, + paint); } if (!mIsBlank && mLinks != null && mHighlightLinks) { paint.setColor(LINK_COLOR); for (LinkInfo link : mLinks) canvas.drawRect(link.rect.left*scale, link.rect.top*scale, - link.rect.right*scale, link.rect.bottom*scale, - paint); + link.rect.right*scale, link.rect.bottom*scale, + paint); } if (mSelectBox != null && mText != null) { @@ -536,7 +536,7 @@ public abstract class PageView extends ViewGroup { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int w = right-left; + int w = right-left; int h = bottom-top; if (mEntire != null) { @@ -556,7 +556,7 @@ public abstract class PageView extends ViewGroup { if (mPatchViewSize.x != w || mPatchViewSize.y != h) { // Zoomed since patch was created mPatchViewSize = null; - mPatchArea = null; + mPatchArea = null; if (mPatch != null) { mPatch.setImageBitmap(null); mPatch.invalidate(); @@ -630,7 +630,7 @@ public abstract class PageView extends ViewGroup { public void onPostExecute(Void result) { mPatchViewSize = patchViewSize; - mPatchArea = patchArea; + mPatchArea = patchArea; mPatch.setImageBitmap(mPatchBm); mPatch.invalidate(); //requestLayout(); diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java index f91efa41..bfccdb19 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java @@ -24,14 +24,14 @@ import android.widget.Scroller; public class ReaderView extends AdapterView<Adapter> implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable { - private static final int MOVING_DIAGONALLY = 0; - private static final int MOVING_LEFT = 1; - private static final int MOVING_RIGHT = 2; - private static final int MOVING_UP = 3; - private static final int MOVING_DOWN = 4; + private static final int MOVING_DIAGONALLY = 0; + private static final int MOVING_LEFT = 1; + private static final int MOVING_RIGHT = 2; + private static final int MOVING_UP = 3; + private static final int MOVING_DOWN = 4; - private static final int FLING_MARGIN = 100; - private static final int GAP = 20; + private static final int FLING_MARGIN = 100; + private static final int GAP = 20; private static final float MIN_SCALE = 1.0f; private static final float MAX_SCALE = 5.0f; @@ -74,7 +74,7 @@ public class ReaderView super(context); mGestureDetector = new GestureDetector(this); mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); + mScroller = new Scroller(context); mStepper = new Stepper(this, this); } @@ -94,7 +94,7 @@ public class ReaderView { mGestureDetector = new GestureDetector(this); mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); + mScroller = new Scroller(context); mStepper = new Stepper(this, this); } } @@ -103,7 +103,7 @@ public class ReaderView super(context, attrs, defStyle); mGestureDetector = new GestureDetector(this); mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); + mScroller = new Scroller(context); mStepper = new Stepper(this, this); } @@ -173,7 +173,7 @@ public class ReaderView // code. // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 - int screenWidth = getWidth(); + int screenWidth = getWidth(); int screenHeight = getHeight(); // We might be mid scroll; we want to calculate where we scroll to based on // where this scroll would end, not where we are now (to allow for people @@ -181,11 +181,11 @@ public class ReaderView int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); // right/bottom is in terms of pixels within the scaled document; e.g. 1000 - int top = -(v.getTop() + mYScroll + remainingY); - int right = screenWidth -(v.getLeft() + mXScroll + remainingX); + int top = -(v.getTop() + mYScroll + remainingY); + int right = screenWidth -(v.getLeft() + mXScroll + remainingX); int bottom = screenHeight+top; // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 - int docWidth = v.getMeasuredWidth(); + int docWidth = v.getMeasuredWidth(); int docHeight = v.getMeasuredHeight(); int xOffset, yOffset; @@ -196,7 +196,7 @@ public class ReaderView View nv = mChildViews.get(mCurrent+1); if (nv == null) // No page to advance to return; - int nextTop = -(nv.getTop() + mYScroll + remainingY); + int nextTop = -(nv.getTop() + mYScroll + remainingY); int nextLeft = -(nv.getLeft() + mXScroll + remainingX); int nextDocWidth = nv.getMeasuredWidth(); int nextDocHeight = nv.getMeasuredHeight(); @@ -246,7 +246,7 @@ public class ReaderView // code. // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 - int screenWidth = getWidth(); + int screenWidth = getWidth(); int screenHeight = getHeight(); // We might be mid scroll; we want to calculate where we scroll to based on // where this scroll would end, not where we are now (to allow for people @@ -254,8 +254,8 @@ public class ReaderView int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); // left/top is in terms of pixels within the scaled document; e.g. 1000 - int left = -(v.getLeft() + mXScroll + remainingX); - int top = -(v.getTop() + mYScroll + remainingY); + int left = -(v.getLeft() + mXScroll + remainingX); + int top = -(v.getTop() + mYScroll + remainingY); // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 int docHeight = v.getMeasuredHeight(); @@ -273,8 +273,8 @@ public class ReaderView // Allow for the next page maybe being shorter than the screen is high yOffset = (prevDocHeight < screenHeight ? ((prevDocHeight - screenHeight)>>1) : 0); - int prevLeft = -(pv.getLeft() + mXScroll); - int prevTop = -(pv.getTop() + mYScroll); + int prevLeft = -(pv.getLeft() + mXScroll); + int prevTop = -(pv.getTop() + mYScroll); if (prevDocWidth < screenWidth) { // Previous page is too narrow to fill the screen. Scroll to the bottom, centred. xOffset = (prevDocWidth - screenWidth)>>1; @@ -580,9 +580,9 @@ public class ReaderView catch (java.lang.OutOfMemoryError e) { System.out.println("Out of memory during layout"); - // we might get an out of memory error. - // so let's display an alert. - // TODO: a better message, in resources. + // we might get an out of memory error. + // so let's display an alert. + // TODO: a better message, in resources. if (!memAlert) { memAlert = true; @@ -700,37 +700,37 @@ public class ReaderView // the views spaced out cvOffset = subScreenSizeOffset(cv); if (notPresent) { - //Main item not already present. Just place it top left + // Main item not already present. Just place it top left cvLeft = cvOffset.x; - cvTop = cvOffset.y; + cvTop = cvOffset.y; } else { // Main item already present. Adjust by scroll offsets cvLeft = cv.getLeft() + mXScroll; - cvTop = cv.getTop() + mYScroll; + cvTop = cv.getTop() + mYScroll; } // Scroll values have been accounted for mXScroll = mYScroll = 0; - cvRight = cvLeft + cv.getMeasuredWidth(); - cvBottom = cvTop + cv.getMeasuredHeight(); + cvRight = cvLeft + cv.getMeasuredWidth(); + cvBottom = cvTop + cv.getMeasuredHeight(); if (!mUserInteracting && mScroller.isFinished()) { Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvRight += corr.x; - cvLeft += corr.x; - cvTop += corr.y; + cvRight += corr.x; + cvLeft += corr.x; + cvTop += corr.y; cvBottom += corr.y; } else if (HORIZONTAL_SCROLLING && cv.getMeasuredHeight() <= getHeight()) { // When the current view is as small as the screen in height, clamp // it vertically Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvTop += corr.y; + cvTop += corr.y; cvBottom += corr.y; } else if (!HORIZONTAL_SCROLLING && cv.getMeasuredWidth() <= getWidth()) { // When the current view is as small as the screen in width, clamp // it horizontally Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvRight += corr.x; - cvLeft += corr.x; + cvRight += corr.x; + cvLeft += corr.x; } cv.layout(cvLeft, cvTop, cvRight, cvBottom); @@ -871,14 +871,14 @@ public class ReaderView // onLayout, so add mXScroll and mYScroll to the current // positions when calculating the bounds. return getScrollBounds(v.getLeft() + mXScroll, - v.getTop() + mYScroll, - v.getLeft() + v.getMeasuredWidth() + mXScroll, - v.getTop() + v.getMeasuredHeight() + mYScroll); + v.getTop() + mYScroll, + v.getLeft() + v.getMeasuredWidth() + mXScroll, + v.getTop() + v.getMeasuredHeight() + mYScroll); } private Point getCorrection(Rect bounds) { return new Point(Math.min(Math.max(0,bounds.left),bounds.right), - Math.min(Math.max(0,bounds.top),bounds.bottom)); + Math.min(Math.max(0,bounds.top),bounds.bottom)); } private void postSettle(final View v) { @@ -926,10 +926,10 @@ public class ReaderView private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) { switch (directionOfTravel(vx, vy)) { case MOVING_DIAGONALLY: return bounds.contains(0, 0); - case MOVING_LEFT: return bounds.left <= 0; - case MOVING_RIGHT: return bounds.right >= 0; - case MOVING_UP: return bounds.top <= 0; - case MOVING_DOWN: return bounds.bottom >= 0; + case MOVING_LEFT: return bounds.left <= 0; + case MOVING_RIGHT: return bounds.right >= 0; + case MOVING_UP: return bounds.top <= 0; + case MOVING_DOWN: return bounds.bottom >= 0; default: throw new NoSuchElementException(); } } diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java index d3969f10..7e15f98d 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java @@ -88,7 +88,7 @@ public abstract class SearchTask { protected void onPostExecute(SearchTaskResult result) { progressDialog.cancel(); if (result != null) { - onTextFound(result); + onTextFound(result); } else { mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurrences_found); AlertDialog alert = mAlertBuilder.create(); diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java index 8fa3c3a2..6c337fe2 100644 --- a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java @@ -4,7 +4,7 @@ import android.graphics.RectF; public class SearchTaskResult { public final String txt; - public final int pageNumber; + public final int pageNumber; public final RectF searchBoxes[]; static private SearchTaskResult singleton; |