summaryrefslogtreecommitdiff
path: root/android/src
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2010-12-02 18:52:55 +0000
committerRobin Watts <robin.watts@artifex.com>2010-12-02 18:52:55 +0000
commitcd7c8b147ad173f7071d3b1d115e913fc08c5cc3 (patch)
treea99ff136ac5458d54b1d1ee5f8856c8c46a3905e /android/src
parentb31a16f7bb711764fab3722898e1b459ca79a1e1 (diff)
downloadmupdf-cd7c8b147ad173f7071d3b1d115e913fc08c5cc3.tar.xz
Import Android demo.
Diffstat (limited to 'android/src')
-rw-r--r--android/src/com/artifex/mupdf/MuPDFActivity.java114
-rw-r--r--android/src/com/artifex/mupdf/PixmapView.java577
2 files changed, 691 insertions, 0 deletions
diff --git a/android/src/com/artifex/mupdf/MuPDFActivity.java b/android/src/com/artifex/mupdf/MuPDFActivity.java
new file mode 100644
index 00000000..f1dd1cc8
--- /dev/null
+++ b/android/src/com/artifex/mupdf/MuPDFActivity.java
@@ -0,0 +1,114 @@
+package com.artifex.mupdf;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.*;
+import android.view.View.OnClickListener;
+import android.widget.*;
+import android.widget.LinearLayout.*;
+import java.io.File;
+
+import com.artifex.mupdf.PixmapView;
+
+public class MuPDFActivity extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ String state = Environment.getExternalStorageState();
+
+ if (Environment.MEDIA_MOUNTED.equals(state))
+ {
+ System.out.println("Media mounted read/write");
+ }
+ else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))
+ {
+ System.out.println("Media mounted read only");
+ }
+ else
+ {
+ System.out.println("No media at all! Bale!\n");
+ return;
+ }
+ File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ File file = new File(path, "test.pdf");
+ System.out.println("Trying to open "+file.toString());
+ PixmapView pixmapView = new PixmapView(this, file.toString());
+ super.onCreate(savedInstanceState);
+
+ /* Now create the UI */
+ RelativeLayout layout;
+ LinearLayout bar;
+ MyButtonHandler bh = new MyButtonHandler(pixmapView);
+
+ bar = new LinearLayout(this);
+ bar.setOrientation(LinearLayout.HORIZONTAL);
+ bh.buttonStart = new Button(this);
+ bh.buttonStart.setText("<<");
+ bh.buttonStart.setOnClickListener(bh);
+ bar.addView(bh.buttonStart);
+ bh.buttonPrev = new Button(this);
+ bh.buttonPrev.setText("<");
+ bh.buttonPrev.setOnClickListener(bh);
+ bar.addView(bh.buttonPrev);
+ bh.buttonNext = new Button(this);
+ bh.buttonNext.setText(">");
+ bh.buttonNext.setOnClickListener(bh);
+ bar.addView(bh.buttonNext);
+ bh.buttonEnd = new Button(this);
+ bh.buttonEnd.setText(">>");
+ bh.buttonEnd.setOnClickListener(bh);
+ bar.addView(bh.buttonEnd);
+
+ layout = new RelativeLayout(this);
+ layout.setLayoutParams(new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.FILL_PARENT,
+ RelativeLayout.LayoutParams.FILL_PARENT));
+ layout.setGravity(Gravity.FILL);
+
+ RelativeLayout.LayoutParams barParams =
+ new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.FILL_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ barParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ bar.setId(100);
+ layout.addView(bar, barParams);
+
+ RelativeLayout.LayoutParams pixmapParams =
+ new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.FILL_PARENT,
+ RelativeLayout.LayoutParams.FILL_PARENT);
+ pixmapParams.addRule(RelativeLayout.ABOVE,100);
+ layout.addView(pixmapView, pixmapParams);
+
+ setContentView(layout);
+ }
+
+ private class MyButtonHandler implements OnClickListener
+ {
+ Button buttonStart;
+ Button buttonPrev;
+ Button buttonNext;
+ Button buttonEnd;
+ PixmapView pixmapView;
+
+ public MyButtonHandler(PixmapView pixmapView)
+ {
+ this.pixmapView = pixmapView;
+ }
+
+ public void onClick(View v)
+ {
+ if (v == buttonStart)
+ pixmapView.changePage(Integer.MIN_VALUE);
+ else if (v == buttonPrev)
+ pixmapView.changePage(-1);
+ else if (v == buttonNext)
+ pixmapView.changePage(+1);
+ else if (v == buttonEnd)
+ pixmapView.changePage(Integer.MAX_VALUE);
+ }
+ }
+}
diff --git a/android/src/com/artifex/mupdf/PixmapView.java b/android/src/com/artifex/mupdf/PixmapView.java
new file mode 100644
index 00000000..80fcb5f1
--- /dev/null
+++ b/android/src/com/artifex/mupdf/PixmapView.java
@@ -0,0 +1,577 @@
+package com.artifex.mupdf;
+
+import android.app.*;
+import android.os.*;
+import android.content.*;
+import android.content.res.*;
+import android.graphics.*;
+import android.util.*;
+import android.view.*;
+import android.widget.*;
+import java.net.*;
+import java.io.*;
+
+public class PixmapView extends SurfaceView implements SurfaceHolder.Callback
+{
+ private SurfaceHolder holder;
+ private MuPDFThread thread = null;
+ private boolean threadStarted = false;
+
+ /* Constructor */
+ public PixmapView(Context context, String filename)
+ {
+ super(context);
+ System.out.println("PixmapView construct");
+ holder = getHolder();
+ holder.addCallback(this);
+ thread = new MuPDFThread(holder, filename);
+ setFocusable(true); // need to get the key events
+ }
+
+ /* load our native library */
+ static {
+ System.loadLibrary("mupdf");
+ }
+
+ /* Handlers for keys - so we can actually do stuff */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ if (thread.onKeyDown(keyCode, event))
+ return true;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ if (thread.onKeyUp(keyCode, event))
+ return true;
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ if (thread.onTouchEvent(event))
+ return true;
+ return super.onTouchEvent(event);
+ }
+
+ public void changePage(int delta)
+ {
+ thread.changePage(delta);
+ }
+
+ /* Handlers for SurfaceHolder callbacks; these are called when the
+ * surface is created/destroyed/changed. We need to ensure that we only
+ * draw into the surface between the created and destroyed calls.
+ * Therefore, we start/stop the thread that actually runs MuPDF on
+ * creation/destruction. */
+ public void surfaceCreated(SurfaceHolder holder)
+ {
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
+ {
+ thread.newScreenSize(width, height);
+ if (!threadStarted)
+ {
+ threadStarted = true;
+ thread.setRunning(true);
+ thread.start();
+ }
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder)
+ {
+ boolean retry = true;
+ thread.setRunning(false);
+ while (retry)
+ {
+ try
+ {
+ thread.join();
+ retry = false;
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ threadStarted = false;
+ }
+
+ class MuPDFThread extends Thread
+ {
+ private SurfaceHolder holder;
+ private boolean running = false;
+ private int keycode = -1;
+ private String filename;
+ private int screenWidth;
+ private int screenHeight;
+ private int screenGeneration;
+ private Bitmap bitmap;
+ private int numPages;
+
+ /* The following variables deal with the size of the current page;
+ * specifically, its position on the screen, its raw size, its
+ * current scale, and its current scaled size (in terms of whole
+ * pixels).
+ */
+ private int pageOriginX;
+ private int pageOriginY;
+ private float pageRawWidth;
+ private float pageRawHeight;
+ private float pageScale;
+ private int pageWidth;
+ private int pageHeight;
+
+ /* The following variables deal with the multitouch handling */
+ private final int NONE = 0;
+ private final int DRAG = 1;
+ private final int ZOOM = 2;
+ private int touchMode = NONE;
+ private float touchInitialSpacing;
+ private float touchDragStartX;
+ private float touchDragStartY;
+ private float touchInitialOriginX;
+ private float touchInitialOriginY;
+ private float touchInitialScale;
+ private PointF touchZoomMidpoint;
+
+ /* The following control the inner loop; other events etc cause
+ * action to be set. The inner loop runs around a tight loop
+ * performing the action requested of it.
+ */
+ private boolean wakeMe = false;
+ private int action;
+ private final int SLEEP = 0;
+ private final int REDRAW = 1;
+ private final int DIE = 2;
+ private final int GOTOPAGE = 3;
+ private int actionPageNum;
+
+ /* Members for blitting, declared here to avoid causing gcs */
+ private Rect srcRect;
+ private RectF dstRect;
+
+ public MuPDFThread(SurfaceHolder holder, String filename)
+ {
+ this.holder = holder;
+ this.filename = filename;
+ touchZoomMidpoint = new PointF(0,0);
+ srcRect = new Rect(0,0,0,0);
+ dstRect = new RectF(0,0,0,0);
+ }
+
+ public void setRunning(boolean running)
+ {
+ this.running = running;
+ }
+
+ public void newScreenSize(int width, int height)
+ {
+ this.screenWidth = width;
+ this.screenHeight = height;
+ this.screenGeneration++;
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent msg)
+ {
+ keycode = keyCode;
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent msg)
+ {
+ return false;
+ }
+
+ public synchronized void changePage(int delta)
+ {
+ action = GOTOPAGE;
+ if (delta == Integer.MIN_VALUE)
+ actionPageNum = 1;
+ else if (delta == Integer.MAX_VALUE)
+ actionPageNum = numPages;
+ else
+ {
+ actionPageNum += delta;
+ if (actionPageNum < 1)
+ actionPageNum = 1;
+ if (actionPageNum > numPages)
+ actionPageNum = numPages;
+ }
+ if (wakeMe)
+ {
+ wakeMe = false;
+ this.notify();
+ }
+ }
+
+ private float spacing(MotionEvent event)
+ {
+ float x = event.getX(0) - event.getX(1);
+ float y = event.getY(0) - event.getY(1);
+ return FloatMath.sqrt(x*x+y*y);
+ }
+
+ private void midpoint(PointF point, MotionEvent event)
+ {
+ float x = event.getX(0) + event.getX(1);
+ float y = event.getY(0) + event.getY(1);
+ point.set(x/2, y/2);
+ }
+
+ private synchronized void forceRedraw()
+ {
+ if (wakeMe)
+ {
+ wakeMe = false;
+ this.notify();
+ }
+ action = REDRAW;
+ }
+
+ public synchronized void setPageOriginTo(int x, int y)
+ {
+ /* Adjust the coordinates so that the page always covers the
+ * centre of the screen. */
+ if (x + pageWidth < screenWidth/2)
+ {
+ x = screenWidth/2 - pageWidth;
+ }
+ else if (x > screenWidth/2)
+ {
+ x = screenWidth/2;
+ }
+ if (y + pageHeight < screenHeight/2)
+ {
+ y = screenHeight/2 - pageHeight;
+ }
+ else if (y > screenHeight/2)
+ {
+ y = screenHeight/2;
+ }
+ if ((x != pageOriginX) || (y != pageOriginY))
+ {
+ pageOriginX = x;
+ pageOriginY = y;
+ }
+ forceRedraw();
+ }
+
+ public void setPageScaleTo(float scale, PointF midpoint)
+ {
+ float x, y;
+ /* Convert midpoint (in screen coords) to page coords */
+ x = (midpoint.x - pageOriginX)/pageScale;
+ y = (midpoint.y - pageOriginY)/pageScale;
+ /* Find new scaled page sizes */
+ synchronized(this)
+ {
+ pageWidth = (int)(pageRawWidth*scale+0.5);
+ if (pageWidth < screenWidth/2)
+ {
+ scale = screenWidth/2/pageRawWidth;
+ pageWidth = (int)(pageRawWidth*scale+0.5);
+ }
+ pageHeight = (int)(pageRawHeight*scale+0.5);
+ if (pageHeight < screenHeight/2)
+ {
+ scale = screenHeight/2/pageRawHeight;
+ pageWidth = (int)(pageRawWidth *scale+0.5);
+ pageHeight = (int)(pageRawHeight*scale+0.5);
+ }
+ pageScale = scale;
+ /* Now given this new scale, calculate page origins so that
+ * x and y are at midpoint */
+ float xscale = (float)pageWidth /(float)pageRawWidth;
+ float yscale = (float)pageHeight/(float)pageRawHeight;
+ setPageOriginTo((int)(midpoint.x - x*xscale + 0.5),
+ (int)(midpoint.y - y*yscale + 0.5));
+ }
+ }
+
+ public void scalePageToScreen()
+ {
+ float scaleX, scaleY;
+ scaleX = (float)screenWidth/pageRawWidth;
+ scaleY = (float)screenHeight/pageRawHeight;
+ synchronized(this)
+ {
+ if (scaleX < scaleY)
+ pageScale = scaleX;
+ else
+ pageScale = scaleY;
+ pageWidth = (int)(pageRawWidth * pageScale + 0.5);
+ pageHeight = (int)(pageRawHeight * pageScale + 0.5);
+ pageOriginX = (screenWidth - pageWidth)/2;
+ pageOriginY = (screenHeight - pageHeight)/2;
+ forceRedraw();
+ }
+ System.out.println("scalePageToScreen: Raw="+
+ pageRawWidth+"x"+pageRawHeight+" scaled="+
+ pageWidth+","+pageHeight+" pageScale="+
+ pageScale);
+ }
+
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ int action = event.getAction();
+ boolean done = false;
+ switch (action & MotionEvent.ACTION_MASK)
+ {
+ case MotionEvent.ACTION_DOWN:
+ touchMode = DRAG;
+ touchDragStartX = event.getX();
+ touchDragStartY = event.getY();
+ touchInitialOriginX = pageOriginX;
+ touchInitialOriginY = pageOriginY;
+ System.out.println("Starting dragging from: "+touchDragStartX+","+touchDragStartY+" ("+pageOriginX+","+pageOriginY+")");
+ done = true;
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ touchInitialSpacing = spacing(event);
+ if (touchInitialSpacing > 10f)
+ {
+ System.out.println("Started zooming: spacing="+touchInitialSpacing);
+ touchInitialScale = pageScale;
+ touchMode = ZOOM;
+ done = true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (touchMode != NONE)
+ {
+ System.out.println("Released!");
+ touchMode = NONE;
+ done = true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (touchMode == DRAG)
+ {
+ float x = touchInitialOriginX+event.getX()-touchDragStartX;
+ float y = touchInitialOriginY+event.getY()-touchDragStartY;
+ System.out.println("Dragged to "+x+","+y);
+ setPageOriginTo((int)(x+0.5),(int)(y+0.5));
+ done = true;
+ }
+ else if (touchMode == ZOOM)
+ {
+ float newSpacing = spacing(event);
+ if (newSpacing > 10f)
+ {
+ float newScale = touchInitialScale*newSpacing/touchInitialSpacing;
+ System.out.println("Zoomed to "+newSpacing);
+ midpoint(touchZoomMidpoint,event);
+ setPageScaleTo(newScale,touchZoomMidpoint);
+ done = true;
+ }
+ }
+ }
+ return done;
+ }
+
+ public void run()
+ {
+ boolean redraw = false;
+ int patchW = 0;
+ int patchH = 0;
+ int patchX = 0;
+ int patchY = 0;
+ int localPageW = 0;
+ int localPageH = 0;
+ int localScreenGeneration = screenGeneration;
+ int localAction;
+ int localActionPageNum = 1;
+ numPages = mupdfOpenFile(filename);
+ if (numPages <= 0)
+ {
+ /* Error whilst loading file */
+ }
+ System.out.println("File loaded: "+numPages+" pages");
+ /* Set up our default action */
+ action = GOTOPAGE;
+ actionPageNum = 1;
+ while (action != DIE)
+ {
+ synchronized(this)
+ {
+ while (action == SLEEP)
+ {
+ wakeMe = true;
+ try
+ {
+ System.out.println("Render thread sleeping");
+ this.wait();
+ System.out.println("Render thread woken");
+ }
+ catch (java.lang.InterruptedException e)
+ {
+ System.out.println("Render thread exception:"+e);
+ }
+ }
+ /* Now we do as little as we can get away with while
+ * synchronised. In general this means copying any action
+ * or global variables into local ones so that when we
+ * unsynchronoise, other people can alter them again.
+ */
+ switch (action)
+ {
+ case GOTOPAGE:
+ localActionPageNum = actionPageNum;
+ break;
+ case REDRAW:
+ /* Figure out what area of the page we want to
+ * redraw (in local variables, in docspace).
+ * We'll always draw a screensized lump, unless
+ * that's too big. */
+ System.out.println("page="+pageWidth+","+pageHeight+" ("+pageRawWidth+","+pageRawHeight+"@"+pageScale+") @ "+pageOriginX+","+pageOriginY);
+ localPageW = pageWidth;
+ localPageH = pageHeight;
+ patchW = pageWidth;
+ patchH = pageHeight;
+ patchX = -pageOriginX;
+ patchY = -pageOriginY;
+ if (patchX < 0)
+ patchX = 0;
+ if (patchW > screenWidth)
+ patchW = screenWidth;
+ srcRect.left = 0;
+ if (patchX+patchW > pageWidth)
+ {
+ srcRect.left += patchX+patchW-pageWidth;
+ patchX = pageWidth-patchW;
+ }
+ if (patchY < 0)
+ patchY = 0;
+ if (patchH > screenHeight)
+ patchH = screenHeight;
+ srcRect.top = 0;
+ if (patchY+patchH > pageHeight)
+ {
+ srcRect.top += patchY+patchH-pageHeight;
+ patchY = pageHeight-patchH;
+ }
+ dstRect.left = pageOriginX;
+ if (dstRect.left < 0)
+ dstRect.left = 0;
+ dstRect.top = pageOriginY;
+ if (dstRect.top < 0)
+ dstRect.top = 0;
+ dstRect.right = dstRect.left + patchW;
+ srcRect.right = srcRect.left + patchW;
+ if (srcRect.right > screenWidth)
+ {
+ dstRect.right -= srcRect.right-screenWidth;
+ srcRect.right = screenWidth;
+ }
+ if (dstRect.right > screenWidth)
+ {
+ srcRect.right -= dstRect.right-screenWidth;
+ dstRect.right = screenWidth;
+ }
+ dstRect.bottom = dstRect.top + patchH;
+ srcRect.bottom = srcRect.top + patchH;
+ if (srcRect.bottom > screenHeight)
+ {
+ dstRect.bottom -=srcRect.bottom-screenHeight;
+ srcRect.bottom = screenHeight;
+ }
+ if (dstRect.bottom > screenHeight)
+ {
+ srcRect.bottom -=dstRect.bottom-screenHeight;
+ dstRect.bottom = screenHeight;
+ }
+ System.out.println("patch=["+patchX+","+patchY+","+patchW+","+patchH+"]");
+ break;
+ }
+ localAction = action;
+ action = SLEEP;
+ }
+ /* In the redraw case:
+ * pW, pH, pX, pY, localPageW, localPageH are now all set
+ * in local variables, and we are safe from the global vars
+ * being altered in calls from other threads. This is all
+ * the information we need to actually do our render.
+ */
+ switch (localAction)
+ {
+ case GOTOPAGE:
+ mupdfGotoPage(localActionPageNum);
+ pageRawWidth = mupdfPageWidth();
+ pageRawHeight = mupdfPageHeight();
+ scalePageToScreen();
+ action = REDRAW;
+ break;
+ case REDRAW:
+ if ((bitmap == null) ||
+ (bitmap.getWidth() != patchW) ||
+ (bitmap.getHeight() != patchH))
+ {
+ /* make bitmap of required size */
+ bitmap = Bitmap.createBitmap(patchW, patchH,
+ Bitmap.Config.ARGB_8888);
+ }
+ System.out.println("Calling redraw native method");
+ mupdfDrawPage(bitmap, localPageW, localPageH,
+ patchX, patchY, patchW, patchH);
+ System.out.println("Called native method");
+ {
+ Canvas c = null;
+ try
+ {
+ c = holder.lockCanvas(null);
+ synchronized(holder)
+ {
+ if (localScreenGeneration == screenGeneration)
+ {
+ doDraw(c);
+ }
+ else
+ {
+ /* Someone has changed the screen
+ * under us! Better redraw again...
+ */
+ action = REDRAW;
+ }
+ }
+ }
+ finally
+ {
+ if (c != null)
+ holder.unlockCanvasAndPost(c);
+ }
+ }
+ }
+ }
+ }
+
+ protected void doDraw(Canvas canvas)
+ {
+ if ((canvas == null) || (bitmap == null))
+ return;
+ /* Clear the screen */
+ canvas.drawRGB(128,128,128);
+ /* Draw our bitmap on top */
+ System.out.println("Blitting bitmap from "+srcRect.left+","+srcRect.top+","+srcRect.right+","+srcRect.bottom+" to "+dstRect.left+","+dstRect.top+","+dstRect.right+","+dstRect.bottom);
+ canvas.drawBitmap(bitmap, srcRect, dstRect, (Paint)null);
+ }
+ }
+
+ /* These should be native functions */
+ private static native int mupdfOpenFile(String filename);
+ private static native void mupdfGotoPage(int localActionPageNum);
+ private static native float mupdfPageWidth();
+ private static native float mupdfPageHeight();
+ private static native void mupdfDrawPage(Bitmap bitmap,
+ int pageW,
+ int pageH,
+ int patchX,
+ int patchY,
+ int patchW,
+ int patchH);
+}