Commit d53cd9b6 authored by wanglei's avatar wanglei

初始化

parent 5166b6dc
Pipeline #1292 failed with stages
/build
\ No newline at end of file
//apply plugin: 'com.android.library'
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace "com.lonelypluto.pdfviewerlibrary"
compileSdk 34
defaultConfig {
minSdk 24
targetSdk 34
ndk {
//noinspection ChromeOsAbiSupport
abiFilters "arm64-v8a", "armeabi-v7a"
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(libs.androidx.appcompat)
// api 'com.artifex.mupdf:viewer:1.24.9a'
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
</manifest>
\ No newline at end of file
package com.artifex.mupdfdemo;
import android.graphics.RectF;
public class Annotation extends RectF {
public enum Type {
TEXT, LINK, FREETEXT, LINE, SQUARE, CIRCLE, POLYGON, POLYLINE, HIGHLIGHT,
UNDERLINE, SQUIGGLY, STRIKEOUT, STAMP, CARET, INK, POPUP, FILEATTACHMENT,
SOUND, MOVIE, WIDGET, SCREEN, PRINTERMARK, TRAPNET, WATERMARK, A3D, UNKNOWN
}
public final Type type;
public Annotation(float x0, float y0, float x1, float y1, int _type) {
super(x0, y0, x1, y1);
type = _type == -1 ? Type.UNKNOWN : Type.values()[_type];
}
}
/*
* Written by Josh Bloch of Google Inc. and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/.
*/
package com.artifex.mupdfdemo;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Stack;
// BEGIN android-note
// removed link to collections framework docs
// END android-note
/**
* Resizable-array implementation of the {@link Deque} interface. Array
* deques have no capacity restrictions; they grow as necessary to support
* usage. They are not thread-safe; in the absence of external
* synchronization, they do not support concurrent access by multiple threads.
* Null elements are prohibited. This class is likely to be faster than
* {@link Stack} when used as a stack, and faster than {@link LinkedList}
* when used as a queue.
*
* <p>Most <tt>ArrayDeque</tt> operations run in amortized constant time.
* Exceptions include {@link #remove(Object) remove}, {@link
* #removeFirstOccurrence removeFirstOccurrence}, {@link #removeLastOccurrence
* removeLastOccurrence}, {@link #contains contains}, {@link #iterator
* iterator.remove()}, and the bulk operations, all of which run in linear
* time.
*
* <p>The iterators returned by this class's <tt>iterator</tt> method are
* <i>fail-fast</i>: If the deque is modified at any time after the iterator
* is created, in any way except through the iterator's own <tt>remove</tt>
* method, the iterator will generally throw a {@link
* ConcurrentModificationException}. Thus, in the face of concurrent
* modification, the iterator fails quickly and cleanly, rather than risking
* arbitrary, non-deterministic behavior at an undetermined time in the
* future.
*
* <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
* as it is, generally speaking, impossible to make any hard guarantees in the
* presence of unsynchronized concurrent modification. Fail-fast iterators
* throw <tt>ConcurrentModificationException</tt> on a best-effort basis.
* Therefore, it would be wrong to write a program that depended on this
* exception for its correctness: <i>the fail-fast behavior of iterators
* should be used only to detect bugs.</i>
*
* <p>This class and its iterator implement all of the
* <em>optional</em> methods of the {@link Collection} and {@link
* Iterator} interfaces.
*
* @author Josh Bloch and Doug Lea
* @since 1.6
* @param <E> the type of elements held in this collection
*/
public class ArrayDeque<E> extends AbstractCollection<E>
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();
}
}
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.artifex.mupdfdemo;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import android.os.Process;
import android.os.Handler;
import android.os.Message;
/**
* <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.</p>
*
* <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> pacakge such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
*
* <p>An asynchronous task is defined by a computation that runs on a background thread and
* whose result is published on the UI thread. An asynchronous task is defined by 3 generic
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using tasks and threads, read the
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and
* Threads</a> developer guide.</p>
* </div>
*
* <h2>Usage</h2>
* <p>AsyncTask must be subclassed to be used. The subclass will override at least
* one method ({@link #doInBackground}), and most often will override a
* second one ({@link #onPostExecute}.)</p>
*
* <p>Here is an example of subclassing:</p>
* <pre class="prettyprint">
* private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
* 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 onPostExecute(Long result) {
* showDialog("Downloaded " + result + " bytes");
* }
* }
* </pre>
*
* <p>Once created, a task is executed very simply:</p>
* <pre class="prettyprint">
* new DownloadFilesTask().execute(url1, url2, url3);
* </pre>
*
* <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>
* </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>
* <pre>
* private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }
* </pre>
*
* <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>
* </ol>
*
* <h2>Cancelling a task</h2>
* <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
* this method will cause subsequent calls to {@link #isCancelled()} to return true.
* After invoking this method, {@link #onCancelled(Object)}, instead of
* {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
* returns. To ensure that a task is cancelled as quickly as possible, you should always
* check the return value of {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
*
* <h2>Threading rules</h2>
* <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>
* </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}.
* </ul>
*
* <h2>Order of execution</h2>
* <p>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 with
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
* thread to avoid common application errors caused by parallel execution.</p>
* <p>If you truly want parallel execution, you can invoke
* {@link #executeOnExecutor(Executor, Object[])} with
* {@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 Status#RUNNING} or {@link Status#FINISHED}.
*
* @see #executeOnExecutor(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 Status#RUNNING} or {@link 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(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;
}
}
}
package com.artifex.mupdfdemo;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
// Ideally this would be a subclass of AsyncTask, however the cancel() method is final, and cannot
// be overridden. I felt that having two different, but similar cancel methods was a bad idea.
public class CancellableAsyncTask<Params, Result>
{
private final AsyncTask<Params, Void, Result> asyncTask;
private final CancellableTaskDefinition<Params, Result> ourTask;
public void onPreExecute()
{
}
public void onPostExecute(Result result)
{
}
public CancellableAsyncTask(final CancellableTaskDefinition<Params, Result> task)
{
if (task == null)
throw new IllegalArgumentException();
this.ourTask = task;
asyncTask = new AsyncTask<Params, Void, Result>()
{
@Override
protected Result doInBackground(Params... params)
{
return task.doInBackground(params);
}
@Override
protected void onPreExecute()
{
CancellableAsyncTask.this.onPreExecute();
}
@Override
protected void onPostExecute(Result result)
{
CancellableAsyncTask.this.onPostExecute(result);
task.doCleanup();
}
};
}
public void cancelAndWait()
{
this.asyncTask.cancel(true);
ourTask.doCancel();
try
{
this.asyncTask.get();
}
catch (InterruptedException e)
{
}
catch (ExecutionException e)
{
}
catch (CancellationException e)
{
}
ourTask.doCleanup();
}
public void execute(Params ... params)
{
asyncTask.execute(params);
}
}
package com.artifex.mupdfdemo;
public interface CancellableTaskDefinition <Params, Result>
{
public Result doInBackground(Params ... params);
public void doCancel();
public void doCleanup();
}
package com.artifex.mupdfdemo;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
import android.view.View;
import android.widget.ListView;
import com.lonelypluto.pdfviewerlibrary.R;
enum Purpose {
PickPDF,
PickKeyFile
}
public class ChoosePDFActivity extends ListActivity {
static public final String PICK_KEY_FILE = "com.artifex.mupdfdemo.PICK_KEY_FILE";
static private File mDirectory;
static private Map<String, Integer> mPositions = new HashMap<String, Integer>();
private File mParent;
private File[] mDirs;
private File[] mFiles;
private Handler mHandler;
private Runnable mUpdateFiles;
private ChoosePDFAdapter adapter;
private Purpose mPurpose;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPurpose = PICK_KEY_FILE.equals(getIntent().getAction()) ? Purpose.PickKeyFile : Purpose.PickPDF;
String storageState = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(storageState)
&& !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.no_media_warning);
builder.setMessage(R.string.no_media_hint);
AlertDialog alert = builder.create();
alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss),
new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
alert.show();
return;
}
if (mDirectory == null)
mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
// Create a list adapter...
adapter = new ChoosePDFAdapter(getLayoutInflater());
setListAdapter(adapter);
// ...that is updated dynamically when files are scanned
mHandler = new Handler();
mUpdateFiles = new Runnable() {
public void run() {
Resources res = getResources();
String appName = "app_name";
String version = "1.0";
String title = res.getString(R.string.picker_title_App_Ver_Dir);
setTitle(String.format(title, appName, version, mDirectory));
mParent = mDirectory.getParentFile();
mDirs = mDirectory.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isDirectory();
}
});
if (mDirs == null)
mDirs = new File[0];
mFiles = mDirectory.listFiles(new FileFilter() {
public boolean accept(File file) {
if (file.isDirectory())
return false;
String fname = file.getName().toLowerCase();
switch (mPurpose) {
case PickPDF:
if (fname.endsWith(".pdf"))
return true;
if (fname.endsWith(".xps"))
return true;
if (fname.endsWith(".cbz"))
return true;
if (fname.endsWith(".epub"))
return true;
if (fname.endsWith(".png"))
return true;
if (fname.endsWith(".jpe"))
return true;
if (fname.endsWith(".jpeg"))
return true;
if (fname.endsWith(".jpg"))
return true;
if (fname.endsWith(".jfif"))
return true;
if (fname.endsWith(".jfif-tbnl"))
return true;
if (fname.endsWith(".tif"))
return true;
if (fname.endsWith(".tiff"))
return true;
return false;
case PickKeyFile:
if (fname.endsWith(".pfx"))
return true;
return false;
default:
return false;
}
}
});
if (mFiles == null)
mFiles = new File[0];
Arrays.sort(mFiles, new Comparator<File>() {
public int compare(File arg0, File arg1) {
return arg0.getName().compareToIgnoreCase(arg1.getName());
}
});
Arrays.sort(mDirs, new Comparator<File>() {
public int compare(File arg0, File arg1) {
return arg0.getName().compareToIgnoreCase(arg1.getName());
}
});
adapter.clear();
if (mParent != null)
adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.PARENT, getString(R.string.parent_directory)));
for (File f : mDirs)
adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DIR, f.getName()));
for (File f : mFiles)
adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DOC, f.getName()));
lastPosition();
}
};
// Start initial file scan...
mHandler.post(mUpdateFiles);
// ...and observe the directory and scan files upon changes.
FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) {
public void onEvent(int event, String path) {
mHandler.post(mUpdateFiles);
}
};
observer.startWatching();
}
private void lastPosition() {
String p = mDirectory.getAbsolutePath();
if (mPositions.containsKey(p))
getListView().setSelection(mPositions.get(p));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition());
if (position < (mParent == null ? 0 : 1)) {
mDirectory = mParent;
mHandler.post(mUpdateFiles);
return;
}
position -= (mParent == null ? 0 : 1);
if (position < mDirs.length) {
mDirectory = mDirs[position];
mHandler.post(mUpdateFiles);
return;
}
position -= mDirs.length;
Uri uri = Uri.fromFile(mFiles[position]);
Intent intent = new Intent(this, MuPDFActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
switch (mPurpose) {
case PickPDF:
// Start an activity to display the PDF file
startActivity(intent);
break;
case PickKeyFile:
// Return the uri to the caller
setResult(RESULT_OK, intent);
finish();
break;
}
}
@Override
protected void onPause() {
super.onPause();
if (mDirectory != null)
mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition());
}
}
package com.artifex.mupdfdemo;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.lonelypluto.pdfviewerlibrary.R;
import java.util.LinkedList;
public class ChoosePDFAdapter extends BaseAdapter {
private final LinkedList<ChoosePDFItem> mItems;
private final LayoutInflater mInflater;
public ChoosePDFAdapter(LayoutInflater inflater) {
mInflater = inflater;
mItems = new LinkedList<ChoosePDFItem>();
}
public void clear() {
mItems.clear();
}
public void add(ChoosePDFItem item) {
mItems.add(item);
notifyDataSetChanged();
}
public int getCount() {
return mItems.size();
}
public Object getItem(int i) {
return null;
}
public long getItemId(int arg0) {
return 0;
}
private int iconForType(ChoosePDFItem.Type type) {
switch (type) {
case PARENT:
return R.drawable.ic_arrow_up;
case DIR:
return R.drawable.ic_dir;
case DOC:
return R.drawable.ic_doc;
default:
return 0;
}
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = mInflater.inflate(R.layout.picker_entry, null);
} else {
v = convertView;
}
ChoosePDFItem item = mItems.get(position);
((TextView) v.findViewById(R.id.name)).setText(item.name);
((ImageView) v.findViewById(R.id.icon)).setImageResource(iconForType(item.type));
((ImageView) v.findViewById(R.id.icon)).setColorFilter(Color.argb(255, 0, 0, 0));
return v;
}
}
package com.artifex.mupdfdemo;
public class ChoosePDFItem {
enum Type {
PARENT, DIR, DOC
}
final public Type type;
final public String name;
public ChoosePDFItem (Type t, String n) {
type = t;
name = n;
}
}
/*
* Written by Doug Lea and Josh Bloch with assistance from members of
* JCP JSR-166 Expert Group and released to the public domain, as explained
* at http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.artifex.mupdfdemo;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Stack;
// BEGIN android-note
// removed link to collections framework docs
// END android-note
/**
* A linear collection that supports element insertion and removal at
* both ends. The name <i>deque</i> is short for "double ended queue"
* and is usually pronounced "deck". Most <tt>Deque</tt>
* implementations place no fixed limits on the number of elements
* they may contain, but this interface supports capacity-restricted
* deques as well as those with no fixed size limit.
*
* <p>This interface defines methods to access the elements at both
* ends of the deque. Methods are provided to insert, remove, and
* examine the element. Each of these methods exists in two forms:
* one throws an exception if the operation fails, the other returns a
* special value (either <tt>null</tt> or <tt>false</tt>, depending on
* the operation). The latter form of the insert operation is
* designed specifically for use with capacity-restricted
* <tt>Deque</tt> implementations; in most implementations, insert
* operations cannot fail.
*
* <p>The twelve methods described above are summarized in the
* following table:
*
* <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>
* </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>
* </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>
* </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>
* </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>
* </tr>
* </table>
*
* <p>This interface extends the {@link Queue} interface. When a deque is
* used as a queue, FIFO (First-In-First-Out) behavior results. Elements are
* added at the end of the deque and removed from the beginning. The methods
* inherited from the <tt>Queue</tt> interface are precisely equivalent to
* <tt>Deque</tt> methods as indicated in the following table:
*
* <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>
* </tr>
* <tr>
* <td>{@link Queue#add add(e)}</td>
* <td>{@link #addLast addLast(e)}</td>
* </tr>
* <tr>
* <td>{@link Queue#offer offer(e)}</td>
* <td>{@link #offerLast offerLast(e)}</td>
* </tr>
* <tr>
* <td>{@link Queue#remove remove()}</td>
* <td>{@link #removeFirst removeFirst()}</td>
* </tr>
* <tr>
* <td>{@link Queue#poll poll()}</td>
* <td>{@link #pollFirst pollFirst()}</td>
* </tr>
* <tr>
* <td>{@link Queue#element element()}</td>
* <td>{@link #getFirst getFirst()}</td>
* </tr>
* <tr>
* <td>{@link Queue#peek peek()}</td>
* <td>{@link #peek peekFirst()}</td>
* </tr>
* </table>
*
* <p>Deques can also be used as LIFO (Last-In-First-Out) stacks. This
* interface should be used in preference to the legacy {@link Stack} class.
* When a deque is used as a stack, elements are pushed and popped from the
* beginning of the deque. Stack methods are precisely equivalent to
* <tt>Deque</tt> methods as indicated in the table below:
*
* <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>
* </tr>
* <tr>
* <td>{@link #push push(e)}</td>
* <td>{@link #addFirst addFirst(e)}</td>
* </tr>
* <tr>
* <td>{@link #pop pop()}</td>
* <td>{@link #removeFirst removeFirst()}</td>
* </tr>
* <tr>
* <td>{@link #peek peek()}</td>
* <td>{@link #peekFirst peekFirst()}</td>
* </tr>
* </table>
*
* <p>Note that the {@link #peek peek} method works equally well when
* a deque is used as a queue or a stack; in either case, elements are
* drawn from the beginning of the deque.
*
* <p>This interface provides two methods to remove interior
* elements, {@link #removeFirstOccurrence removeFirstOccurrence} and
* {@link #removeLastOccurrence removeLastOccurrence}.
*
* <p>Unlike the {@link List} interface, this interface does not
* provide support for indexed access to elements.
*
* <p>While <tt>Deque</tt> implementations are not strictly required
* to prohibit the insertion of null elements, they are strongly
* encouraged to do so. Users of any <tt>Deque</tt> implementations
* that do allow null elements are strongly encouraged <i>not</i> to
* take advantage of the ability to insert nulls. This is so because
* <tt>null</tt> is used as a special return value by various methods
* to indicated that the deque is empty.
*
* <p><tt>Deque</tt> implementations generally do not define
* element-based versions of the <tt>equals</tt> and <tt>hashCode</tt>
* methods, but instead inherit the identity-based versions from class
* <tt>Object</tt>.
*
* @author Doug Lea
* @author Josh Bloch
* @since 1.6
* @param <E> the type of elements held in this collection
*/
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&nbsp;?&nbsp;e==null&nbsp;:&nbsp;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&nbsp;?&nbsp;e==null&nbsp;:&nbsp;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&nbsp;?&nbsp;e==null&nbsp;:&nbsp;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&nbsp;?&nbsp;e==null&nbsp;:&nbsp;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();
}
package com.artifex.mupdfdemo;
import android.net.Uri;
public abstract class FilePicker {
public interface FilePickerSupport {
void performPickFor(FilePicker picker);
}
private final FilePickerSupport support;
FilePicker(FilePickerSupport _support) {
support = _support;
}
void pick() {
support.performPickFor(this);
}
abstract void onPick(Uri uri);
}
package com.artifex.mupdfdemo;
public enum Hit {
Nothing, Widget, Annotation
}
package com.artifex.mupdfdemo;
import android.graphics.RectF;
public class LinkInfo {
final public RectF rect;
public LinkInfo(float l, float t, float r, float b) {
rect = new RectF(l, t, r, b);
}
public void acceptVisitor(LinkInfoVisitor visitor) {
}
}
package com.artifex.mupdfdemo;
public class LinkInfoExternal extends LinkInfo {
final public String url;
public LinkInfoExternal(float l, float t, float r, float b, String u) {
super(l, t, r, b);
url = u;
}
public void acceptVisitor(LinkInfoVisitor visitor) {
visitor.visitExternal(this);
}
}
package com.artifex.mupdfdemo;
public class LinkInfoInternal extends LinkInfo {
final public int pageNumber;
public LinkInfoInternal(float l, float t, float r, float b, int p) {
super(l, t, r, b);
pageNumber = p;
}
public void acceptVisitor(LinkInfoVisitor visitor) {
visitor.visitInternal(this);
}
}
package com.artifex.mupdfdemo;
public class LinkInfoRemote extends LinkInfo {
final public String fileSpec;
final public int pageNumber;
final public boolean newWindow;
public LinkInfoRemote(float l, float t, float r, float b, String f, int p, boolean n) {
super(l, t, r, b);
fileSpec = f;
pageNumber = p;
newWindow = n;
}
public void acceptVisitor(LinkInfoVisitor visitor) {
visitor.visitRemote(this);
}
}
package com.artifex.mupdfdemo;
abstract public class LinkInfoVisitor {
public abstract void visitInternal(LinkInfoInternal li);
public abstract void visitExternal(LinkInfoExternal li);
public abstract void visitRemote(LinkInfoRemote li);
}
package com.artifex.mupdfdemo;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.ViewAnimator;
import com.lonelypluto.pdfviewerlibrary.R;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
public class MuPDFActivity extends Activity implements FilePicker.FilePickerSupport {
/* The core rendering instance */
enum TopBarMode {Main, Search, Annot, Delete, More, Accept}
;
enum AcceptMode {Highlight, Underline, StrikeOut, Ink, CopyText}
;
private final int OUTLINE_REQUEST = 0;
private final int PRINT_REQUEST = 1;
private final int FILEPICK_REQUEST = 2;
private final int PROOF_REQUEST = 3;
private MuPDFCore core;
private String mFileName;
private MuPDFReaderView mDocView;
private View mButtonsView;
private boolean mButtonsVisible;
private EditText mPasswordView;
private TextView mFilenameView;
private SeekBar mPageSlider;
private int mPageSliderRes;
private TextView mPageNumberView;
private TextView mInfoView;
private ImageButton mSearchButton;
private ImageButton mReflowButton;
private ImageButton mOutlineButton;
private ImageButton mMoreButton;
private TextView mAnnotTypeText;
private ImageButton mAnnotButton;
private ViewAnimator mTopBarSwitcher;
private ImageButton mLinkButton;
private TopBarMode mTopBarMode = TopBarMode.Main;
private AcceptMode mAcceptMode;
private ImageButton mSearchBack;
private ImageButton mSearchFwd;
private EditText mSearchText;
private SearchTask mSearchTask;
private ImageButton mProofButton;
private ImageButton mSepsButton;
private AlertDialog.Builder mAlertBuilder;
private boolean mLinkHighlight = false;
private final Handler mHandler = new Handler();
private boolean mAlertsActive = false;
private boolean mReflow = false;
private AsyncTask<Void, Void, MuPDFAlert> mAlertTask;
private AlertDialog mAlertDialog;
private FilePicker mFilePicker;
private String mProofFile;
private boolean mSepEnabled[][];
static private AlertDialog.Builder gAlertBuilder;
static public AlertDialog.Builder getAlertBuilder() {
return gAlertBuilder;
}
public void createAlertWaiter() {
mAlertsActive = true;
// All mupdf library calls are performed on asynchronous tasks to avoid stalling
// the UI. Some calls can lead to javascript-invoked requests to display an
// alert dialog and collect a reply from the user. The task has to be blocked
// until the user's reply is received. This method creates an asynchronous task,
// the purpose of which is to wait of these requests and produce the dialog
// in response, while leaving the core blocked. When the dialog receives the
// user's response, it is sent to the core via replyToAlert, unblocking it.
// Another alert-waiting task is then created to pick up the next alert.
if (mAlertTask != null) {
mAlertTask.cancel(true);
mAlertTask = null;
}
if (mAlertDialog != null) {
mAlertDialog.cancel();
mAlertDialog = null;
}
mAlertTask = new AsyncTask<Void, Void, MuPDFAlert>() {
@Override
protected MuPDFAlert doInBackground(Void... arg0) {
if (!mAlertsActive)
return null;
return core.waitForAlert();
}
@Override
protected void onPostExecute(final MuPDFAlert result) {
// core.waitForAlert may return null when shutting down
if (result == null)
return;
final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3];
for (int i = 0; i < 3; i++)
pressed[i] = MuPDFAlert.ButtonPressed.None;
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mAlertDialog = null;
if (mAlertsActive) {
int index = 0;
switch (which) {
case AlertDialog.BUTTON1:
index = 0;
break;
case AlertDialog.BUTTON2:
index = 1;
break;
case AlertDialog.BUTTON3:
index = 2;
break;
}
result.buttonPressed = pressed[index];
// Send the user's response to the core, so that it can
// continue processing.
core.replyToAlert(result);
// Create another alert-waiter to pick up the next alert.
createAlertWaiter();
}
}
};
mAlertDialog = mAlertBuilder.create();
mAlertDialog.setTitle(result.title);
mAlertDialog.setMessage(result.message);
switch (result.iconType) {
case Error:
break;
case Warning:
break;
case Question:
break;
case Status:
break;
}
switch (result.buttonGroupType) {
case OkCancel:
mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.cancel), listener);
pressed[1] = MuPDFAlert.ButtonPressed.Cancel;
case Ok:
mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.okay), listener);
pressed[0] = MuPDFAlert.ButtonPressed.Ok;
break;
case YesNoCancel:
mAlertDialog.setButton(AlertDialog.BUTTON3, getString(R.string.cancel), listener);
pressed[2] = MuPDFAlert.ButtonPressed.Cancel;
case YesNo:
mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.yes), listener);
pressed[0] = MuPDFAlert.ButtonPressed.Yes;
mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.no), listener);
pressed[1] = MuPDFAlert.ButtonPressed.No;
break;
}
mAlertDialog.setOnCancelListener(new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
mAlertDialog = null;
if (mAlertsActive) {
result.buttonPressed = MuPDFAlert.ButtonPressed.None;
core.replyToAlert(result);
createAlertWaiter();
}
}
});
mAlertDialog.show();
}
};
mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor());
}
public void destroyAlertWaiter() {
mAlertsActive = false;
if (mAlertDialog != null) {
mAlertDialog.cancel();
mAlertDialog = null;
}
if (mAlertTask != null) {
mAlertTask.cancel(true);
mAlertTask = null;
}
}
private MuPDFCore openFile(String path) {
int lastSlashPos = path.lastIndexOf('/');
mFileName = new String(lastSlashPos == -1
? path
: path.substring(lastSlashPos + 1));
System.out.println("Trying to open " + path);
try {
core = new MuPDFCore(this, path);
// New file: drop the old outline data
OutlineActivityData.set(null);
} catch (Exception e) {
System.out.println(e);
return null;
} catch (OutOfMemoryError e) {
// out of memory is not an Exception, so we catch it separately.
System.out.println(e);
return null;
}
return core;
}
private MuPDFCore openBuffer(byte buffer[], String magic) {
System.out.println("Trying to open byte buffer");
try {
core = new MuPDFCore(this, buffer, magic);
// New file: drop the old outline data
OutlineActivityData.set(null);
} catch (Exception e) {
System.out.println(e);
return null;
}
return core;
}
// determine whether the current activity is a proofing activity.
public boolean isProofing() {
String format = core.fileFormat();
return (format.equals("GPROOF"));
}
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlertBuilder = new AlertDialog.Builder(this);
gAlertBuilder = mAlertBuilder; // keep a static copy of this that other classes can use
if (core == null) {
core = (MuPDFCore) getLastNonConfigurationInstance();
if (savedInstanceState != null && savedInstanceState.containsKey("FileName")) {
mFileName = savedInstanceState.getString("FileName");
}
}
if (core == null) {
Intent intent = getIntent();
byte buffer[] = null;
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
System.out.println("URI to open is: " + uri);
if (uri.toString().startsWith("content://")) {
String reason = null;
try {
InputStream is = getContentResolver().openInputStream(uri);
int len = is.available();
buffer = new byte[len];
is.read(buffer, 0, len);
is.close();
} catch (OutOfMemoryError e) {
System.out.println("Out of memory during buffer reading");
reason = e.toString();
} catch (Exception e) {
System.out.println("Exception reading from stream: " + e);
// Handle view requests from the Transformer Prime's file manager
// Hopefully other file managers will use this same scheme, if not
// using explicit paths.
// I'm hoping that this case below is no longer needed...but it's
// hard to test as the file manager seems to have changed in 4.x.
try {
Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null);
if (cursor.moveToFirst()) {
String str = cursor.getString(0);
if (str == null) {
reason = "Couldn't parse data in intent";
} else {
uri = Uri.parse(str);
}
}
} catch (Exception e2) {
System.out.println("Exception in Transformer Prime file manager code: " + e2);
reason = e2.toString();
}
}
if (reason != null) {
buffer = null;
Resources res = getResources();
AlertDialog alert = mAlertBuilder.create();
setTitle(String.format(res.getString(R.string.cannot_open_document_Reason), reason));
alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
alert.show();
return;
}
}
if (buffer != null) {
core = openBuffer(buffer, intent.getType());
} else {
String path = Uri.decode(uri.getEncodedPath());
if (path == null) {
path = uri.toString();
}
core = openFile(path);
}
SearchTaskResult.set(null);
}
if (core != null && core.needsPassword()) {
requestPassword(savedInstanceState);
return;
}
if (core != null && core.countPages() == 0) {
core = null;
}
}
if (core == null) {
AlertDialog alert = mAlertBuilder.create();
alert.setTitle(R.string.cannot_open_document);
alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
alert.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
alert.show();
return;
}
createUI(savedInstanceState);
// hide the proof button if this file can't be proofed
if (!core.canProof()) {
mProofButton.setVisibility(View.INVISIBLE);
}
if (isProofing()) {
// start the activity with a new array
mSepEnabled = null;
// show the separations button
mSepsButton.setVisibility(View.VISIBLE);
// hide some other buttons
mLinkButton.setVisibility(View.INVISIBLE);
mReflowButton.setVisibility(View.INVISIBLE);
mOutlineButton.setVisibility(View.INVISIBLE);
mSearchButton.setVisibility(View.INVISIBLE);
mMoreButton.setVisibility(View.INVISIBLE);
} else {
// hide the separations button
mSepsButton.setVisibility(View.INVISIBLE);
}
}
public void requestPassword(final Bundle savedInstanceState) {
mPasswordView = new EditText(this);
mPasswordView.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
mPasswordView.setTransformationMethod(new PasswordTransformationMethod());
AlertDialog alert = mAlertBuilder.create();
alert.setTitle(R.string.enter_password);
alert.setView(mPasswordView);
alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.okay),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (core.authenticatePassword(mPasswordView.getText().toString())) {
createUI(savedInstanceState);
} else {
requestPassword(savedInstanceState);
}
}
});
alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
alert.show();
}
public void createUI(Bundle savedInstanceState) {
if (core == null)
return;
// Now create the UI.
// First create the document view
mDocView = new MuPDFReaderView(this) {
@Override
protected void onMoveToChild(int i) {
if (core == null)
return;
mPageNumberView.setText(String.format("%d / %d", i + 1,
core.countPages()));
mPageSlider.setMax((core.countPages() - 1) * mPageSliderRes);
mPageSlider.setProgress(i * mPageSliderRes);
super.onMoveToChild(i);
}
@Override
protected void onTapMainDocArea() {
if (!mButtonsVisible) {
showButtons();
} else {
if (mTopBarMode == TopBarMode.Main)
hideButtons();
}
}
@Override
protected void onDocMotion() {
hideButtons();
}
@Override
protected void onHit(Hit item) {
switch (mTopBarMode) {
case Annot:
if (item == Hit.Annotation) {
showButtons();
mTopBarMode = TopBarMode.Delete;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
break;
case Delete:
mTopBarMode = TopBarMode.Annot;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
// fall through
default:
// Not in annotation editing mode, but the pageview will
// still select and highlight hit annotations, so
// deselect just in case.
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
if (pageView != null)
pageView.deselectAnnotation();
break;
}
}
};
mDocView.setAdapter(new MuPDFPageAdapter(this, this, core));
mSearchTask = new SearchTask(this, core) {
@Override
protected void onTextFound(SearchTaskResult result) {
SearchTaskResult.set(result);
// Ask the ReaderView to move to the resulting page
mDocView.setDisplayedViewIndex(result.pageNumber);
// Make the ReaderView act on the change to SearchTaskResult
// via overridden onChildSetup method.
mDocView.resetupChildren();
}
};
// Make the buttons overlay, and store all its
// controls in variables
makeButtonsView();
// Set up the page slider
int smax = Math.max(core.countPages() - 1, 1);
mPageSliderRes = ((10 + smax - 1) / smax) * 2;
// Set the file-name text
mFilenameView.setText(mFileName);
// Activate the seekbar
mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
mDocView.setDisplayedViewIndex((seekBar.getProgress() + mPageSliderRes / 2) / mPageSliderRes);
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
updatePageNumView((progress + mPageSliderRes / 2) / mPageSliderRes);
}
});
// Activate the search-preparing button
mSearchButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
searchModeOn();
}
});
// Activate the reflow button
mReflowButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
toggleReflow();
}
});
if (core.fileFormat().startsWith("PDF") && core.isUnencryptedPDF() && !core.wasOpenedFromBuffer()) {
mAnnotButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mTopBarMode = TopBarMode.Annot;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
});
} else {
mAnnotButton.setVisibility(View.GONE);
}
// Search invoking buttons are disabled while there is no text specified
mSearchBack.setEnabled(false);
mSearchFwd.setEnabled(false);
mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128));
mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128));
// React to interaction with the text widget
mSearchText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
boolean haveText = s.toString().length() > 0;
setButtonEnabled(mSearchBack, haveText);
setButtonEnabled(mSearchFwd, haveText);
// Remove any previous search results
if (SearchTaskResult.get() != null && !mSearchText.getText().toString().equals(SearchTaskResult.get().txt)) {
SearchTaskResult.set(null);
mDocView.resetupChildren();
}
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
});
//React to Done button on keyboard
mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE)
search(1);
return false;
}
});
mSearchText.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER)
search(1);
return false;
}
});
// Activate search invoking buttons
mSearchBack.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
search(-1);
}
});
mSearchFwd.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
search(1);
}
});
mLinkButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
setLinkHighlight(!mLinkHighlight);
}
});
if (core.hasOutline()) {
mOutlineButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
OutlineItem outline[] = core.getOutline();
if (outline != null) {
OutlineActivityData.get().items = outline;
Intent intent = new Intent(MuPDFActivity.this, OutlineActivity.class);
startActivityForResult(intent, OUTLINE_REQUEST);
}
}
});
} else {
mOutlineButton.setVisibility(View.GONE);
}
// Reenstate last state if it was recorded
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
mDocView.setDisplayedViewIndex(prefs.getInt("page" + mFileName, 0));
if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false))
showButtons();
if (savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false))
searchModeOn();
if (savedInstanceState != null && savedInstanceState.getBoolean("ReflowMode", false))
reflowModeSet(true);
// Stick the document view and the buttons overlay into a parent view
RelativeLayout layout = new RelativeLayout(this);
layout.addView(mDocView);
layout.addView(mButtonsView);
setContentView(layout);
if (isProofing()) {
// go to the current page
int currentPage = getIntent().getIntExtra("startingPage", 0);
mDocView.setDisplayedViewIndex(currentPage);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case OUTLINE_REQUEST:
if (resultCode >= 0)
mDocView.setDisplayedViewIndex(resultCode);
break;
case PRINT_REQUEST:
if (resultCode == RESULT_CANCELED)
showInfo(getString(R.string.print_failed));
break;
case FILEPICK_REQUEST:
if (mFilePicker != null && resultCode == RESULT_OK)
mFilePicker.onPick(data.getData());
case PROOF_REQUEST:
// we're returning from a proofing activity
if (mProofFile != null) {
core.endProof(mProofFile);
mProofFile = null;
}
// return the top bar to default
mTopBarMode = TopBarMode.Main;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
super.onActivityResult(requestCode, resultCode, data);
}
public Object onRetainNonConfigurationInstance() {
MuPDFCore mycore = core;
core = null;
return mycore;
}
private void reflowModeSet(boolean reflow) {
mReflow = reflow;
mDocView.setAdapter(mReflow ? new MuPDFReflowAdapter(this, core) : new MuPDFPageAdapter(this, this, core));
mReflowButton.setColorFilter(mReflow ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255));
setButtonEnabled(mAnnotButton, !reflow);
setButtonEnabled(mSearchButton, !reflow);
if (reflow) setLinkHighlight(false);
setButtonEnabled(mLinkButton, !reflow);
setButtonEnabled(mMoreButton, !reflow);
mDocView.refresh(mReflow);
}
private void toggleReflow() {
reflowModeSet(!mReflow);
showInfo(mReflow ? getString(R.string.entering_reflow_mode) : getString(R.string.leaving_reflow_mode));
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mFileName != null && mDocView != null) {
outState.putString("FileName", mFileName);
// Store current page in the prefs against the file name,
// so that we can pick it up each time the file is loaded
// Other info is needed only for screen-orientation change,
// so it can go in the bundle
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putInt("page" + mFileName, mDocView.getDisplayedViewIndex());
edit.commit();
}
if (!mButtonsVisible)
outState.putBoolean("ButtonsHidden", true);
if (mTopBarMode == TopBarMode.Search)
outState.putBoolean("SearchMode", true);
if (mReflow)
outState.putBoolean("ReflowMode", true);
}
@Override
protected void onPause() {
super.onPause();
if (mSearchTask != null)
mSearchTask.stop();
if (mFileName != null && mDocView != null) {
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putInt("page" + mFileName, mDocView.getDisplayedViewIndex());
edit.commit();
}
}
public void onDestroy() {
if (mDocView != null) {
mDocView.applyToChildren(new ReaderView.ViewMapper() {
public void applyToView(View view) {
((MuPDFView) view).releaseBitmaps();
}
});
}
if (core != null)
core.onDestroy();
if (mAlertTask != null) {
mAlertTask.cancel(true);
mAlertTask = null;
}
core = null;
super.onDestroy();
}
private void setButtonEnabled(ImageButton button, boolean enabled) {
button.setEnabled(enabled);
button.setColorFilter(enabled ? Color.argb(255, 255, 255, 255) : Color.argb(255, 128, 128, 128));
}
private void setLinkHighlight(boolean highlight) {
mLinkHighlight = highlight;
// LINK_COLOR tint
mLinkButton.setColorFilter(highlight ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255));
// Inform pages of the change.
mDocView.setLinksEnabled(highlight);
}
private void showButtons() {
if (core == null)
return;
if (!mButtonsVisible) {
mButtonsVisible = true;
// Update page number text and slider
int index = mDocView.getDisplayedViewIndex();
updatePageNumView(index);
mPageSlider.setMax((core.countPages() - 1) * mPageSliderRes);
mPageSlider.setProgress(index * mPageSliderRes);
if (mTopBarMode == TopBarMode.Search) {
mSearchText.requestFocus();
showKeyboard();
}
Animation anim = new TranslateAnimation(0, 0, -mTopBarSwitcher.getHeight(), 0);
anim.setDuration(200);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
mTopBarSwitcher.setVisibility(View.VISIBLE);
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
}
});
mTopBarSwitcher.startAnimation(anim);
anim = new TranslateAnimation(0, 0, mPageSlider.getHeight(), 0);
anim.setDuration(200);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
mPageSlider.setVisibility(View.VISIBLE);
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
mPageNumberView.setVisibility(View.VISIBLE);
}
});
mPageSlider.startAnimation(anim);
}
}
private void hideButtons() {
if (mButtonsVisible) {
mButtonsVisible = false;
hideKeyboard();
Animation anim = new TranslateAnimation(0, 0, 0, -mTopBarSwitcher.getHeight());
anim.setDuration(200);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
mTopBarSwitcher.setVisibility(View.INVISIBLE);
}
});
mTopBarSwitcher.startAnimation(anim);
anim = new TranslateAnimation(0, 0, 0, mPageSlider.getHeight());
anim.setDuration(200);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
mPageNumberView.setVisibility(View.INVISIBLE);
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
mPageSlider.setVisibility(View.INVISIBLE);
}
});
mPageSlider.startAnimation(anim);
}
}
private void searchModeOn() {
if (mTopBarMode != TopBarMode.Search) {
mTopBarMode = TopBarMode.Search;
//Focus on EditTextWidget
mSearchText.requestFocus();
showKeyboard();
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
}
private void searchModeOff() {
if (mTopBarMode == TopBarMode.Search) {
mTopBarMode = TopBarMode.Main;
hideKeyboard();
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
SearchTaskResult.set(null);
// Make the ReaderView act on the change to mSearchTaskResult
// via overridden onChildSetup method.
mDocView.resetupChildren();
}
}
private void updatePageNumView(int index) {
if (core == null)
return;
mPageNumberView.setText(String.format("%d / %d", index + 1, core.countPages()));
}
private void printDoc() {
if (!core.fileFormat().startsWith("PDF")) {
showInfo(getString(R.string.format_currently_not_supported));
return;
}
Intent myIntent = getIntent();
Uri docUri = myIntent != null ? myIntent.getData() : null;
if (docUri == null) {
showInfo(getString(R.string.print_failed));
}
if (docUri.getScheme() == null)
docUri = Uri.parse("file://" + docUri.toString());
Intent printIntent = new Intent(this, PrintDialogActivity.class);
printIntent.setDataAndType(docUri, "aplication/pdf");
printIntent.putExtra("title", mFileName);
startActivityForResult(printIntent, PRINT_REQUEST);
}
private void showInfo(String message) {
mInfoView.setText(message);
int currentApiVersion = android.os.Build.VERSION.SDK_INT;
if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
SafeAnimatorInflater safe = new SafeAnimatorInflater((Activity) this, R.animator.info, (View) mInfoView);
} else {
mInfoView.setVisibility(View.VISIBLE);
mHandler.postDelayed(new Runnable() {
public void run() {
mInfoView.setVisibility(View.INVISIBLE);
}
}, 500);
}
}
private void makeButtonsView() {
mButtonsView = getLayoutInflater().inflate(R.layout.buttons, null);
mFilenameView = (TextView) mButtonsView.findViewById(R.id.docNameText);
mPageSlider = (SeekBar) mButtonsView.findViewById(R.id.pageSlider);
mPageNumberView = (TextView) mButtonsView.findViewById(R.id.pageNumber);
mInfoView = (TextView) mButtonsView.findViewById(R.id.info);
mSearchButton = (ImageButton) mButtonsView.findViewById(R.id.searchButton);
mReflowButton = (ImageButton) mButtonsView.findViewById(R.id.reflowButton);
mOutlineButton = (ImageButton) mButtonsView.findViewById(R.id.outlineButton);
mAnnotButton = (ImageButton) mButtonsView.findViewById(R.id.editAnnotButton);
mAnnotTypeText = (TextView) mButtonsView.findViewById(R.id.annotType);
mTopBarSwitcher = (ViewAnimator) mButtonsView.findViewById(R.id.switcher);
mSearchBack = (ImageButton) mButtonsView.findViewById(R.id.searchBack);
mSearchFwd = (ImageButton) mButtonsView.findViewById(R.id.searchForward);
mSearchText = (EditText) mButtonsView.findViewById(R.id.searchText);
mLinkButton = (ImageButton) mButtonsView.findViewById(R.id.linkButton);
mMoreButton = (ImageButton) mButtonsView.findViewById(R.id.moreButton);
mProofButton = (ImageButton) mButtonsView.findViewById(R.id.proofButton);
mSepsButton = (ImageButton) mButtonsView.findViewById(R.id.sepsButton);
mTopBarSwitcher.setVisibility(View.INVISIBLE);
mPageNumberView.setVisibility(View.INVISIBLE);
mInfoView.setVisibility(View.INVISIBLE);
mPageSlider.setVisibility(View.INVISIBLE);
if (!core.gprfSupported()) {
mProofButton.setVisibility(View.INVISIBLE);
}
mSepsButton.setVisibility(View.INVISIBLE);
}
public void OnMoreButtonClick(View v) {
mTopBarMode = TopBarMode.More;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnCancelMoreButtonClick(View v) {
mTopBarMode = TopBarMode.Main;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnPrintButtonClick(View v) {
printDoc();
}
// start a proof activity with the given resolution.
public void proofWithResolution(int resolution) {
mProofFile = core.startProof(resolution);
Uri uri = Uri.parse("file://" + mProofFile);
Intent intent = new Intent(this, MuPDFActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
// add the current page so it can be found when the activity is running
intent.putExtra("startingPage", mDocView.getDisplayedViewIndex());
startActivityForResult(intent, PROOF_REQUEST);
}
public void OnProofButtonClick(final View v) {
// set up the menu or resolutions.
final PopupMenu popup = new PopupMenu(this, v);
popup.getMenu().add(0, 1, 0, "Select a resolution:");
popup.getMenu().add(0, 72, 0, "72");
popup.getMenu().add(0, 96, 0, "96");
popup.getMenu().add(0, 150, 0, "150");
popup.getMenu().add(0, 300, 0, "300");
popup.getMenu().add(0, 600, 0, "600");
popup.getMenu().add(0, 1200, 0, "1200");
popup.getMenu().add(0, 2400, 0, "2400");
// prevent the first item from being dismissed.
// is there not a better way to do this? It requires minimum API 14
MenuItem item = popup.getMenu().getItem(0);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
item.setActionView(new View(v.getContext()));
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return false;
}
});
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
if (id != 1) {
// it's a resolution. The id is also the resolution value
proofWithResolution(id);
return true;
}
return false;
}
});
popup.show();
}
public void OnSepsButtonClick(final View v) {
if (isProofing()) {
// get the current page
final int currentPage = mDocView.getDisplayedViewIndex();
// buid a popup menu based on the given separations
final PopupMenu menu = new PopupMenu(this, v);
// This makes the popup menu display icons, which by default it does not do.
// I worry that this relies on the internals of PopupMenu, which could change.
try {
Field[] fields = menu.getClass().getDeclaredFields();
for (Field field : fields) {
if ("mPopup".equals(field.getName())) {
field.setAccessible(true);
Object menuPopupHelper = field.get(menu);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// get the maximum number of seps on any page.
// We use this to dimension an array further down
int maxSeps = 0;
int numPages = core.countPages();
for (int page = 0; page < numPages; page++) {
int numSeps = core.getNumSepsOnPage(page);
if (numSeps > maxSeps)
maxSeps = numSeps;
}
// if this is the first time, create the "enabled" array
if (mSepEnabled == null) {
mSepEnabled = new boolean[numPages][maxSeps];
for (int page = 0; page < numPages; page++) {
for (int i = 0; i < maxSeps; i++)
mSepEnabled[page][i] = true;
}
}
// count the seps on this page
int numSeps = core.getNumSepsOnPage(currentPage);
// for each sep,
for (int i = 0; i < numSeps; i++) {
// // Robin use this to skip separations
// if (i==12)
// break;
// get the name
Separation sep = core.getSep(currentPage, i);
String name = sep.name;
// make a checkable menu item with that name
// and the separation index as the id
MenuItem item = menu.getMenu().add(0, i, 0, name + " ");
item.setCheckable(true);
// set an icon that's the right color
int iconSize = 48;
int alpha = (sep.rgba >> 24) & 0xFF;
int red = (sep.rgba >> 16) & 0xFF;
int green = (sep.rgba >> 8) & 0xFF;
int blue = (sep.rgba >> 0) & 0xFF;
int color = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
ShapeDrawable swatch = new ShapeDrawable(new RectShape());
swatch.setIntrinsicHeight(iconSize);
swatch.setIntrinsicWidth(iconSize);
swatch.setBounds(new Rect(0, 0, iconSize, iconSize));
swatch.getPaint().setColor(color);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
item.setIcon(swatch);
// check it (or not)
item.setChecked(mSepEnabled[currentPage][i]);
// establishing a menu item listener
item.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// someone tapped a menu item. get the ID
int sep = item.getItemId();
// toggle the sep
mSepEnabled[currentPage][sep] = !mSepEnabled[currentPage][sep];
item.setChecked(mSepEnabled[currentPage][sep]);
core.controlSepOnPage(currentPage, sep, !mSepEnabled[currentPage][sep]);
// prevent the menu from being dismissed by these items
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
item.setActionView(new View(v.getContext()));
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return false;
}
});
return false;
}
});
// tell core to enable or disable each sep as appropriate
// but don't refresh the page yet.
core.controlSepOnPage(currentPage, i, !mSepEnabled[currentPage][i]);
}
// add one for done
MenuItem itemDone = menu.getMenu().add(0, 0, 0, "Done");
itemDone.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// refresh the view
mDocView.refresh(false);
return true;
}
});
// show the menu
menu.show();
}
}
public void OnCopyTextButtonClick(View v) {
mTopBarMode = TopBarMode.Accept;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mAcceptMode = AcceptMode.CopyText;
mDocView.setMode(MuPDFReaderView.Mode.Selecting);
mAnnotTypeText.setText(getString(R.string.copy_text));
showInfo(getString(R.string.select_text));
}
public void OnEditAnnotButtonClick(View v) {
mTopBarMode = TopBarMode.Annot;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnCancelAnnotButtonClick(View v) {
mTopBarMode = TopBarMode.More;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnHighlightButtonClick(View v) {
mTopBarMode = TopBarMode.Accept;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mAcceptMode = AcceptMode.Highlight;
mDocView.setMode(MuPDFReaderView.Mode.Selecting);
mAnnotTypeText.setText(R.string.highlight);
showInfo(getString(R.string.select_text));
}
public void OnUnderlineButtonClick(View v) {
mTopBarMode = TopBarMode.Accept;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mAcceptMode = AcceptMode.Underline;
mDocView.setMode(MuPDFReaderView.Mode.Selecting);
mAnnotTypeText.setText(R.string.underline);
showInfo(getString(R.string.select_text));
}
public void OnStrikeOutButtonClick(View v) {
mTopBarMode = TopBarMode.Accept;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mAcceptMode = AcceptMode.StrikeOut;
mDocView.setMode(MuPDFReaderView.Mode.Selecting);
mAnnotTypeText.setText(R.string.strike_out);
showInfo(getString(R.string.select_text));
}
public void OnInkButtonClick(View v) {
mTopBarMode = TopBarMode.Accept;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mAcceptMode = AcceptMode.Ink;
mDocView.setMode(MuPDFReaderView.Mode.Drawing);
mAnnotTypeText.setText(R.string.ink);
showInfo(getString(R.string.draw_annotation));
}
public void OnCancelAcceptButtonClick(View v) {
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
if (pageView != null) {
pageView.deselectText();
pageView.cancelDraw();
}
mDocView.setMode(MuPDFReaderView.Mode.Viewing);
switch (mAcceptMode) {
case CopyText:
mTopBarMode = TopBarMode.More;
break;
default:
mTopBarMode = TopBarMode.Annot;
break;
}
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnAcceptButtonClick(View v) {
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
boolean success = false;
switch (mAcceptMode) {
case CopyText:
if (pageView != null)
success = pageView.copySelection();
mTopBarMode = TopBarMode.More;
showInfo(success ? getString(R.string.copied_to_clipboard) : getString(R.string.no_text_selected));
break;
case Highlight:
if (pageView != null)
success = pageView.markupSelection(Annotation.Type.HIGHLIGHT);
mTopBarMode = TopBarMode.Annot;
if (!success)
showInfo(getString(R.string.no_text_selected));
break;
case Underline:
if (pageView != null)
success = pageView.markupSelection(Annotation.Type.UNDERLINE);
mTopBarMode = TopBarMode.Annot;
if (!success)
showInfo(getString(R.string.no_text_selected));
break;
case StrikeOut:
if (pageView != null)
success = pageView.markupSelection(Annotation.Type.STRIKEOUT);
mTopBarMode = TopBarMode.Annot;
if (!success)
showInfo(getString(R.string.no_text_selected));
break;
case Ink:
if (pageView != null)
success = pageView.saveDraw();
mTopBarMode = TopBarMode.Annot;
if (!success)
showInfo(getString(R.string.nothing_to_save));
break;
}
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
mDocView.setMode(MuPDFReaderView.Mode.Viewing);
}
public void OnCancelSearchButtonClick(View v) {
searchModeOff();
}
public void OnDeleteButtonClick(View v) {
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
if (pageView != null)
pageView.deleteSelectedAnnotation();
mTopBarMode = TopBarMode.Annot;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
public void OnCancelDeleteButtonClick(View v) {
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
if (pageView != null)
pageView.deselectAnnotation();
mTopBarMode = TopBarMode.Annot;
mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal());
}
private void showKeyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.showSoftInput(mSearchText, 0);
}
private void hideKeyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0);
}
private void search(int direction) {
hideKeyboard();
int displayPage = mDocView.getDisplayedViewIndex();
SearchTaskResult r = SearchTaskResult.get();
int searchPage = r != null ? r.pageNumber : -1;
mSearchTask.go(mSearchText.getText().toString(), direction, displayPage, searchPage);
}
@Override
public boolean onSearchRequested() {
if (mButtonsVisible && mTopBarMode == TopBarMode.Search) {
hideButtons();
} else {
showButtons();
searchModeOn();
}
return super.onSearchRequested();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (mButtonsVisible && mTopBarMode != TopBarMode.Search) {
hideButtons();
} else {
showButtons();
searchModeOff();
}
return super.onPrepareOptionsMenu(menu);
}
@Override
protected void onStart() {
if (core != null) {
core.startAlerts();
createAlertWaiter();
}
super.onStart();
}
@Override
protected void onStop() {
if (core != null) {
destroyAlertWaiter();
core.stopAlerts();
}
super.onStop();
}
@Override
public void onBackPressed() {
if (core != null && core.hasChanges()) {
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE)
core.save();
finish();
}
};
AlertDialog alert = mAlertBuilder.create();
alert.setTitle("MuPDF");
alert.setMessage(getString(R.string.document_has_changes_save_them_));
alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.yes), listener);
alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.no), listener);
alert.show();
} else {
super.onBackPressed();
}
}
@Override
public void performPickFor(FilePicker picker) {
mFilePicker = picker;
Intent intent = new Intent(this, ChoosePDFActivity.class);
intent.setAction(ChoosePDFActivity.PICK_KEY_FILE);
startActivityForResult(intent, FILEPICK_REQUEST);
}
}
package com.artifex.mupdfdemo;
public class MuPDFAlert {
public enum IconType {Error,Warning,Question,Status};
public enum ButtonPressed {None,Ok,Cancel,No,Yes};
public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel};
public final String message;
public final IconType iconType;
public final ButtonGroupType buttonGroupType;
public final String title;
public ButtonPressed buttonPressed;
MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) {
message = aMessage;
iconType = aIconType;
buttonGroupType = aButtonGroupType;
title = aTitle;
buttonPressed = aButtonPressed;
}
}
package com.artifex.mupdfdemo;
// Version of MuPDFAlert without enums to simplify JNI
public class MuPDFAlertInternal {
public final String message;
public final int iconType;
public final int buttonGroupType;
public final String title;
public int buttonPressed;
MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) {
message = aMessage;
iconType = aIconType;
buttonGroupType = aButtonGroupType;
title = aTitle;
buttonPressed = aButtonPressed;
}
MuPDFAlertInternal(MuPDFAlert alert) {
message = alert.message;
iconType = alert.iconType.ordinal();
buttonGroupType = alert.buttonGroupType.ordinal();
title = alert.message;
buttonPressed = alert.buttonPressed.ordinal();
}
MuPDFAlert toAlert() {
return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]);
}
}
package com.artifex.mupdfdemo;
public abstract class MuPDFCancellableTaskDefinition<Params, Result> implements CancellableTaskDefinition<Params, Result>
{
private MuPDFCore.Cookie cookie;
public MuPDFCancellableTaskDefinition(MuPDFCore core)
{
this.cookie = core.new Cookie();
}
@Override
public void doCancel()
{
if (cookie == null)
return;
cookie.abort();
}
@Override
public void doCleanup()
{
if (cookie == null)
return;
cookie.destroy();
cookie = null;
}
@Override
public final Result doInBackground(Params ... params)
{
return doInBackground(cookie, params);
}
public abstract Result doInBackground(MuPDFCore.Cookie cookie, Params ... params);
}
package com.artifex.mupdfdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.RectF;
import java.util.ArrayList;
import com.lonelypluto.pdfviewerlibrary.R;
public class MuPDFCore {
/* load our native library */
private static boolean gs_so_available = false;
static {
try {
System.out.println("Loading dll");
System.loadLibrary("mupdf_java");
System.out.println("Loaded dll");
Boolean flag = gprfSupportedInternal();
if (flag) {
try {
System.loadLibrary("gs");
gs_so_available = true;
} catch (UnsatisfiedLinkError e) {
gs_so_available = false;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* Readable members */
private int numPages = -1;
private float pageWidth;
private float pageHeight;
private long globals;
private byte fileBuffer[];
private String file_format;
private boolean isUnencryptedPDF;
private final boolean wasOpenedFromBuffer;
/* The native functions */
private static native boolean gprfSupportedInternal();
private native long openFile(String filename);
private native long openBuffer(String magic);
private native String fileFormatInternal();
private native boolean isUnencryptedPDFInternal();
private native int countPagesInternal();
private native void gotoPageInternal(int localActionPageNum);
private native float getPageWidth();
private native float getPageHeight();
private native void drawPage(Bitmap bitmap,
int pageW, int pageH,
int patchX, int patchY,
int patchW, int patchH,
long cookiePtr);
private native void updatePageInternal(Bitmap bitmap,
int page,
int pageW, int pageH,
int patchX, int patchY,
int patchW, int patchH,
long cookiePtr);
private native RectF[] searchPage(String text);
private native TextChar[][][][] text();
private native byte[] textAsHtml();
private native void addMarkupAnnotationInternal(PointF[] quadPoints, int type);
// private native void addInkAnnotationInternal(PointF[][] arcs);
/**
* ����
*
* @param arcs ��
* @param colorR ��ɫֵ R
* @param colorG ��ɫֵ G
* @param colorB ��ɫֵ B
* @param inkThickness �ߵĴ�ϸ
*/
private native void addInkAnnotationInternal(PointF[][] arcs, float colorR, float colorG, float colorB, float inkThickness);
private native void deleteAnnotationInternal(int annot_index);
private native int passClickEventInternal(int page, float x, float y);
private native void setFocusedWidgetChoiceSelectedInternal(String[] selected);
private native String[] getFocusedWidgetChoiceSelected();
private native String[] getFocusedWidgetChoiceOptions();
private native int getFocusedWidgetSignatureState();
private native String checkFocusedSignatureInternal();
private native boolean signFocusedSignatureInternal(String keyFile, String password);
private native int setFocusedWidgetTextInternal(String text);
private native String getFocusedWidgetTextInternal();
private native int getFocusedWidgetTypeInternal();
private native LinkInfo[] getPageLinksInternal(int page);
private native RectF[] getWidgetAreasInternal(int page);
private native Annotation[] getAnnotationsInternal(int page);
private native OutlineItem[] getOutlineInternal();
private native boolean hasOutlineInternal();
private native boolean needsPasswordInternal();
private native boolean authenticatePasswordInternal(String password);
private native MuPDFAlertInternal waitForAlertInternal();
private native void replyToAlertInternal(MuPDFAlertInternal alert);
private native void startAlertsInternal();
private native void stopAlertsInternal();
private native void destroying();
private native boolean hasChangesInternal();
private native void saveInternal();
private native long createCookie();
private native void destroyCookie(long cookie);
private native void abortCookie(long cookie);
private native String startProofInternal(int resolution);
private native void endProofInternal(String filename);
private native int getNumSepsOnPageInternal(int page);
private native int controlSepOnPageInternal(int page, int sep, boolean disable);
private native Separation getSepInternal(int page, int sep);
public native boolean javascriptSupported();
public class Cookie {
private final long cookiePtr;
public Cookie() {
cookiePtr = createCookie();
if (cookiePtr == 0) {
throw new OutOfMemoryError();
}
}
public void abort() {
abortCookie(cookiePtr);
}
public void destroy() {
// We could do this in finalize, but there's no guarantee that
// a finalize will occur before the muPDF context occurs.
destroyCookie(cookiePtr);
}
}
public MuPDFCore(Context context, String filename) throws Exception {
globals = openFile(filename);
if (globals == 0) {
throw new Exception(String.format(context.getString(R.string.cannot_open_file_Path), filename));
}
file_format = fileFormatInternal();
isUnencryptedPDF = isUnencryptedPDFInternal();
wasOpenedFromBuffer = false;
}
public MuPDFCore(Context context, byte buffer[], String magic) throws Exception {
fileBuffer = buffer;
globals = openBuffer(magic != null ? magic : "");
if (globals == 0) {
throw new Exception(context.getString(R.string.cannot_open_buffer));
}
file_format = fileFormatInternal();
isUnencryptedPDF = isUnencryptedPDFInternal();
wasOpenedFromBuffer = true;
}
public int countPages() {
if (numPages < 0)
numPages = countPagesSynchronized();
return numPages;
}
public String fileFormat() {
return file_format;
}
public boolean isUnencryptedPDF() {
return isUnencryptedPDF;
}
public boolean wasOpenedFromBuffer() {
return wasOpenedFromBuffer;
}
private synchronized int countPagesSynchronized() {
return countPagesInternal();
}
/* Shim function */
private void gotoPage(int page) {
if (page > numPages - 1)
page = numPages - 1;
else if (page < 0)
page = 0;
gotoPageInternal(page);
this.pageWidth = getPageWidth();
this.pageHeight = getPageHeight();
}
public synchronized PointF getPageSize(int page) {
gotoPage(page);
return new PointF(pageWidth, pageHeight);
}
public MuPDFAlert waitForAlert() {
MuPDFAlertInternal alert = waitForAlertInternal();
return alert != null ? alert.toAlert() : null;
}
public void replyToAlert(MuPDFAlert alert) {
replyToAlertInternal(new MuPDFAlertInternal(alert));
}
public void stopAlerts() {
stopAlertsInternal();
}
public void startAlerts() {
startAlertsInternal();
}
public synchronized void onDestroy() {
destroying();
globals = 0;
}
public synchronized void drawPage(Bitmap bm, int page,
int pageW, int pageH,
int patchX, int patchY,
int patchW, int patchH,
Cookie cookie) {
gotoPage(page);
drawPage(bm, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr);
}
public synchronized void updatePage(Bitmap bm, int page,
int pageW, int pageH,
int patchX, int patchY,
int patchW, int patchH,
Cookie cookie) {
updatePageInternal(bm, page, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr);
}
public synchronized PassClickResult passClickEvent(int page, float x, float y) {
boolean changed = passClickEventInternal(page, x, y) != 0;
switch (WidgetType.values()[getFocusedWidgetTypeInternal()]) {
case TEXT:
return new PassClickResultText(changed, getFocusedWidgetTextInternal());
case LISTBOX:
case COMBOBOX:
return new PassClickResultChoice(changed, getFocusedWidgetChoiceOptions(), getFocusedWidgetChoiceSelected());
case SIGNATURE:
return new PassClickResultSignature(changed, getFocusedWidgetSignatureState());
default:
return new PassClickResult(changed);
}
}
public synchronized boolean setFocusedWidgetText(int page, String text) {
boolean success;
gotoPage(page);
success = setFocusedWidgetTextInternal(text) != 0 ? true : false;
return success;
}
public synchronized void setFocusedWidgetChoiceSelected(String[] selected) {
setFocusedWidgetChoiceSelectedInternal(selected);
}
public synchronized String checkFocusedSignature() {
return checkFocusedSignatureInternal();
}
public synchronized boolean signFocusedSignature(String keyFile, String password) {
return signFocusedSignatureInternal(keyFile, password);
}
public synchronized LinkInfo[] getPageLinks(int page) {
return getPageLinksInternal(page);
}
public synchronized RectF[] getWidgetAreas(int page) {
return getWidgetAreasInternal(page);
}
public synchronized Annotation[] getAnnoations(int page) {
return getAnnotationsInternal(page);
}
public synchronized RectF[] searchPage(int page, String text) {
gotoPage(page);
return searchPage(text);
}
public synchronized byte[] html(int page) {
gotoPage(page);
return textAsHtml();
}
public synchronized TextWord[][] textLines(int page) {
gotoPage(page);
TextChar[][][][] chars = text();
// The text of the page held in a hierarchy (blocks, lines, spans).
// Currently we don't need to distinguish the blocks level or
// the spans, and we need to collect the text into words.
ArrayList<TextWord[]> lns = new ArrayList<TextWord[]>();
for (TextChar[][][] bl : chars) {
if (bl == null)
continue;
for (TextChar[][] ln : bl) {
ArrayList<TextWord> wds = new ArrayList<TextWord>();
TextWord wd = new TextWord();
for (TextChar[] sp : ln) {
for (TextChar tc : sp) {
if (tc.c != ' ') {
wd.Add(tc);
} else if (wd.w.length() > 0) {
wds.add(wd);
wd = new TextWord();
}
}
}
if (wd.w.length() > 0)
wds.add(wd);
if (wds.size() > 0)
lns.add(wds.toArray(new TextWord[wds.size()]));
}
}
return lns.toArray(new TextWord[lns.size()][]);
}
public synchronized void addMarkupAnnotation(int page, PointF[] quadPoints, Annotation.Type type) {
gotoPage(page);
addMarkupAnnotationInternal(quadPoints, type.ordinal());
}
// public synchronized void addInkAnnotation(int page, PointF[][] arcs) {
// gotoPage(page);
// addInkAnnotationInternal(arcs);
// }
public synchronized void addInkAnnotation(int page, PointF[][] arcs, float color[], float inkThickness) {
gotoPage(page);
// Log.e("zyw", "color = " + color[0] + " " + color[1] + " " + color[2]);
addInkAnnotationInternal(arcs, color[0], color[1], color[2], inkThickness);
}
public synchronized void deleteAnnotation(int page, int annot_index) {
gotoPage(page);
deleteAnnotationInternal(annot_index);
}
public synchronized boolean hasOutline() {
return hasOutlineInternal();
}
public synchronized OutlineItem[] getOutline() {
return getOutlineInternal();
}
public synchronized boolean needsPassword() {
return needsPasswordInternal();
}
public synchronized boolean authenticatePassword(String password) {
return authenticatePasswordInternal(password);
}
public synchronized boolean hasChanges() {
return hasChangesInternal();
}
public synchronized void save() {
saveInternal();
}
public synchronized String startProof(int resolution) {
return startProofInternal(resolution);
}
public synchronized void endProof(String filename) {
endProofInternal(filename);
}
public static boolean gprfSupported() {
if (gs_so_available == false)
return false;
return gprfSupportedInternal();
}
public boolean canProof() {
String format = fileFormat();
if (format.contains("PDF"))
return true;
return false;
}
public synchronized int getNumSepsOnPage(int page) {
return getNumSepsOnPageInternal(page);
}
public synchronized int controlSepOnPage(int page, int sep, boolean disable) {
return controlSepOnPageInternal(page, sep, disable);
}
public synchronized Separation getSep(int page, int sep) {
return getSepInternal(page, sep);
}
}
package com.artifex.mupdfdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class MuPDFPageAdapter extends BaseAdapter {
private final Context mContext;
private final FilePicker.FilePickerSupport mFilePickerSupport;
private final MuPDFCore mCore;
private final SparseArray<PointF> mPageSizes = new SparseArray<PointF>();
private Bitmap mSharedHqBm;
public MuPDFPageAdapter(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core) {
mContext = c;
mFilePickerSupport = filePickerSupport;
mCore = core;
}
public int getCount() {
return mCore.countPages();
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public void releaseBitmaps()
{
// recycle and release the shared bitmap.
if (mSharedHqBm!=null)
mSharedHqBm.recycle();
mSharedHqBm = null;
}
public View getView(final int position, View convertView, ViewGroup parent) {
final MuPDFPageView pageView;
if (convertView == null) {
if (mSharedHqBm == null || mSharedHqBm.getWidth() != parent.getWidth() || mSharedHqBm.getHeight() != parent.getHeight())
mSharedHqBm = Bitmap.createBitmap(parent.getWidth(), parent.getHeight(), Bitmap.Config.ARGB_8888);
pageView = new MuPDFPageView(mContext, mFilePickerSupport, mCore, new Point(parent.getWidth(), parent.getHeight()), mSharedHqBm);
} else {
pageView = (MuPDFPageView) convertView;
}
PointF pageSize = mPageSizes.get(position);
if (pageSize != null) {
// We already know the page size. Set it up
// immediately
pageView.setPage(position, pageSize);
} else {
// Page size as yet unknown. Blank it for now, and
// start a background task to find the size
pageView.blank(position);
AsyncTask<Void,Void,PointF> sizingTask = new AsyncTask<Void,Void,PointF>() {
@Override
protected PointF doInBackground(Void... arg0) {
return mCore.getPageSize(position);
}
@Override
protected void onPostExecute(PointF result) {
super.onPostExecute(result);
// We now know the page size
mPageSizes.put(position, result);
// Check that this view hasn't been reused for
// another page since we started
if (pageView.getPage() == position)
pageView.setPage(position, result);
}
};
sizingTask.execute((Void)null);
}
return pageView;
}
}
package com.artifex.mupdfdemo;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import com.artifex.mupdfdemo.MuPDFCore.Cookie;
import com.lonelypluto.pdfviewerlibrary.R;
import java.util.ArrayList;
/* This enum should be kept in line with the cooresponding C enum in mupdf.c */
enum SignatureState {
NoSupport,
Unsigned,
Signed
}
abstract class PassClickResultVisitor {
public abstract void visitText(PassClickResultText result);
public abstract void visitChoice(PassClickResultChoice result);
public abstract void visitSignature(PassClickResultSignature result);
}
class PassClickResult {
public final boolean changed;
public PassClickResult(boolean _changed) {
changed = _changed;
}
public void acceptVisitor(PassClickResultVisitor visitor) {
}
}
class PassClickResultText extends PassClickResult {
public final String text;
public PassClickResultText(boolean _changed, String _text) {
super(_changed);
text = _text;
}
public void acceptVisitor(PassClickResultVisitor visitor) {
visitor.visitText(this);
}
}
class PassClickResultChoice extends PassClickResult {
public final String[] options;
public final String[] selected;
public PassClickResultChoice(boolean _changed, String[] _options, String[] _selected) {
super(_changed);
options = _options;
selected = _selected;
}
public void acceptVisitor(PassClickResultVisitor visitor) {
visitor.visitChoice(this);
}
}
class PassClickResultSignature extends PassClickResult {
public final SignatureState state;
public PassClickResultSignature(boolean _changed, int _state) {
super(_changed);
state = SignatureState.values()[_state];
}
public void acceptVisitor(PassClickResultVisitor visitor) {
visitor.visitSignature(this);
}
}
public class MuPDFPageView extends PageView implements MuPDFView {
final private FilePicker.FilePickerSupport mFilePickerSupport;
private final MuPDFCore mCore;
private AsyncTask<Void, Void, PassClickResult> mPassClick;
private RectF mWidgetAreas[];
private Annotation mAnnotations[];
private int mSelectedAnnotationIndex = -1;
private AsyncTask<Void, Void, RectF[]> mLoadWidgetAreas;
private AsyncTask<Void, Void, Annotation[]> mLoadAnnotations;
private AlertDialog.Builder mTextEntryBuilder;
private AlertDialog.Builder mChoiceEntryBuilder;
private AlertDialog.Builder mSigningDialogBuilder;
private AlertDialog.Builder mSignatureReportBuilder;
private AlertDialog.Builder mPasswordEntryBuilder;
private EditText mPasswordText;
private AlertDialog mTextEntry;
private AlertDialog mPasswordEntry;
private EditText mEditText;
private AsyncTask<String, Void, Boolean> mSetWidgetText;
private AsyncTask<String, Void, Void> mSetWidgetChoice;
private AsyncTask<PointF[], Void, Void> mAddStrikeOut;
private AsyncTask<Object, Void, Void> mAddInk;
private AsyncTask<Integer, Void, Void> mDeleteAnnotation;
private AsyncTask<Void, Void, String> mCheckSignature;
private AsyncTask<Void, Void, Boolean> mSign;
private Runnable changeReporter;
public MuPDFPageView(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) {
super(c, parentSize, sharedHqBm);
mFilePickerSupport = filePickerSupport;
mCore = core;
mTextEntryBuilder = new AlertDialog.Builder(c);
mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field));
LayoutInflater inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mEditText = (EditText) inflater.inflate(R.layout.textentry, null);
mTextEntryBuilder.setView(mEditText);
mTextEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
mTextEntryBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mSetWidgetText = new AsyncTask<String, Void, Boolean>() {
@Override
protected Boolean doInBackground(String... arg0) {
return mCore.setFocusedWidgetText(mPageNumber, arg0[0]);
}
@Override
protected void onPostExecute(Boolean result) {
changeReporter.run();
if (!result)
invokeTextDialog(mEditText.getText().toString());
}
};
mSetWidgetText.execute(mEditText.getText().toString());
}
});
mTextEntry = mTextEntryBuilder.create();
mChoiceEntryBuilder = new AlertDialog.Builder(c);
mChoiceEntryBuilder.setTitle(getContext().getString(R.string.choose_value));
mSigningDialogBuilder = new AlertDialog.Builder(c);
mSigningDialogBuilder.setTitle(getContext().getString(R.string.select_certificate_and_sign));
mSigningDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FilePicker picker = new FilePicker(mFilePickerSupport) {
@Override
void onPick(Uri uri) {
signWithKeyFile(uri);
}
};
picker.pick();
}
});
mSignatureReportBuilder = new AlertDialog.Builder(c);
mSignatureReportBuilder.setTitle(getContext().getString(R.string.signature_checked));
mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
mPasswordText = new EditText(c);
mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
mPasswordText.setTransformationMethod(new PasswordTransformationMethod());
mPasswordEntryBuilder = new AlertDialog.Builder(c);
mPasswordEntryBuilder.setTitle(R.string.enter_password);
mPasswordEntryBuilder.setView(mPasswordText);
mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
mPasswordEntry = mPasswordEntryBuilder.create();
}
private void signWithKeyFile(final Uri uri) {
mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
signWithKeyFileAndPassword(uri, mPasswordText.getText().toString());
}
});
mPasswordEntry.show();
}
private void signWithKeyFileAndPassword(final Uri uri, final String password) {
mSign = new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
changeReporter.run();
} else {
mPasswordText.setText("");
signWithKeyFile(uri);
}
}
};
mSign.execute();
}
public LinkInfo hitLink(float x, float y) {
// Since link highlighting was implemented, the super class
// PageView has had sufficient information to be able to
// perform this method directly. Making that change would
// make MuPDFCore.hitLinkPage superfluous.
float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
float docRelX = (x - getLeft()) / scale;
float docRelY = (y - getTop()) / scale;
for (LinkInfo l : mLinks)
if (l.rect.contains(docRelX, docRelY))
return l;
return null;
}
private void invokeTextDialog(String text) {
mEditText.setText(text);
mTextEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
mTextEntry.show();
}
private void invokeChoiceDialog(final String[] options) {
mChoiceEntryBuilder.setItems(options, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mSetWidgetChoice = new AsyncTask<String, Void, Void>() {
@Override
protected Void doInBackground(String... params) {
String[] sel = {params[0]};
mCore.setFocusedWidgetChoiceSelected(sel);
return null;
}
@Override
protected void onPostExecute(Void result) {
changeReporter.run();
}
};
mSetWidgetChoice.execute(options[which]);
}
});
AlertDialog dialog = mChoiceEntryBuilder.create();
dialog.show();
}
private void invokeSignatureCheckingDialog() {
mCheckSignature = new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
return mCore.checkFocusedSignature();
}
@Override
protected void onPostExecute(String result) {
AlertDialog report = mSignatureReportBuilder.create();
report.setMessage(result);
report.show();
}
};
mCheckSignature.execute();
}
private void invokeSigningDialog() {
AlertDialog dialog = mSigningDialogBuilder.create();
dialog.show();
}
private void warnNoSignatureSupport() {
AlertDialog dialog = mSignatureReportBuilder.create();
dialog.setTitle("App built with no signature support");
dialog.show();
}
public void setChangeReporter(Runnable reporter) {
changeReporter = reporter;
}
public Hit passClickEvent(float x, float y) {
float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
final float docRelX = (x - getLeft()) / scale;
final float docRelY = (y - getTop()) / scale;
boolean hit = false;
int i;
if (mAnnotations != null) {
for (i = 0; i < mAnnotations.length; i++)
if (mAnnotations[i].contains(docRelX, docRelY)) {
hit = true;
break;
}
if (hit) {
switch (mAnnotations[i].type) {
case HIGHLIGHT:
case UNDERLINE:
case SQUIGGLY:
case STRIKEOUT:
case INK:
mSelectedAnnotationIndex = i;
setItemSelectBox(mAnnotations[i]);
return Hit.Annotation;
}
}
}
mSelectedAnnotationIndex = -1;
setItemSelectBox(null);
if (!mCore.javascriptSupported())
return Hit.Nothing;
if (mWidgetAreas != null) {
for (i = 0; i < mWidgetAreas.length && !hit; i++)
if (mWidgetAreas[i].contains(docRelX, docRelY))
hit = true;
}
if (hit) {
mPassClick = new AsyncTask<Void, Void, PassClickResult>() {
@Override
protected PassClickResult doInBackground(Void... arg0) {
return mCore.passClickEvent(mPageNumber, docRelX, docRelY);
}
@Override
protected void onPostExecute(PassClickResult result) {
if (result.changed) {
changeReporter.run();
}
result.acceptVisitor(new PassClickResultVisitor() {
@Override
public void visitText(PassClickResultText result) {
invokeTextDialog(result.text);
}
@Override
public void visitChoice(PassClickResultChoice result) {
invokeChoiceDialog(result.options);
}
@Override
public void visitSignature(PassClickResultSignature result) {
switch (result.state) {
case NoSupport:
warnNoSignatureSupport();
break;
case Unsigned:
invokeSigningDialog();
break;
case Signed:
invokeSignatureCheckingDialog();
break;
}
}
});
}
};
mPassClick.execute();
return Hit.Widget;
}
return Hit.Nothing;
}
@TargetApi(11)
public boolean copySelection() {
final StringBuilder text = new StringBuilder();
processSelectedText(new TextProcessor() {
StringBuilder line;
public void onStartLine() {
line = new StringBuilder();
}
public void onWord(TextWord word) {
if (line.length() > 0)
line.append(' ');
line.append(word.w);
}
public void onEndLine() {
if (text.length() > 0)
text.append('\n');
text.append(line);
}
});
if (text.length() == 0)
return false;
int currentApiVersion = Build.VERSION.SDK_INT;
if (currentApiVersion >= Build.VERSION_CODES.HONEYCOMB) {
android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text));
} else {
android.text.ClipboardManager cm = (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
}
deselectText();
return true;
}
public boolean markupSelection(final Annotation.Type type) {
final ArrayList<PointF> quadPoints = new ArrayList<PointF>();
processSelectedText(new TextProcessor() {
RectF rect;
public void onStartLine() {
rect = new RectF();
}
public void onWord(TextWord word) {
rect.union(word);
}
public void onEndLine() {
if (!rect.isEmpty()) {
quadPoints.add(new PointF(rect.left, rect.bottom));
quadPoints.add(new PointF(rect.right, rect.bottom));
quadPoints.add(new PointF(rect.right, rect.top));
quadPoints.add(new PointF(rect.left, rect.top));
}
}
});
if (quadPoints.size() == 0)
return false;
mAddStrikeOut = new AsyncTask<PointF[], Void, Void>() {
@Override
protected Void doInBackground(PointF[]... params) {
addMarkup(params[0], type);
return null;
}
@Override
protected void onPostExecute(Void result) {
loadAnnotations();
update();
}
};
mAddStrikeOut.execute(quadPoints.toArray(new PointF[quadPoints.size()]));
deselectText();
return true;
}
public void deleteSelectedAnnotation() {
if (mSelectedAnnotationIndex != -1) {
if (mDeleteAnnotation != null)
mDeleteAnnotation.cancel(true);
mDeleteAnnotation = new AsyncTask<Integer, Void, Void>() {
@Override
protected Void doInBackground(Integer... params) {
mCore.deleteAnnotation(mPageNumber, params[0]);
return null;
}
@Override
protected void onPostExecute(Void result) {
loadAnnotations();
update();
}
};
mDeleteAnnotation.execute(mSelectedAnnotationIndex);
mSelectedAnnotationIndex = -1;
setItemSelectBox(null);
}
}
public void deselectAnnotation() {
mSelectedAnnotationIndex = -1;
setItemSelectBox(null);
}
public boolean saveDraw() {
PointF[][] path = getDraw();
if (path == null)
return false;
if (mAddInk != null) {
mAddInk.cancel(true);
mAddInk = null;
}
// mAddInk = new AsyncTask<PointF[][],Void,Void>() {
// @Override
// protected Void doInBackground(PointF[][]... params) {
// mCore.addInkAnnotation(mPageNumber, params[0]);
// return null;
// }
//
// @Override
// protected void onPostExecute(Void result) {
// loadAnnotations();
// update();
// }
//
// };
mAddInk = new AsyncTask<Object, Void, Void>() {
@Override
protected Void doInBackground(Object... params) {
mCore.addInkAnnotation(mPageNumber, (PointF[][]) params[0], (float[]) params[1], (float) params[2]);
return null;
}
@Override
protected void onPostExecute(Void result) {
loadAnnotations();
update();
}
};
// mAddInk.execute(getDraw());
mAddInk.execute(getDraw(), getColor(), getInkThickness());
cancelDraw();
return true;
}
@Override
protected CancellableTaskDefinition<Void, Void> getDrawPageTask(final Bitmap bm, final int sizeX, final int sizeY,
final int patchX, final int patchY, final int patchWidth, final int patchHeight) {
return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) {
@Override
public Void doInBackground(Cookie cookie, Void... params) {
// Workaround bug in Android Honeycomb 3.x, where the bitmap generation count
// is not incremented when drawing.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
bm.eraseColor(0);
mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
return null;
}
};
}
protected CancellableTaskDefinition<Void, Void> getUpdatePageTask(final Bitmap bm, final int sizeX, final int sizeY,
final int patchX, final int patchY, final int patchWidth, final int patchHeight) {
return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) {
@Override
public Void doInBackground(Cookie cookie, Void... params) {
// Workaround bug in Android Honeycomb 3.x, where the bitmap generation count
// is not incremented when drawing.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
bm.eraseColor(0);
mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
return null;
}
};
}
@Override
protected LinkInfo[] getLinkInfo() {
return mCore.getPageLinks(mPageNumber);
}
@Override
protected TextWord[][] getText() {
return mCore.textLines(mPageNumber);
}
@Override
protected void addMarkup(PointF[] quadPoints, Annotation.Type type) {
mCore.addMarkupAnnotation(mPageNumber, quadPoints, type);
}
private void loadAnnotations() {
mAnnotations = null;
if (mLoadAnnotations != null)
mLoadAnnotations.cancel(true);
mLoadAnnotations = new AsyncTask<Void, Void, Annotation[]>() {
@Override
protected Annotation[] doInBackground(Void... params) {
return mCore.getAnnoations(mPageNumber);
}
@Override
protected void onPostExecute(Annotation[] result) {
mAnnotations = result;
}
};
mLoadAnnotations.execute();
}
@Override
public void setPage(final int page, PointF size) {
loadAnnotations();
mLoadWidgetAreas = new AsyncTask<Void, Void, RectF[]>() {
@Override
protected RectF[] doInBackground(Void... arg0) {
return mCore.getWidgetAreas(page);
}
@Override
protected void onPostExecute(RectF[] result) {
mWidgetAreas = result;
}
};
mLoadWidgetAreas.execute();
super.setPage(page, size);
}
public void setScale(float scale) {
// This type of view scales automatically to fit the size
// determined by the parent view groups during layout
}
@Override
public void releaseResources() {
if (mPassClick != null) {
mPassClick.cancel(true);
mPassClick = null;
}
if (mLoadWidgetAreas != null) {
mLoadWidgetAreas.cancel(true);
mLoadWidgetAreas = null;
}
if (mLoadAnnotations != null) {
mLoadAnnotations.cancel(true);
mLoadAnnotations = null;
}
if (mSetWidgetText != null) {
mSetWidgetText.cancel(true);
mSetWidgetText = null;
}
if (mSetWidgetChoice != null) {
mSetWidgetChoice.cancel(true);
mSetWidgetChoice = null;
}
if (mAddStrikeOut != null) {
mAddStrikeOut.cancel(true);
mAddStrikeOut = null;
}
if (mDeleteAnnotation != null) {
mDeleteAnnotation.cancel(true);
mDeleteAnnotation = null;
}
super.releaseResources();
}
}
package com.artifex.mupdfdemo;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.WindowManager;
import com.lonelypluto.pdflibrary.constants.SPConsts;
import com.lonelypluto.pdflibrary.utils.SharedPreferencesUtil;
import com.lonelypluto.pdfviewerlibrary.R;
public class MuPDFReaderView extends ReaderView {
private MuPDFReaderViewListener listener;
public enum Mode {Viewing, Selecting, Drawing}
private final Context mContext;
private boolean mLinksEnabled = false;// 超链接是否高亮显示
private boolean isLinkHighlightColor = false;// 是否设置了超链接颜色
private Mode mMode = Mode.Viewing;
private boolean tapDisabled = false;
private int tapPageMargin;
private int mLinkHighlightColor;// 超链接颜色
protected void onTapMainDocArea() {
checkMuPDFReaderViewListener();
listener.onTapMainDocArea();
}
protected void onDocMotion() {
checkMuPDFReaderViewListener();
listener.onDocMotion();
}
protected void onHit(Hit item) {
checkMuPDFReaderViewListener();
listener.onHit(item);
}
/**
* 设置超链接是否高亮显示
*
* @param b
*/
public void setLinksEnabled(boolean b) {
mLinksEnabled = b;
resetupChildren();
}
/**
* 设置超链接颜色
*
* @param color 颜色值
*/
public void setLinkHighlightColor(int color) {
isLinkHighlightColor = true;
mLinkHighlightColor = color;
resetupChildren();
}
/**
* 设置搜索文字颜色
*
* @param color 颜色值
*/
public void setSearchTextColor(int color) {
SharedPreferencesUtil.put(SPConsts.SP_COLOR_SEARCH_TEXT, color);
resetupChildren();
}
/**
* 设置画笔颜色
*
* @param color 颜色值
*/
public void setInkColor(int color) {
// SharedPreferencesUtil.put(SPConsts.SP_COLOR_SEARCH_TEXT, color);
((MuPDFView) getCurrentView()).setInkColor(color);
}
/**
* 设置画笔粗细
*
* @param inkThickness 粗细值
*/
public void setPaintStrockWidth(float inkThickness) {
// SharedPreferencesUtil.put(SPConsts.SP_COLOR_SEARCH_TEXT, color);
((MuPDFView) getCurrentView()).setPaintStrockWidth(inkThickness);
}
public float getCurrentScale() {
return ((MuPDFView) getCurrentView()).getCurrentScale();
}
public void setMode(Mode m) {
mMode = m;
}
private void setup() {
// Get the screen size etc to customise tap margins.
// We calculate the size of 1 inch of the screen for tapping.
// On some devices the dpi values returned are wrong, so we
// sanity check it: we first restrict it so that we are never
// less than 100 pixels (the smallest Android device screen
// dimension I've seen is 480 pixels or so). Then we check
// to ensure we are never more than 1/5 of the screen width.
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
tapPageMargin = (int) dm.xdpi;
if (tapPageMargin < 100)
tapPageMargin = 100;
if (tapPageMargin > dm.widthPixels / 5)
tapPageMargin = dm.widthPixels / 5;
// set view backgroundColor
setBackgroundColor(mContext.getColor(R.color.muPDFReaderView_bg));
}
public MuPDFReaderView(Context context) {
super(context);
mContext = context;
setup();
}
public MuPDFReaderView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setup();
}
public boolean onSingleTapUp(MotionEvent e) {
LinkInfo link = null;
if (mMode == Mode.Viewing && !tapDisabled) {
MuPDFView pageView = (MuPDFView) getDisplayedView();
Hit item = pageView.passClickEvent(e.getX(), e.getY());
onHit(item);
if (item == Hit.Nothing) {
if (mLinksEnabled && pageView != null
&& (link = pageView.hitLink(e.getX(), e.getY())) != null) {
link.acceptVisitor(new LinkInfoVisitor() {
@Override
public void visitInternal(LinkInfoInternal li) {
// Clicked on an internal (GoTo) link
setDisplayedViewIndex(li.pageNumber);
}
@Override
public void visitExternal(LinkInfoExternal li) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri
.parse(li.url));
mContext.startActivity(intent);
}
@Override
public void visitRemote(LinkInfoRemote li) {
// Clicked on a remote (GoToR) link
}
});
} else if (e.getX() < tapPageMargin) {
super.smartMoveBackwards();
} else if (e.getX() > super.getWidth() - tapPageMargin) {
super.smartMoveForwards();
} else if (e.getY() < tapPageMargin) {
super.smartMoveBackwards();
} else if (e.getY() > super.getHeight() - tapPageMargin) {
super.smartMoveForwards();
} else {
onTapMainDocArea();
}
}
}
return super.onSingleTapUp(e);
}
@Override
public boolean onDown(MotionEvent e) {
return super.onDown(e);
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
MuPDFView pageView = (MuPDFView) getDisplayedView();
switch (mMode) {
case Viewing:
if (!tapDisabled)
onDocMotion();
return super.onScroll(e1, e2, distanceX, distanceY);
case Selecting:
if (pageView != null)
pageView.selectText(e1.getX(), e1.getY(), e2.getX(), e2.getY());
return true;
default:
return true;
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
switch (mMode) {
case Viewing:
return super.onFling(e1, e2, velocityX, velocityY);
default:
return true;
}
}
public boolean onScaleBegin(ScaleGestureDetector d) {
// Disabled showing the buttons until next touch.
// Not sure why this is needed, but without it
// pinch zoom can make the buttons appear
tapDisabled = true;
return super.onScaleBegin(d);
}
public boolean onTouchEvent(MotionEvent event) {
if (mMode == Mode.Drawing) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
break;
case MotionEvent.ACTION_UP:
touch_up();
break;
}
}
if ((event.getAction() & event.getActionMasked()) == MotionEvent.ACTION_DOWN) {
tapDisabled = false;
}
return super.onTouchEvent(event);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 2;
private void touch_start(float x, float y) {
MuPDFView pageView = (MuPDFView) getDisplayedView();
if (pageView != null) {
pageView.startDraw(x, y);
}
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
MuPDFView pageView = (MuPDFView) getDisplayedView();
if (pageView != null) {
pageView.continueDraw(x, y);
}
mX = x;
mY = y;
}
}
private void touch_up() {
// NOOP
}
protected void onChildSetup(int i, View v) {
if (SearchTaskResult.get() != null
&& SearchTaskResult.get().pageNumber == i)
((MuPDFView) v).setSearchBoxes(SearchTaskResult.get().searchBoxes);
else
((MuPDFView) v).setSearchBoxes(null);
((MuPDFView) v).setLinkHighlighting(mLinksEnabled);
// 设置超链接颜色
if (isLinkHighlightColor) {
((MuPDFView) v).setLinkHighlightColor(mLinkHighlightColor);
}
((MuPDFView) v).setChangeReporter(new Runnable() {
public void run() {
applyToChildren(new ViewMapper() {
@Override
public void applyToView(View view) {
((MuPDFView) view).update();
}
});
}
});
}
protected void onMoveToChild(int i) {
if (SearchTaskResult.get() != null
&& SearchTaskResult.get().pageNumber != i) {
SearchTaskResult.set(null);
resetupChildren();
}
checkMuPDFReaderViewListener();
listener.onMoveToChild(i);
}
@Override
protected void onMoveOffChild(int i) {
View v = getView(i);
if (v != null)
((MuPDFView) v).deselectAnnotation();
}
protected void onSettle(View v) {
// When the layout has settled ask the page to render
// in HQ
((MuPDFView) v).updateHq(false);
}
protected void onUnsettle(View v) {
// When something changes making the previous settled view
// no longer appropriate, tell the page to remove HQ
((MuPDFView) v).removeHq();
}
@Override
protected void onNotInUse(View v) {
((MuPDFView) v).releaseResources();
}
@Override
protected void onScaleChild(View v, Float scale) {
((MuPDFView) v).setScale(scale);
}
/**
* 设置监听事件
*
* @param listener
*/
public void setListener(MuPDFReaderViewListener listener) {
this.listener = listener;
}
private void checkMuPDFReaderViewListener() {
if (listener == null) {
listener = new MuPDFReaderViewListener() {
@Override
public void onMoveToChild(int i) {
}
@Override
public void onTapMainDocArea() {
}
@Override
public void onDocMotion() {
}
@Override
public void onHit(Hit item) {
}
};
}
}
}
package com.artifex.mupdfdemo;
public interface MuPDFReaderViewListener {
void onMoveToChild(int i);
void onTapMainDocArea();
void onDocMotion();
void onHit(Hit item);
}
package com.artifex.mupdfdemo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class MuPDFReflowAdapter extends BaseAdapter {
private final Context mContext;
private final MuPDFCore mCore;
public MuPDFReflowAdapter(Context c, MuPDFCore core) {
mContext = c;
mCore = core;
}
public int getCount() {
return mCore.countPages();
}
public Object getItem(int arg0) {
return null;
}
public long getItemId(int arg0) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
final MuPDFReflowView reflowView;
if (convertView == null) {
reflowView = new MuPDFReflowView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight()));
} else {
reflowView = (MuPDFReflowView) convertView;
}
reflowView.setPage(position, new PointF());
return reflowView;
}
}
package com.artifex.mupdfdemo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Handler;
import android.util.Base64;
import android.view.MotionEvent;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MuPDFReflowView extends WebView implements MuPDFView {
private final MuPDFCore mCore;
private final Handler mHandler;
private final Point mParentSize;
private int mPage;
private float mScale;
private int mContentHeight;
AsyncTask<Void, Void, byte[]> mLoadHTML;
public MuPDFReflowView(Context c, MuPDFCore core, Point parentSize) {
super(c);
mHandler = new Handler();
mCore = core;
mParentSize = parentSize;
mScale = 1.0f;
mContentHeight = parentSize.y;
getSettings().setJavaScriptEnabled(true);
addJavascriptInterface(new Object() {
public void reportContentHeight(String value) {
mContentHeight = (int) Float.parseFloat(value);
mHandler.post(new Runnable() {
public void run() {
requestLayout();
}
});
}
}, "HTMLOUT");
setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
setScale(mScale);
}
});
}
private void requestHeight() {
// Get the webview to report the content height via the interface setup
// above. Workaround for getContentHeight not working
loadUrl("javascript:elem=document.getElementById('content');window.HTMLOUT.reportContentHeight(" + mParentSize.x + "*elem.offsetHeight/elem.offsetWidth)");
}
public void setPage(int page, PointF size) {
mPage = page;
if (mLoadHTML != null) {
mLoadHTML.cancel(true);
}
mLoadHTML = new AsyncTask<Void, Void, byte[]>() {
@Override
protected byte[] doInBackground(Void... params) {
return mCore.html(mPage);
}
@Override
protected void onPostExecute(byte[] result) {
String b64 = Base64.encodeToString(result, Base64.DEFAULT);
loadData(b64, "text/html; charset=utf-8", "base64");
}
};
mLoadHTML.execute();
}
public int getPage() {
return mPage;
}
public void setScale(float scale) {
mScale = scale;
loadUrl("javascript:document.getElementById('content').style.zoom=\"" + (int) (mScale * 100) + "%\"");
requestHeight();
}
public void blank(int page) {
}
public Hit passClickEvent(float x, float y) {
return Hit.Nothing;
}
public LinkInfo hitLink(float x, float y) {
return null;
}
public void selectText(float x0, float y0, float x1, float y1) {
}
public void deselectText() {
}
public boolean copySelection() {
return false;
}
public boolean markupSelection(Annotation.Type type) {
return false;
}
public void startDraw(float x, float y) {
}
public void continueDraw(float x, float y) {
}
public void cancelDraw() {
}
public boolean saveDraw() {
return false;
}
public void setSearchBoxes(RectF[] searchBoxes) {
}
public void setLinkHighlighting(boolean f) {
}
public void deleteSelectedAnnotation() {
}
public void deselectAnnotation() {
}
public void setChangeReporter(Runnable reporter) {
}
public void update() {
}
public void updateHq(boolean update) {
}
public void removeHq() {
}
public void releaseResources() {
if (mLoadHTML != null) {
mLoadHTML.cancel(true);
mLoadHTML = null;
}
}
public void releaseBitmaps() {
}
@Override
public void setLinkHighlightColor(int color) {
}
@Override
public void setInkColor(int color) {
}
@Override
public void setPaintStrockWidth(float inkThickness) {
}
@Override
public float getCurrentScale() {
return 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int x, y;
switch (MeasureSpec.getMode(widthMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
x = mParentSize.x;
break;
default:
x = MeasureSpec.getSize(widthMeasureSpec);
}
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
y = mContentHeight;
break;
default:
y = MeasureSpec.getSize(heightMeasureSpec);
}
setMeasuredDimension(x, y);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return false;
}
}
package com.artifex.mupdfdemo;
import android.graphics.PointF;
import android.graphics.RectF;
public interface MuPDFView {
public void setPage(int page, PointF size);
public void setScale(float scale);
public int getPage();
public void blank(int page);
public Hit passClickEvent(float x, float y);
public LinkInfo hitLink(float x, float y);
public void selectText(float x0, float y0, float x1, float y1);
public void deselectText();
public boolean copySelection();
/**
* 文字选中后根据类型 高亮、下划线、删除线进行处理标注
*
* @param type
* @return
*/
public boolean markupSelection(Annotation.Type type);
public void deleteSelectedAnnotation();
public void setSearchBoxes(RectF searchBoxes[]);
public void setLinkHighlighting(boolean f);
public void deselectAnnotation();
public void startDraw(float x, float y);
public void continueDraw(float x, float y);
public void cancelDraw();
public boolean saveDraw();
public void setChangeReporter(Runnable reporter);
public void update();
public void updateHq(boolean update);
public void removeHq();
public void releaseResources();
public void releaseBitmaps();
/**
* 设置超链接颜色
*
* @param color 颜色值
*/
public void setLinkHighlightColor(int color);
/**
* 设置画笔颜色
*
* @param color 颜色值
*/
public void setInkColor(int color);
/**
* 设置画笔粗细
*
* @param inkThickness 粗细值
*/
public void setPaintStrockWidth(float inkThickness);
public float getCurrentScale();
}
package com.artifex.mupdfdemo;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
public class OutlineActivity extends ListActivity {
OutlineItem mItems[];
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mItems = OutlineActivityData.get().items;
setListAdapter(new OutlineAdapter(getLayoutInflater(),mItems));
// Restore the position within the list from last viewing
getListView().setSelection(OutlineActivityData.get().position);
getListView().setDividerHeight(0);
setResult(-1);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
OutlineActivityData.get().position = getListView().getFirstVisiblePosition();
setResult(mItems[position].page);
finish();
}
}
package com.artifex.mupdfdemo;
public class OutlineActivityData {
public OutlineItem items[];
public int position;
static private OutlineActivityData singleton;
static public void set(OutlineActivityData d) {
singleton = d;
}
static public OutlineActivityData get() {
if (singleton == null)
singleton = new OutlineActivityData();
return singleton;
}
}
package com.artifex.mupdfdemo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.lonelypluto.pdfviewerlibrary.R;
public class OutlineAdapter extends BaseAdapter {
private final OutlineItem mItems[];
private final LayoutInflater mInflater;
public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) {
mInflater = inflater;
mItems = items;
}
public int getCount() {
return mItems.length;
}
public Object getItem(int arg0) {
return null;
}
public long getItemId(int arg0) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = mInflater.inflate(R.layout.outline_entry, null);
} else {
v = convertView;
}
int level = mItems[position].level;
if (level > 8) level = 8;
String space = "";
for (int i = 0; i < level; i++)
space += " ";
((TextView) v.findViewById(R.id.title)).setText(space + mItems[position].title);
((TextView) v.findViewById(R.id.page)).setText(String.valueOf(mItems[position].page + 1));
return v;
}
}
package com.artifex.mupdfdemo;
public class OutlineItem {
public final int level;
public final String title;
public final int page;
OutlineItem(int _level, String _title, int _page) {
level = _level;
title = _title;
page = _page;
}
}
package com.artifex.mupdfdemo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.lonelypluto.pdfviewerlibrary.R;
import java.util.ArrayList;
import java.util.Iterator;
// Make our ImageViews opaque to optimize redraw
@SuppressLint("AppCompatCustomView")
class OpaqueImageView extends ImageView {
public OpaqueImageView(Context context) {
super(context);
}
@Override
public boolean isOpaque() {
return true;
}
}
interface TextProcessor {
void onStartLine();
void onWord(TextWord word);
void onEndLine();
}
class TextSelector {
final private TextWord[][] mText;
final private RectF mSelectBox;
public TextSelector(TextWord[][] text, RectF selectBox) {
mText = text;
mSelectBox = selectBox;
}
public void select(TextProcessor tp) {
if (mText == null || mSelectBox == null)
return;
ArrayList<TextWord[]> lines = new ArrayList<TextWord[]>();
for (TextWord[] line : mText)
if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom)
lines.add(line);
Iterator<TextWord[]> it = lines.iterator();
while (it.hasNext()) {
TextWord[] line = it.next();
boolean firstLine = line[0].top < mSelectBox.top;
boolean lastLine = line[0].bottom > mSelectBox.bottom;
float start = Float.NEGATIVE_INFINITY;
float end = Float.POSITIVE_INFINITY;
if (firstLine && lastLine) {
start = Math.min(mSelectBox.left, mSelectBox.right);
end = Math.max(mSelectBox.left, mSelectBox.right);
} else if (firstLine) {
start = mSelectBox.left;
} else if (lastLine) {
end = mSelectBox.right;
}
tp.onStartLine();
for (TextWord word : line)
if (word.right > start && word.left < end)
tp.onWord(word);
tp.onEndLine();
}
}
}
public abstract class PageView extends ViewGroup {
private static final float ITEM_SELECT_BOX_WIDTH = 4.0f;// 选中时边框的宽
private static final int HIGHLIGHT_COLOR = 0x80ff5722;// 选中文字时的颜色
private int LINK_COLOR = 0x80ff5722;// 超链接颜色
// private static final int BOX_COLOR = 0xFF4444FF;
private static final int BOX_COLOR = 0xFF696969;// 选中时边框的颜色
// private static final int INK_COLOR = 0xFF000000;// 绘制时画笔颜色
private int INK_COLOR = 0xFF000000;// 绘制时画笔颜色
// private static final float INK_THICKNESS = 10.0f;// 绘制时画笔宽
private float INK_THICKNESS = 10.0f;// 绘制时画笔宽
private float current_scale;
private static final int BACKGROUND_COLOR = 0xFFFFFFFF;
private static final int PROGRESS_DIALOG_DELAY = 200;
protected final Context mContext;
protected int mPageNumber;
private Point mParentSize;
protected Point mSize; // Size of page at minimum zoom
protected float mSourceScale;
private ImageView mEntire; // Image rendered at minimum zoom
private Bitmap mEntireBm;
private Matrix mEntireMat;
private AsyncTask<Void, Void, TextWord[][]> mGetText;
private AsyncTask<Void, Void, LinkInfo[]> mGetLinkInfo;
private CancellableAsyncTask<Void, Void> mDrawEntire;
private Point mPatchViewSize; // View size on the basis of which the patch was created
private Rect mPatchArea;
private ImageView mPatch;
private Bitmap mPatchBm;
private CancellableAsyncTask<Void, Void> mDrawPatch;
private RectF mSearchBoxes[];
protected LinkInfo mLinks[];
private RectF mSelectBox;
private TextWord mText[][];
private RectF mItemSelectBox;
protected ArrayList<ArrayList<PointF>> mDrawing;
private View mSearchView;
private boolean mIsBlank;
private boolean mHighlightLinks;
private ProgressBar mBusyIndicator;
private final Handler mHandler = new Handler();
public PageView(Context c, Point parentSize, Bitmap sharedHqBm) {
super(c);
mContext = c;
mParentSize = parentSize;
setBackgroundColor(BACKGROUND_COLOR);
mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888);
mPatchBm = sharedHqBm;
mEntireMat = new Matrix();
}
protected abstract CancellableTaskDefinition<Void, Void> getDrawPageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight);
protected abstract CancellableTaskDefinition<Void, Void> getUpdatePageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight);
protected abstract LinkInfo[] getLinkInfo();
protected abstract TextWord[][] getText();
protected abstract void addMarkup(PointF[] quadPoints, Annotation.Type type);
private void reinit() {
// Cancel pending render task
if (mDrawEntire != null) {
mDrawEntire.cancelAndWait();
mDrawEntire = null;
}
if (mDrawPatch != null) {
mDrawPatch.cancelAndWait();
mDrawPatch = null;
}
if (mGetLinkInfo != null) {
mGetLinkInfo.cancel(true);
mGetLinkInfo = null;
}
if (mGetText != null) {
mGetText.cancel(true);
mGetText = null;
}
mIsBlank = true;
mPageNumber = 0;
if (mSize == null)
mSize = mParentSize;
if (mEntire != null) {
mEntire.setImageBitmap(null);
mEntire.invalidate();
}
if (mPatch != null) {
mPatch.setImageBitmap(null);
mPatch.invalidate();
}
mPatchViewSize = null;
mPatchArea = null;
mSearchBoxes = null;
mLinks = null;
mSelectBox = null;
mText = null;
mItemSelectBox = null;
}
public void releaseResources() {
reinit();
if (mBusyIndicator != null) {
removeView(mBusyIndicator);
mBusyIndicator = null;
}
}
public void releaseBitmaps() {
reinit();
// recycle bitmaps before releasing them.
if (mEntireBm != null)
mEntireBm.recycle();
mEntireBm = null;
if (mPatchBm != null)
mPatchBm.recycle();
mPatchBm = null;
}
public void blank(int page) {
reinit();
mPageNumber = page;
if (mBusyIndicator == null) {
mBusyIndicator = new ProgressBar(mContext);
mBusyIndicator.setIndeterminate(true);
mBusyIndicator.setBackgroundResource(R.drawable.busy);
addView(mBusyIndicator);
}
setBackgroundColor(BACKGROUND_COLOR);
}
public void setPage(int page, PointF size) {
// Cancel pending render task
if (mDrawEntire != null) {
mDrawEntire.cancelAndWait();
mDrawEntire = null;
}
mIsBlank = false;
// Highlights may be missing because mIsBlank was true on last draw
if (mSearchView != null)
mSearchView.invalidate();
mPageNumber = page;
if (mEntire == null) {
mEntire = new OpaqueImageView(mContext);
mEntire.setScaleType(ImageView.ScaleType.MATRIX);
addView(mEntire);
}
// Calculate scaled size that fits within the screen limits
// This is the size at minimum zoom
mSourceScale = Math.min(mParentSize.x / size.x, mParentSize.y / size.y);
Point newSize = new Point((int) (size.x * mSourceScale), (int) (size.y * mSourceScale));
mSize = newSize;
mEntire.setImageBitmap(null);
mEntire.invalidate();
// Get the link info in the background
mGetLinkInfo = new AsyncTask<Void, Void, LinkInfo[]>() {
protected LinkInfo[] doInBackground(Void... v) {
return getLinkInfo();
}
protected void onPostExecute(LinkInfo[] v) {
mLinks = v;
if (mSearchView != null)
mSearchView.invalidate();
}
};
mGetLinkInfo.execute();
// Render the page in the background
mDrawEntire = new CancellableAsyncTask<Void, Void>(getDrawPageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) {
@Override
public void onPreExecute() {
setBackgroundColor(BACKGROUND_COLOR);
mEntire.setImageBitmap(null);
mEntire.invalidate();
if (mBusyIndicator == null) {
mBusyIndicator = new ProgressBar(mContext);
mBusyIndicator.setIndeterminate(true);
mBusyIndicator.setBackgroundResource(R.drawable.busy);
addView(mBusyIndicator);
mBusyIndicator.setVisibility(INVISIBLE);
mHandler.postDelayed(new Runnable() {
public void run() {
if (mBusyIndicator != null)
mBusyIndicator.setVisibility(VISIBLE);
}
}, PROGRESS_DIALOG_DELAY);
}
}
@Override
public void onPostExecute(Void result) {
removeView(mBusyIndicator);
mBusyIndicator = null;
mEntire.setImageBitmap(mEntireBm);
mEntire.invalidate();
setBackgroundColor(Color.TRANSPARENT);
}
};
mDrawEntire.execute();
if (mSearchView == null) {
mSearchView = new View(mContext) {
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
// Work out current total scale factor
// from source to view
final float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
current_scale = scale;
final Paint paint = new Paint();
if (!mIsBlank && mSearchBoxes != null) {
// 搜索颜色
// paint.setColor(mContext.getResources().getColor(R.color.search_bg));
paint.setColor(HIGHLIGHT_COLOR);
for (RectF rect : mSearchBoxes)
canvas.drawRect(rect.left * scale, rect.top * scale,
rect.right * scale, rect.bottom * scale,
paint);
}
if (!mIsBlank && mLinks != null && mHighlightLinks) {
// 超链接颜色
// paint.setColor(mContext.getResources().getColor(R.color.link_bg));
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);
}
if (mSelectBox != null && mText != null) {
// 选中文字 复制,高亮,下划线,删除线选中时的颜色
paint.setColor(HIGHLIGHT_COLOR);
processSelectedText(new TextProcessor() {
RectF rect;
public void onStartLine() {
rect = new RectF();
}
public void onWord(TextWord word) {
rect.union(word);
}
public void onEndLine() {
if (!rect.isEmpty())
canvas.drawRect(rect.left * scale, rect.top * scale, rect.right * scale, rect.bottom * scale, paint);
}
});
}
// 选中时的外边框
if (mItemSelectBox != null) {
paint.setStyle(Paint.Style.STROKE);
// 边框宽
paint.setStrokeWidth(ITEM_SELECT_BOX_WIDTH);
// 边框颜色
// paint.setColor(mContext.getResources().getColor(R.color.link_bg));
paint.setColor(BOX_COLOR);
canvas.drawRect(mItemSelectBox.left * scale, mItemSelectBox.top * scale, mItemSelectBox.right * scale, mItemSelectBox.bottom * scale, paint);
}
if (mDrawing != null) {
Path path = new Path();
PointF p;
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL);
// 绘制时画笔宽
paint.setStrokeWidth(INK_THICKNESS * scale);
// 绘制时画笔颜色
paint.setColor(INK_COLOR);
Iterator<ArrayList<PointF>> it = mDrawing.iterator();
while (it.hasNext()) {
ArrayList<PointF> arc = it.next();
if (arc.size() >= 2) {
Iterator<PointF> iit = arc.iterator();
p = iit.next();
float mX = p.x * scale;
float mY = p.y * scale;
path.moveTo(mX, mY);
while (iit.hasNext()) {
p = iit.next();
float x = p.x * scale;
float y = p.y * scale;
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
path.lineTo(mX, mY);
} else {
p = arc.get(0);
canvas.drawCircle(p.x * scale, p.y * scale, INK_THICKNESS * scale / 2, paint);
}
}
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, paint);
}
}
};
addView(mSearchView);
}
requestLayout();
}
public void setSearchBoxes(RectF searchBoxes[]) {
mSearchBoxes = searchBoxes;
if (mSearchView != null)
mSearchView.invalidate();
}
/**
* 设置是否高亮显示超链接
*
* @param f boolean
*/
public void setLinkHighlighting(boolean f) {
mHighlightLinks = f;
if (mSearchView != null)
mSearchView.invalidate();
}
/**
* 置超链接颜色
*
* @param color 颜色值
*/
public void setLinkHighlightColor(int color) {
LINK_COLOR = color;
if (mHighlightLinks) {
if (mSearchView != null) {
mSearchView.invalidate();
}
}
}
public void deselectText() {
mSelectBox = null;
mSearchView.invalidate();
}
public void selectText(float x0, float y0, float x1, float y1) {
float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
float docRelX0 = (x0 - getLeft()) / scale;
float docRelY0 = (y0 - getTop()) / scale;
float docRelX1 = (x1 - getLeft()) / scale;
float docRelY1 = (y1 - getTop()) / scale;
// Order on Y but maintain the point grouping
if (docRelY0 <= docRelY1)
mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1);
else
mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0);
mSearchView.invalidate();
if (mGetText == null) {
mGetText = new AsyncTask<Void, Void, TextWord[][]>() {
@Override
protected TextWord[][] doInBackground(Void... params) {
return getText();
}
@Override
protected void onPostExecute(TextWord[][] result) {
mText = result;
mSearchView.invalidate();
}
};
mGetText.execute();
}
}
public void startDraw(float x, float y) {
float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
float docRelX = (x - getLeft()) / scale;
float docRelY = (y - getTop()) / scale;
if (mDrawing == null)
mDrawing = new ArrayList<ArrayList<PointF>>();
ArrayList<PointF> arc = new ArrayList<PointF>();
arc.add(new PointF(docRelX, docRelY));
mDrawing.add(arc);
mSearchView.invalidate();
}
public void continueDraw(float x, float y) {
float scale = mSourceScale * (float) getWidth() / (float) mSize.x;
float docRelX = (x - getLeft()) / scale;
float docRelY = (y - getTop()) / scale;
if (mDrawing != null && mDrawing.size() > 0) {
ArrayList<PointF> arc = mDrawing.get(mDrawing.size() - 1);
arc.add(new PointF(docRelX, docRelY));
mSearchView.invalidate();
}
}
public void cancelDraw() {
mDrawing = null;
mSearchView.invalidate();
}
protected PointF[][] getDraw() {
if (mDrawing == null)
return null;
PointF[][] path = new PointF[mDrawing.size()][];
for (int i = 0; i < mDrawing.size(); i++) {
ArrayList<PointF> arc = mDrawing.get(i);
path[i] = arc.toArray(new PointF[arc.size()]);
}
return path;
}
/**
* 设置画笔颜色
*
* @param color 颜色值
*/
public void setInkColor(int color) {
INK_COLOR = color;
}
/**
* 设置画笔粗细
*
* @param inkThickness 粗细值
*/
public void setPaintStrockWidth(float inkThickness) {
INK_THICKNESS = inkThickness;
}
protected float getInkThickness() {
if (current_scale == 0) {
return 9.07563f / 2;
} else {
// return (INK_THICKNESS * current_scale) / 2;
return INK_THICKNESS / 2;
}
}
public float getCurrentScale() {
if (current_scale == 0) {
return 9.07563f;
}
return current_scale;
}
protected float[] getColor() {
return changeColor(INK_COLOR);
}
/**
* 将十进制颜色值转换成RGB格式
*
* @param color
* @return
*/
private float[] changeColor(int color) {
int red = (color & 0xff0000) >> 16;
int green = (color & 0x00ff00) >> 8;
int blue = (color & 0x0000ff);
float colors[] = new float[3];
colors[0] = red / 255f;
colors[1] = green / 255f;
colors[2] = blue / 255f;
return colors;
}
protected void processSelectedText(TextProcessor tp) {
(new TextSelector(mText, mSelectBox)).select(tp);
}
public void setItemSelectBox(RectF rect) {
mItemSelectBox = rect;
if (mSearchView != null)
mSearchView.invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int x, y;
switch (MeasureSpec.getMode(widthMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
x = mSize.x;
break;
default:
x = MeasureSpec.getSize(widthMeasureSpec);
}
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
y = mSize.y;
break;
default:
y = MeasureSpec.getSize(heightMeasureSpec);
}
setMeasuredDimension(x, y);
if (mBusyIndicator != null) {
int limit = Math.min(mParentSize.x, mParentSize.y) / 2;
mBusyIndicator.measure(MeasureSpec.AT_MOST | limit, MeasureSpec.AT_MOST | limit);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int w = right - left;
int h = bottom - top;
if (mEntire != null) {
if (mEntire.getWidth() != w || mEntire.getHeight() != h) {
mEntireMat.setScale(w / (float) mSize.x, h / (float) mSize.y);
mEntire.setImageMatrix(mEntireMat);
mEntire.invalidate();
}
mEntire.layout(0, 0, w, h);
}
if (mSearchView != null) {
mSearchView.layout(0, 0, w, h);
}
if (mPatchViewSize != null) {
if (mPatchViewSize.x != w || mPatchViewSize.y != h) {
// Zoomed since patch was created
mPatchViewSize = null;
mPatchArea = null;
if (mPatch != null) {
mPatch.setImageBitmap(null);
mPatch.invalidate();
}
} else {
mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
}
}
if (mBusyIndicator != null) {
int bw = mBusyIndicator.getMeasuredWidth();
int bh = mBusyIndicator.getMeasuredHeight();
mBusyIndicator.layout((w - bw) / 2, (h - bh) / 2, (w + bw) / 2, (h + bh) / 2);
}
}
public void updateHq(boolean update) {
Rect viewArea = new Rect(getLeft(), getTop(), getRight(), getBottom());
if (viewArea.width() == mSize.x || viewArea.height() == mSize.y) {
// If the viewArea's size matches the unzoomed size, there is no need for an hq patch
if (mPatch != null) {
mPatch.setImageBitmap(null);
mPatch.invalidate();
}
} else {
final Point patchViewSize = new Point(viewArea.width(), viewArea.height());
final Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y);
// Intersect and test that there is an intersection
if (!patchArea.intersect(viewArea))
return;
// Offset patch area to be relative to the view top left
patchArea.offset(-viewArea.left, -viewArea.top);
boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize);
// If being asked for the same area as last time and not because of an update then nothing to do
if (area_unchanged && !update)
return;
boolean completeRedraw = !(area_unchanged && update);
// Stop the drawing of previous patch if still going
if (mDrawPatch != null) {
mDrawPatch.cancelAndWait();
mDrawPatch = null;
}
// Create and add the image view if not already done
if (mPatch == null) {
mPatch = new OpaqueImageView(mContext);
mPatch.setScaleType(ImageView.ScaleType.MATRIX);
addView(mPatch);
mSearchView.bringToFront();
}
CancellableTaskDefinition<Void, Void> task;
if (completeRedraw)
task = getDrawPageTask(mPatchBm, patchViewSize.x, patchViewSize.y,
patchArea.left, patchArea.top,
patchArea.width(), patchArea.height());
else
task = getUpdatePageTask(mPatchBm, patchViewSize.x, patchViewSize.y,
patchArea.left, patchArea.top,
patchArea.width(), patchArea.height());
mDrawPatch = new CancellableAsyncTask<Void, Void>(task) {
public void onPostExecute(Void result) {
mPatchViewSize = patchViewSize;
mPatchArea = patchArea;
mPatch.setImageBitmap(mPatchBm);
mPatch.invalidate();
//requestLayout();
// Calling requestLayout here doesn't lead to a later call to layout. No idea
// why, but apparently others have run into the problem.
mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
}
};
mDrawPatch.execute();
}
}
public void update() {
// Cancel pending render task
if (mDrawEntire != null) {
mDrawEntire.cancelAndWait();
mDrawEntire = null;
}
if (mDrawPatch != null) {
mDrawPatch.cancelAndWait();
mDrawPatch = null;
}
// Render the page in the background
mDrawEntire = new CancellableAsyncTask<Void, Void>(getUpdatePageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) {
public void onPostExecute(Void result) {
mEntire.setImageBitmap(mEntireBm);
mEntire.invalidate();
}
};
mDrawEntire.execute();
updateHq(true);
}
public void removeHq() {
// Stop the drawing of the patch if still going
if (mDrawPatch != null) {
mDrawPatch.cancelAndWait();
mDrawPatch = null;
}
// And get rid of it
mPatchViewSize = null;
mPatchArea = null;
if (mPatch != null) {
mPatch.setImageBitmap(null);
mPatch.invalidate();
}
}
public int getPage() {
return mPageNumber;
}
@Override
public boolean isOpaque() {
return true;
}
}
package com.artifex.mupdfdemo;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
import android.util.Base64;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.lonelypluto.pdfviewerlibrary.R;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class PrintDialogActivity extends Activity {
private static final String PRINT_DIALOG_URL = "https://www.google.com/cloudprint/dialog.html";
private static final String JS_INTERFACE = "AndroidPrintDialog";
private static final String CONTENT_TRANSFER_ENCODING = "base64";
private static final String ZXING_URL = "http://zxing.appspot.com";
private static final int ZXING_SCAN_REQUEST = 65743;
/**
* Post message that is sent by Print Dialog web page when the printing dialog
* needs to be closed.
*/
private static final String CLOSE_POST_MESSAGE_NAME = "cp-dialog-on-close";
/**
* Web view element to show the printing dialog in.
*/
private WebView dialogWebView;
/**
* Intent that started the action.
*/
Intent cloudPrintIntent;
private int resultCode;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
resultCode = RESULT_OK;
setContentView(R.layout.print_dialog);
dialogWebView = (WebView) findViewById(R.id.webview);
cloudPrintIntent = this.getIntent();
WebSettings settings = dialogWebView.getSettings();
settings.setJavaScriptEnabled(true);
dialogWebView.setWebViewClient(new PrintDialogWebClient());
dialogWebView.addJavascriptInterface(new PrintDialogJavaScriptInterface(), JS_INTERFACE);
dialogWebView.loadUrl(PRINT_DIALOG_URL);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == ZXING_SCAN_REQUEST && resultCode == RESULT_OK) {
dialogWebView.loadUrl(intent.getStringExtra("SCAN_RESULT"));
}
}
final class PrintDialogJavaScriptInterface {
public String getType() {
return cloudPrintIntent.getType();
}
public String getTitle() {
return cloudPrintIntent.getExtras().getString("title");
}
public String getContent() {
try {
ContentResolver contentResolver = getContentResolver();
InputStream is = contentResolver.openInputStream(cloudPrintIntent.getData());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = is.read(buffer);
while (n >= 0) {
baos.write(buffer, 0, n);
n = is.read(buffer);
}
is.close();
baos.flush();
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
} catch (Throwable e) {
resultCode = RESULT_CANCELED;
setResult(resultCode);
finish();
e.printStackTrace();
}
return "";
}
public String getEncoding() {
return CONTENT_TRANSFER_ENCODING;
}
public void onPostMessage(String message) {
if (message.startsWith(CLOSE_POST_MESSAGE_NAME)) {
setResult(resultCode);
finish();
}
}
}
private final class PrintDialogWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(ZXING_URL)) {
Intent intentScan = new Intent("com.google.zxing.client.android.SCAN");
intentScan.putExtra("SCAN_MODE", "QR_CODE_MODE");
try {
startActivityForResult(intentScan, ZXING_SCAN_REQUEST);
} catch (ActivityNotFoundException error) {
view.loadUrl(url);
}
} else {
view.loadUrl(url);
}
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
if (PRINT_DIALOG_URL.equals(url)) {
// Submit print document.
view.loadUrl("javascript:printDialog.setPrintDocument(printDialog.createPrintDocument("
+ "window." + JS_INTERFACE + ".getType(),window." + JS_INTERFACE + ".getTitle(),"
+ "window." + JS_INTERFACE + ".getContent(),window." + JS_INTERFACE + ".getEncoding()))");
// Add post messages listener.
view.loadUrl("javascript:window.addEventListener('message',"
+ "function(evt){window." + JS_INTERFACE + ".onPostMessage(evt.data)}, false)");
}
}
}
}
package com.artifex.mupdfdemo;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.Scroller;
import com.lonelypluto.pdfviewerlibrary.R;
import java.util.LinkedList;
import java.util.NoSuchElementException;
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 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;
private static final float REFLOW_SCALE_FACTOR = 0.5f;
private boolean HORIZONTAL_SCROLLING = true;// ������ʾ����������ʾ
private Adapter mAdapter;
private int mCurrent; // Adapter's index for the current view
private boolean mResetLayout;
private final SparseArray<View>
mChildViews = new SparseArray<View>(3);
// Shadows the children of the adapter view
// but with more sensible indexing
private final LinkedList<View>
mViewCache = new LinkedList<View>();
private boolean mUserInteracting; // Whether the user is interacting
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 boolean mReflow = false;
private boolean mReflowChanged = false;
private final GestureDetector
mGestureDetector;
private final ScaleGestureDetector
mScaleGestureDetector;
private final Scroller mScroller;
private final Stepper mStepper;
private int mScrollerLastX;
private int mScrollerLastY;
private float mLastScaleFocusX;
private float mLastScaleFocusY;
public static abstract class ViewMapper {
public abstract void applyToView(View view);
}
public ReaderView(Context context) {
super(context);
mGestureDetector = new GestureDetector(this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScroller = new Scroller(context);
mStepper = new Stepper(this, this);
}
public ReaderView(Context context, AttributeSet attrs) {
super(context, attrs);
// "Edit mode" means when the View is being displayed in the Android GUI editor. (this class
// is instantiated in the IDE, so we need to be a bit careful what we do).
if (isInEditMode()) {
mGestureDetector = null;
mScaleGestureDetector = null;
mScroller = null;
mStepper = null;
} else {
mGestureDetector = new GestureDetector(this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScroller = new Scroller(context);
mStepper = new Stepper(this, this);
}
}
public ReaderView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mGestureDetector = new GestureDetector(this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScroller = new Scroller(context);
mStepper = new Stepper(this, this);
}
public int getDisplayedViewIndex() {
return mCurrent;
}
public void setDisplayedViewIndex(int i) {
if (mAdapter != null) {
if (0 <= i && i < mAdapter.getCount()) {
onMoveOffChild(mCurrent);
mCurrent = i;
onMoveToChild(i);
mResetLayout = true;
requestLayout();
}
}
}
/**
* 设置横向或竖向滑动
*
* @param HORIZONTAL_SCROLLING 是否横向滑动 true:横向, false:纵向
*/
public void setHorizontalScrolling(boolean HORIZONTAL_SCROLLING) {
this.HORIZONTAL_SCROLLING = HORIZONTAL_SCROLLING;
}
public void moveToNext() {
View v = mChildViews.get(mCurrent + 1);
if (v != null)
slideViewOntoScreen(v);
}
public void moveToPrevious() {
View v = mChildViews.get(mCurrent - 1);
if (v != null)
slideViewOntoScreen(v);
}
// When advancing down the page, we want to advance by about
// 90% of a screenful. But we'd be happy to advance by between
// 80% and 95% if it means we hit the bottom in a whole number
// of steps.
private int smartAdvanceAmount(int screenHeight, int max) {
int advance = (int) (screenHeight * 0.9 + 0.5);
int leftOver = max % advance;
int steps = max / advance;
if (leftOver == 0) {
// We'll make it exactly. No adjustment
} else if ((float) leftOver / steps <= screenHeight * 0.05) {
// We can adjust up by less than 5% to make it exact.
advance += (int) ((float) leftOver / steps + 0.5);
} else {
int overshoot = advance - leftOver;
if ((float) overshoot / steps <= screenHeight * 0.1) {
// We can adjust down by less than 10% to make it exact.
advance -= (int) ((float) overshoot / steps + 0.5);
}
}
if (advance > max)
advance = max;
return advance;
}
public void smartMoveForwards() {
View v = mChildViews.get(mCurrent);
if (v == null)
return;
// The following code works in terms of where the screen is on the views;
// so for example, if the currentView is at (-100,-100), the visible
// region would be at (100,100). If the previous page was (2000, 3000) in
// size, the visible region of the previous page might be (2100 + GAP, 100)
// (i.e. off the previous page). This is different to the way the rest of
// the code in this file is written, but it's easier for me to think about.
// At some point we may refactor this to fit better with the rest of the
// code.
// screenWidth/Height are the actual width/height of the screen. e.g. 480/800
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
// bashing 'forwards' very fast.
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 bottom = screenHeight + top;
// docWidth/Height are the width/height of the scaled document e.g. 2000x3000
int docWidth = v.getMeasuredWidth();
int docHeight = v.getMeasuredHeight();
int xOffset, yOffset;
if (bottom >= docHeight) {
// We are flush with the bottom. Advance to next column.
if (right + screenWidth > docWidth) {
// No room for another column - go to next page
View nv = mChildViews.get(mCurrent + 1);
if (nv == null) // No page to advance to
return;
int nextTop = -(nv.getTop() + mYScroll + remainingY);
int nextLeft = -(nv.getLeft() + mXScroll + remainingX);
int nextDocWidth = nv.getMeasuredWidth();
int nextDocHeight = nv.getMeasuredHeight();
// Allow for the next page maybe being shorter than the screen is high
yOffset = (nextDocHeight < screenHeight ? ((nextDocHeight - screenHeight) >> 1) : 0);
if (nextDocWidth < screenWidth) {
// Next page is too narrow to fill the screen. Scroll to the top, centred.
xOffset = (nextDocWidth - screenWidth) >> 1;
} else {
// Reset X back to the left hand column
xOffset = right % screenWidth;
// Adjust in case the previous page is less wide
if (xOffset + screenWidth > nextDocWidth)
xOffset = nextDocWidth - screenWidth;
}
xOffset -= nextLeft;
yOffset -= nextTop;
} else {
// Move to top of next column
xOffset = screenWidth;
yOffset = screenHeight - bottom;
}
} else {
// Advance by 90% of the screen height downwards (in case lines are partially cut off)
xOffset = 0;
yOffset = smartAdvanceAmount(screenHeight, docHeight - bottom);
}
mScrollerLastX = mScrollerLastY = 0;
mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400);
mStepper.prod();
}
public void smartMoveBackwards() {
View v = mChildViews.get(mCurrent);
if (v == null)
return;
// The following code works in terms of where the screen is on the views;
// so for example, if the currentView is at (-100,-100), the visible
// region would be at (100,100). If the previous page was (2000, 3000) in
// size, the visible region of the previous page might be (2100 + GAP, 100)
// (i.e. off the previous page). This is different to the way the rest of
// the code in this file is written, but it's easier for me to think about.
// At some point we may refactor this to fit better with the rest of the
// code.
// screenWidth/Height are the actual width/height of the screen. e.g. 480/800
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
// bashing 'forwards' very fast.
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);
// docWidth/Height are the width/height of the scaled document e.g. 2000x3000
int docHeight = v.getMeasuredHeight();
int xOffset, yOffset;
if (top <= 0) {
// We are flush with the top. Step back to previous column.
if (left < screenWidth) {
/* No room for previous column - go to previous page */
View pv = mChildViews.get(mCurrent - 1);
if (pv == null) /* No page to advance to */
return;
int prevDocWidth = pv.getMeasuredWidth();
int prevDocHeight = pv.getMeasuredHeight();
// 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);
if (prevDocWidth < screenWidth) {
// Previous page is too narrow to fill the screen. Scroll to the bottom, centred.
xOffset = (prevDocWidth - screenWidth) >> 1;
} else {
// Reset X back to the right hand column
xOffset = (left > 0 ? left % screenWidth : 0);
if (xOffset + screenWidth > prevDocWidth)
xOffset = prevDocWidth - screenWidth;
while (xOffset + screenWidth * 2 < prevDocWidth)
xOffset += screenWidth;
}
xOffset -= prevLeft;
yOffset -= prevTop - prevDocHeight + screenHeight;
} else {
// Move to bottom of previous column
xOffset = -screenWidth;
yOffset = docHeight - screenHeight + top;
}
} else {
// Retreat by 90% of the screen height downwards (in case lines are partially cut off)
xOffset = 0;
yOffset = -smartAdvanceAmount(screenHeight, top);
}
mScrollerLastX = mScrollerLastY = 0;
mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400);
mStepper.prod();
}
public void resetupChildren() {
for (int i = 0; i < mChildViews.size(); i++)
onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i));
}
public View getCurrentView() {
return mChildViews.get(mCurrent);
}
public void applyToChildren(ViewMapper mapper) {
for (int i = 0; i < mChildViews.size(); i++)
mapper.applyToView(mChildViews.valueAt(i));
}
public void refresh(boolean reflow) {
mReflow = reflow;
mReflowChanged = true;
mResetLayout = true;
mScale = 1.0f;
mXScroll = mYScroll = 0;
requestLayout();
}
protected void onChildSetup(int i, View v) {
}
protected void onMoveToChild(int i) {
}
protected void onMoveOffChild(int i) {
}
protected void onSettle(View v) {
}
protected void onUnsettle(View v) {
}
protected void onNotInUse(View v) {
}
protected void onScaleChild(View v, Float scale) {
}
public View getView(int i) {
return mChildViews.get(i);
}
public View getDisplayedView() {
return mChildViews.get(mCurrent);
}
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;
requestLayout();
mStepper.prod();
} else if (!mUserInteracting) {
// End of an inertial scroll and the user is not interacting.
// The layout is stable
View v = mChildViews.get(mCurrent);
if (v != null)
postSettle(v);
}
}
public boolean onDown(MotionEvent arg0) {
mScroller.forceFinished(true);
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (mScaling)
return true;
View v = mChildViews.get(mCurrent);
if (v != null) {
Rect bounds = getScrollBounds(v);
switch (directionOfTravel(velocityX, velocityY)) {
case MOVING_LEFT:
if (HORIZONTAL_SCROLLING && bounds.left >= 0) {
// Fling off to the left bring next view onto screen
View vl = mChildViews.get(mCurrent + 1);
if (vl != null) {
slideViewOntoScreen(vl);
return true;
}
}
break;
case MOVING_UP:
if (!HORIZONTAL_SCROLLING && bounds.top >= 0) {
// Fling off to the top bring next view onto screen
View vl = mChildViews.get(mCurrent + 1);
if (vl != null) {
slideViewOntoScreen(vl);
return true;
}
}
break;
case MOVING_RIGHT:
if (HORIZONTAL_SCROLLING && bounds.right <= 0) {
// Fling off to the right bring previous view onto screen
View vr = mChildViews.get(mCurrent - 1);
if (vr != null) {
slideViewOntoScreen(vr);
return true;
}
}
break;
case MOVING_DOWN:
if (!HORIZONTAL_SCROLLING && bounds.bottom <= 0) {
// Fling off to the bottom bring previous view onto screen
View vr = mChildViews.get(mCurrent - 1);
if (vr != null) {
slideViewOntoScreen(vr);
return true;
}
}
break;
}
mScrollerLastX = mScrollerLastY = 0;
// If the page has been dragged out of bounds then we want to spring back
// nicely. fling jumps back into bounds instantly, so we don't want to use
// fling in that case. On the other hand, we don't want to forgo a fling
// just because of a slightly off-angle drag taking us out of bounds other
// than in the direction of the drag, so we test for out of bounds only
// in the direction of travel.
//
// Also don't fling if out of bounds in any direction by more than fling
// margin
Rect expandedBounds = new Rect(bounds);
expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN);
if (withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY)
&& expandedBounds.contains(0, 0)) {
mScroller.fling(0, 0, (int) velocityX, (int) velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom);
mStepper.prod();
}
}
return true;
}
public void onLongPress(MotionEvent e) {
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
if (!mScaling) {
mXScroll -= distanceX;
mYScroll -= distanceY;
requestLayout();
}
return true;
}
public void onShowPress(MotionEvent e) {
}
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
public boolean onScale(ScaleGestureDetector detector) {
float previousScale = mScale;
float scale_factor = mReflow ? REFLOW_SCALE_FACTOR : 1.0f;
float min_scale = MIN_SCALE * scale_factor;
float max_scale = MAX_SCALE * scale_factor;
mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), min_scale), max_scale);
if (mReflow) {
View v = mChildViews.get(mCurrent);
if (v != null)
onScaleChild(v, mScale);
} else {
float factor = mScale / previousScale;
View v = mChildViews.get(mCurrent);
if (v != null) {
float currentFocusX = detector.getFocusX();
float currentFocusY = detector.getFocusY();
// Work out the focus point relative to the view top left
int viewFocusX = (int) currentFocusX - (v.getLeft() + mXScroll);
int viewFocusY = (int) currentFocusY - (v.getTop() + mYScroll);
// Scroll to maintain the focus point
mXScroll += viewFocusX - viewFocusX * factor;
mYScroll += viewFocusY - viewFocusY * factor;
if (mLastScaleFocusX >= 0)
mXScroll += currentFocusX - mLastScaleFocusX;
if (mLastScaleFocusY >= 0)
mYScroll += currentFocusY - mLastScaleFocusY;
mLastScaleFocusX = currentFocusX;
mLastScaleFocusY = currentFocusY;
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;
mLastScaleFocusX = mLastScaleFocusY = -1;
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) {
if (mReflow) {
applyToChildren(new ViewMapper() {
@Override
public void applyToView(View view) {
onScaleChild(view, mScale);
}
});
}
mScaling = false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
mUserInteracting = true;
}
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
mUserInteracting = false;
View v = mChildViews.get(mCurrent);
if (v != null) {
if (mScroller.isFinished()) {
// If, at the end of user interaction, there is no
// current inertial scroll in operation then animate
// the view onto screen if necessary
slideViewOntoScreen(v);
}
if (mScroller.isFinished()) {
// If still there is no inertial scroll in operation
// then the layout is stable
postSettle(v);
}
}
}
requestLayout();
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int n = getChildCount();
for (int i = 0; i < n; i++)
measureView(getChildAt(i));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
try {
if (mAdapter != null) {
onLayout2(changed, left, top, right, bottom);
}
} catch (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.
if (!memAlert) {
memAlert = true;
AlertDialog alertDialog = MuPDFActivity.getAlertBuilder().create();
alertDialog.setMessage("Out of memory during layout");
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
memAlert = false;
}
});
alertDialog.show();
}
}
}
private boolean memAlert = false;
private void onLayout2(boolean changed, int left, int top, int right,
int bottom) {
// "Edit mode" means when the View is being displayed in the Android GUI editor. (this class
// is instantiated in the IDE, so we need to be a bit careful what we do).
if (isInEditMode())
return;
View cv = mChildViews.get(mCurrent);
Point cvOffset;
if (!mResetLayout) {
// Move to next or previous if current is sufficiently off center
if (cv != null) {
boolean move;
cvOffset = subScreenSizeOffset(cv);
// cv.getRight() may be out of date with the current scale
// so add left to the measured width for the correct position
if (HORIZONTAL_SCROLLING)
move = cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP / 2 + mXScroll < getWidth() / 2;
else
move = cv.getTop() + cv.getMeasuredHeight() + cvOffset.y + GAP / 2 + mYScroll < getHeight() / 2;
if (move && mCurrent + 1 < mAdapter.getCount()) {
postUnsettle(cv);
// post to invoke test for end of animation
// where we must set hq area for the new current view
mStepper.prod();
onMoveOffChild(mCurrent);
mCurrent++;
onMoveToChild(mCurrent);
}
if (HORIZONTAL_SCROLLING)
move = cv.getLeft() - cvOffset.x - GAP / 2 + mXScroll >= getWidth() / 2;
else
move = cv.getTop() - cvOffset.y - GAP / 2 + mYScroll >= getHeight() / 2;
if (move && mCurrent > 0) {
postUnsettle(cv);
// post to invoke test for end of animation
// where we must set hq area for the new current view
mStepper.prod();
onMoveOffChild(mCurrent);
mCurrent--;
onMoveToChild(mCurrent);
}
}
// Remove not needed children and hold them for reuse
int numChildren = mChildViews.size();
int childIndices[] = new int[numChildren];
for (int i = 0; i < numChildren; i++)
childIndices[i] = mChildViews.keyAt(i);
for (int i = 0; i < numChildren; i++) {
int ai = childIndices[i];
if (ai < mCurrent - 1 || ai > mCurrent + 1) {
View v = mChildViews.get(ai);
onNotInUse(v);
mViewCache.add(v);
removeViewInLayout(v);
mChildViews.remove(ai);
}
}
} else {
mResetLayout = false;
mXScroll = mYScroll = 0;
// Remove all children and hold them for reuse
int numChildren = mChildViews.size();
for (int i = 0; i < numChildren; i++) {
View v = mChildViews.valueAt(i);
onNotInUse(v);
mViewCache.add(v);
removeViewInLayout(v);
}
mChildViews.clear();
// Don't reuse cached views if the adapter has changed
if (mReflowChanged) {
mReflowChanged = false;
mViewCache.clear();
}
// post to ensure generation of hq area
mStepper.prod();
}
// Ensure current view is present
int cvLeft, cvRight, cvTop, cvBottom;
boolean notPresent = (mChildViews.get(mCurrent) == null);
cv = getOrCreateChild(mCurrent);
// When the view is sub-screen-size in either dimension we
// offset it to center within the screen area, and to keep
// the views spaced out
cvOffset = subScreenSizeOffset(cv);
if (notPresent) {
//Main item not already present. Just place it top left
cvLeft = cvOffset.x;
cvTop = cvOffset.y;
} else {
// Main item already present. Adjust by scroll offsets
cvLeft = cv.getLeft() + mXScroll;
cvTop = cv.getTop() + mYScroll;
}
// Scroll values have been accounted for
mXScroll = mYScroll = 0;
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;
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;
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;
}
cv.layout(cvLeft, cvTop, cvRight, cvBottom);
if (mCurrent > 0) {
View lv = getOrCreateChild(mCurrent - 1);
Point leftOffset = subScreenSizeOffset(lv);
if (HORIZONTAL_SCROLLING) {
int gap = leftOffset.x + GAP + cvOffset.x;
lv.layout(cvLeft - lv.getMeasuredWidth() - gap,
(cvBottom + cvTop - lv.getMeasuredHeight()) / 2,
cvLeft - gap,
(cvBottom + cvTop + lv.getMeasuredHeight()) / 2);
} else {
int gap = leftOffset.y + GAP + cvOffset.y;
lv.layout((cvLeft + cvRight - lv.getMeasuredWidth()) / 2,
cvTop - lv.getMeasuredHeight() - gap,
(cvLeft + cvRight + lv.getMeasuredWidth()) / 2,
cvTop - gap);
}
}
if (mCurrent + 1 < mAdapter.getCount()) {
View rv = getOrCreateChild(mCurrent + 1);
Point rightOffset = subScreenSizeOffset(rv);
if (HORIZONTAL_SCROLLING) {
int gap = cvOffset.x + GAP + rightOffset.x;
rv.layout(cvRight + gap,
(cvBottom + cvTop - rv.getMeasuredHeight()) / 2,
cvRight + rv.getMeasuredWidth() + gap,
(cvBottom + cvTop + rv.getMeasuredHeight()) / 2);
} else {
int gap = cvOffset.y + GAP + rightOffset.y;
rv.layout((cvLeft + cvRight - rv.getMeasuredWidth()) / 2,
cvBottom + gap,
(cvLeft + cvRight + rv.getMeasuredWidth()) / 2,
cvBottom + gap + rv.getMeasuredHeight());
}
}
invalidate();
}
@Override
public Adapter getAdapter() {
return mAdapter;
}
@Override
public View getSelectedView() {
return null;
}
@Override
public void setAdapter(Adapter adapter) {
// release previous adapter's bitmaps
if (null != mAdapter && adapter != mAdapter) {
if (adapter instanceof MuPDFPageAdapter) {
((MuPDFPageAdapter) adapter).releaseBitmaps();
}
}
mAdapter = adapter;
requestLayout();
}
@Override
public void setSelection(int arg0) {
throw new UnsupportedOperationException(getContext().getString(R.string.not_supported));
}
private View getCached() {
if (mViewCache.size() == 0)
return null;
else
return mViewCache.removeFirst();
}
private View getOrCreateChild(int i) {
View v = mChildViews.get(i);
if (v == null) {
if (mAdapter != null) {
v = mAdapter.getView(i, getCached(), this);
addAndMeasureChild(i, v);
onChildSetup(i, v);
onScaleChild(v, mScale);
}
}
return v;
}
private void addAndMeasureChild(int i, View v) {
LayoutParams params = v.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
addViewInLayout(v, 0, params, true);
mChildViews.append(i, v); // Record the view against it's adapter index
measureView(v);
}
private void measureView(View v) {
// See what size the view wants to be
v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (!mReflow) {
// Work out a scale that will fit it to this view
float scale = Math.min((float) getWidth() / (float) v.getMeasuredWidth(),
(float) getHeight() / (float) v.getMeasuredHeight());
// Use the fitting values scaled by our current scale factor
v.measure(MeasureSpec.EXACTLY | (int) (v.getMeasuredWidth() * scale * mScale),
MeasureSpec.EXACTLY | (int) (v.getMeasuredHeight() * scale * mScale));
} else {
v.measure(MeasureSpec.EXACTLY | (int) (v.getMeasuredWidth()),
MeasureSpec.EXACTLY | (int) (v.getMeasuredHeight()));
}
}
private Rect getScrollBounds(int left, int top, int right, int bottom) {
int xmin = getWidth() - right;
int xmax = -left;
int ymin = getHeight() - bottom;
int ymax = -top;
// In either dimension, if view smaller than screen then
// constrain it to be central
if (xmin > xmax) xmin = xmax = (xmin + xmax) / 2;
if (ymin > ymax) ymin = ymax = (ymin + ymax) / 2;
return new Rect(xmin, ymin, xmax, ymax);
}
private Rect getScrollBounds(View v) {
// There can be scroll amounts not yet accounted for in
// 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);
}
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));
}
private void postSettle(final View v) {
// onSettle and onUnsettle are posted so that the calls
// wont be executed until after the system has performed
// layout.
post(new Runnable() {
public void run() {
onSettle(v);
}
});
}
private void postUnsettle(final View v) {
post(new Runnable() {
public void run() {
onUnsettle(v);
}
});
}
private void slideViewOntoScreen(View v) {
Point corr = getCorrection(getScrollBounds(v));
if (corr.x != 0 || corr.y != 0) {
mScrollerLastX = mScrollerLastY = 0;
mScroller.startScroll(0, 0, corr.x, corr.y, 400);
mStepper.prod();
}
}
private Point subScreenSizeOffset(View v) {
int mw = 0;
int mh = 0;
if (v != null) {
mw = v.getMeasuredWidth();
mh = v.getMeasuredHeight();
}
return new Point(Math.max((getWidth() - mw) / 2, 0),
Math.max((getHeight() - mh) / 2, 0));
}
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;
}
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;
default:
throw new NoSuchElementException();
}
}
}
package com.artifex.mupdfdemo;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.app.Activity;
import android.view.View;
import com.lonelypluto.pdfviewerlibrary.R;
public class SafeAnimatorInflater {
private View mView;
public SafeAnimatorInflater(Activity activity, int animation, View view) {
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(activity, R.animator.info);
mView = view;
set.setTarget(view);
set.addListener(new Animator.AnimatorListener() {
public void onAnimationStart(Animator animation) {
mView.setVisibility(View.VISIBLE);
}
public void onAnimationRepeat(Animator animation) {
}
public void onAnimationEnd(Animator animation) {
mView.setVisibility(View.INVISIBLE);
}
public void onAnimationCancel(Animator animation) {
}
});
set.start();
}
}
package com.artifex.mupdfdemo;
import android.graphics.Bitmap;
import android.util.Log;
import com.lowagie.text.BadElementException;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
/**
* Created by Jammy on 2016/6/23.
*/
public class SavePdf {
private float defaultScale = 0.90756303f;
public void setWidthScale(float widthScale) {
this.widthScale = widthScale;
}
public void setHeightScale(float heightScale) {
this.heightScale = heightScale;
}
float widthScale;
float heightScale;
String inPath;/////当前的PDF地址
String outPath;////要输出的PDF地址
private int pageNum;/////签名所在的页码
private Bitmap bitmap;//////签名图像
private float scale;
private float density; ///手机屏幕的分辨率密度
private float width;
private float height;
/**
* 设置放大比例
*
* @param scale
*/
public void setScale(float scale) {
this.scale = scale;
}
/**
* 设置宽高
*
* @param
*/
public void setWH(float width, float height) {
this.width = width;
this.height = height;
}
/**
* 设置分辨率密度
*
* @param density
*/
public void setDensity(float density) {
this.density = density;
}
/**
* 设置嵌入的图片
*
* @param bitmap
*/
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
/**
* 设置需要嵌入的页面
*
* @param pageNum
*/
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public SavePdf(String inPath, String outPath) {
this.inPath = inPath;
this.outPath = outPath;
}
/**
* 将图片加入PDF并保存
*/
public void addText() {
try {
PdfReader reader = new PdfReader(inPath, "PDF".getBytes());///打开要写入的PDF
Log.e("addText", "1");
FileOutputStream outputStream = new FileOutputStream(outPath);//设置涂鸦后的PDF
Log.e("addText", "2");
PdfStamper stamp;
stamp = new PdfStamper(reader, outputStream);
Log.e("addText", "3 " + pageNum);
PdfContentByte over = stamp.getOverContent(pageNum);//////用于设置在第几页打印签名
Log.e("addText", "4");
if (bitmap != null) {
byte[] bytes = Bitmap2Bytes(bitmap);
Image img = Image.getInstance(bytes);//将要放到PDF的图片传过来,要设置为byte[]类型
com.lowagie.text.Rectangle rectangle = reader.getPageSize(pageNum);
img.setAlignment(Image.MIDDLE);// 图像在文档中的对齐方式
img.scalePercent(rectangle.getWidth() * widthScale * 100);
img.setAbsolutePosition(width * (rectangle.getWidth() * widthScale) * (scale), rectangle.getHeight() - ((height) * (rectangle.getWidth() * widthScale) * (scale / defaultScale)) + img.getHeight() / 2 * widthScale * 100);
over.addImage(img);
}
Log.e("addText", "5");
stamp.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (BadElementException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 将BitMap转换为Bytes
*
* @param bm
* @return
*/
public byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
}
package com.artifex.mupdfdemo;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.RectF;
import android.os.Handler;
import com.lonelypluto.pdfviewerlibrary.R;
class ProgressDialogX extends ProgressDialog {
public ProgressDialogX(Context context) {
super(context);
}
private boolean mCancelled = false;
public boolean isCancelled() {
return mCancelled;
}
@Override
public void cancel() {
mCancelled = true;
super.cancel();
}
}
public abstract class SearchTask {
private static final int SEARCH_PROGRESS_DELAY = 200;
private final Context mContext;
private final MuPDFCore mCore;
private final Handler mHandler;
private final AlertDialog.Builder mAlertBuilder;
private AsyncTask<Void, Integer, SearchTaskResult> mSearchTask;
public SearchTask(Context context, MuPDFCore core) {
mContext = context;
mCore = core;
mHandler = new Handler();
mAlertBuilder = new AlertDialog.Builder(context);
}
protected abstract void onTextFound(SearchTaskResult result);
public void stop() {
if (mSearchTask != null) {
mSearchTask.cancel(true);
mSearchTask = null;
}
}
public void go(final String text, int direction, int displayPage, int searchPage) {
if (mCore == null)
return;
stop();
final int increment = direction;
final int startIndex = searchPage == -1 ? displayPage : searchPage + increment;
final ProgressDialogX progressDialog = new ProgressDialogX(mContext);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle(mContext.getString(R.string.searching_));
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
stop();
}
});
progressDialog.setMax(mCore.countPages());
mSearchTask = new AsyncTask<Void, Integer, SearchTaskResult>() {
@Override
protected SearchTaskResult doInBackground(Void... params) {
int index = startIndex;
while (0 <= index && index < mCore.countPages() && !isCancelled()) {
publishProgress(index);
RectF searchHits[] = mCore.searchPage(index, text);
if (searchHits != null && searchHits.length > 0)
return new SearchTaskResult(text, index, searchHits);
index += increment;
}
return null;
}
@Override
protected void onPostExecute(SearchTaskResult result) {
progressDialog.cancel();
if (result != null) {
onTextFound(result);
} else {
mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurrences_found);
AlertDialog alert = mAlertBuilder.create();
alert.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.dismiss),
(DialogInterface.OnClickListener) null);
alert.show();
}
}
@Override
protected void onCancelled() {
progressDialog.cancel();
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setProgress(values[0].intValue());
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mHandler.postDelayed(new Runnable() {
public void run() {
if (!progressDialog.isCancelled()) {
progressDialog.show();
progressDialog.setProgress(startIndex);
}
}
}, SEARCH_PROGRESS_DELAY);
}
};
mSearchTask.execute();
}
}
package com.artifex.mupdfdemo;
import android.graphics.RectF;
public class SearchTaskResult {
public final String txt;
public final int pageNumber;
public final RectF searchBoxes[];
static private SearchTaskResult singleton;
SearchTaskResult(String _txt, int _pageNumber, RectF _searchBoxes[]) {
txt = _txt;
pageNumber = _pageNumber;
searchBoxes = _searchBoxes;
}
static public SearchTaskResult get() {
return singleton;
}
static public void set(SearchTaskResult r) {
singleton = r;
}
}
package com.artifex.mupdfdemo;
public class Separation
{
String name;
int rgba;
int cmyk;
public Separation(String name, int rgba, int cmyk)
{
this.name = name;
this.rgba = rgba;
this.cmyk = cmyk;
}
}
package com.artifex.mupdfdemo;
import android.annotation.SuppressLint;
import android.os.Build;
import android.view.View;
public class Stepper {
protected final View mPoster;
protected final Runnable mTask;
protected boolean mPending;
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();
}
});
}
}
}
}
package com.artifex.mupdfdemo;
import android.graphics.RectF;
public class TextChar extends RectF {
public char c;
public TextChar(float x0, float y0, float x1, float y1, char _c) {
super(x0, y0, x1, y1);
c = _c;
}
}
package com.artifex.mupdfdemo;
import android.graphics.RectF;
public class TextWord extends RectF {
public String w;
public TextWord() {
super();
w = new String();
}
public void Add(TextChar tc) {
super.union(tc);
w = w.concat(new String(new char[]{tc.c}));
}
}
package com.artifex.mupdfdemo;
public enum WidgetType {
NONE,
TEXT,
LISTBOX,
COMBOBOX,
SIGNATURE
}
package com.artifex.mupdfdemo.widget;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.customview.widget.ViewDragHelper;
public class VDHDeepLayout extends RelativeLayout {
private ViewDragHelper mViewDragHelper;
/**
* 第一个View,可自由拖动的 view
*/
private View mDragView;
private Point mAutoBackOriginPos = new Point();
public VDHDeepLayout(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* 创建实例需要3个参数,第一个就是当前的ViewGroup,第二个sensitivity,主要用于设置touchSlop:
*/
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
/**
* 如何返回ture则表示可以捕获该view,你可以根据传入的第一个view参数决定哪些可以捕获
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
// mEdgeTrackerView禁止直接拖动
return child == mDragView;
}
/**
* 可以在该方法中对child移动的边界进行控制,left , top 分别为即将移动到的位置,
* 比如横向的情况下,我希望只在ViewGroup的内部移动,
* 即:最小>=paddingleft,最大<=ViewGroup.getWidth()-paddingright-child.getWidth。
* 若直接返回left则会拖到边界外(针对于所有的View)。
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mDragView.getWidth() - leftBound;
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
/**
* 优化了一下,对移动的Y边界进行控制
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = getPaddingTop();
int bottomBound = getHeight() - mDragView.getHeight() - topBound;
int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
/**
* 手指释放时回调:
*
* 如果是mAutoBackView则调用settleCapturedViewAt回到初始的位置。
* 大家可以看到紧随其后的代码是invalidate();
* 因为其内部使用的是mScroller.startScroll,所以别忘了需要invalidate()以及结合computeScroll方法一起。
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
}
/**
* 在边界拖动时回调:
*
* 我们在onEdgeDragStarted回调方法中,主动通过captureChildView对其进行捕获,
* 该方法可以绕过tryCaptureView,所以我们的tryCaptureView虽然并为返回true,但却不影响。
* 注意如果需要使用边界检测需要添加: ViewDragHelper.EDGE_LEFT
* */
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
}
// clickable=true,意思就是子View可以消耗事件
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
});
// 注意如果需要使用边界检测需要添加 ViewDragHelper.EDGE_LEFT
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
// super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mAutoBackOriginPos.x = mDragView.getLeft();
mAutoBackOriginPos.y = mDragView.getTop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragView = getChildAt(0);
}
}
package com.lonelypluto.pdflibrary.constants;
public class SPConsts {
/**
* SharedPreferences名字
*/
public static final String SP_NAME = "lonelypluto_mupdf";
/**
* 搜索文字颜色
*/
public static final String SP_COLOR_SEARCH_TEXT = "sp_color_search_text";
}
package com.lonelypluto.pdflibrary.utils;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import com.lonelypluto.pdflibrary.constants.SPConsts;
public class SharedPreferencesUtil {
private static SharedPreferences sharedPreferences;
/**
* 初始化 在application中
*
* @param application
*/
public static void init(Application application) {
sharedPreferences = application.getSharedPreferences(SPConsts.SP_NAME, Context.MODE_PRIVATE);
}
/**
* 获取搜索文字颜色值
*
* @return
*/
public static int getSearchTextColor() {
int color;
color = sharedPreferences.getInt(SPConsts.SP_COLOR_SEARCH_TEXT, 0x80ff5722);
return color;
}
/**
* 插入
*
* @param key
* @param value
*/
public static void put(String key, Object value) {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (value.getClass() == Boolean.class) {
editor.putBoolean(key, (Boolean) value);
}
if (value.getClass() == String.class) {
editor.putString(key, (String) value);
}
if (value.getClass() == Integer.class) {
editor.putInt(key, ((Integer) value).intValue());
}
editor.commit();
}
/**
* @param context
* @param keys
*/
public static void cleanStringValue(Context context, String... keys) {
for (String key : keys) {
SharedPreferences settings = context.getSharedPreferences(
SPConsts.SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(key)) {
editor.remove(key).commit();
}
}
}
/**
* 清除
*/
public static void clear() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(SPConsts.SP_COLOR_SEARCH_TEXT);
editor.commit();
}
}
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<objectAnimator
android:propertyName="alpha"
android:valueFrom="0.0"
android:valueTo="1.0"
android:duration="200" />
<objectAnimator
android:propertyName="alpha"
android:valueTo="1.0"
android:duration="800" />
<objectAnimator
android:propertyName="alpha"
android:valueTo="0.0"
android:duration="400" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp" />
<padding android:left="16dp"
android:right="16dp"
android:top="16dp"
android:bottom="16dp" />
<solid android:color="@color/busy_indicator" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/button_pressed" />
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/button_normal" />
<stroke android:width="4dp" android:color="@color/button_pressed" />
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/button_normal" />
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
</shape>
</item>
</selector>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<padding android:left="8dp"
android:right="8dp"
android:top="1dp"
android:bottom="2dp" />
<solid android:color="@color/page_indicator" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="@color/text_pressed" />
<stroke android:width="4dp" android:color="@color/text_border_pressed" />
<padding
android:left="12dp"
android:right="12dp"
android:top="8dp"
android:bottom="8dp" />
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/text_normal" />
<stroke android:width="4dp" android:color="@color/text_border_focused" />
<padding
android:left="12dp"
android:right="12dp"
android:top="8dp"
android:bottom="8dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/text_normal" />
<stroke android:width="4dp" android:color="@color/text_border_normal" />
<padding
android:left="12dp"
android:right="12dp"
android:top="8dp"
android:bottom="8dp" />
</shape>
</item>
</selector>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<stroke android:width="6dp" android:color="@color/seek_progress" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<size android:width="28dp" android:height="28dp" />
<solid android:color="@color/seek_thumb" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/darkdenim3"
android:tileMode="repeat" />
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ViewAnimator
android:id="@+id/switcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" >
<RelativeLayout
android:id="@+id/topBar0Main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<TextView
android:id="@+id/docNameText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/linkButton"
android:layout_alignParentLeft="true"
android:paddingLeft="16dp"
android:singleLine="true"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageButton
android:id="@+id/sepsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="Separations"
android:background="@drawable/button"
android:onClick="OnSepsButtonClick"
android:src="@drawable/ic_sep" />
<ImageButton
android:id="@+id/linkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/reflowButton"
android:contentDescription="@string/toggle_links"
android:background="@drawable/button"
android:src="@drawable/ic_link" />
<ImageButton
android:id="@+id/reflowButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/outlineButton"
android:contentDescription="@string/toggle_reflow_mode"
android:background="@drawable/button"
android:src="@drawable/ic_reflow" />
<ImageButton
android:id="@+id/outlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/searchButton"
android:contentDescription="@string/outline_title"
android:background="@drawable/button"
android:src="@drawable/ic_list" />
<ImageButton
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/moreButton"
android:contentDescription="@string/search_document"
android:background="@drawable/button"
android:src="@drawable/ic_magnifying_glass" />
<ImageButton
android:id="@+id/moreButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/more"
android:background="@drawable/button"
android:onClick="OnMoreButtonClick"
android:src="@drawable/ic_more" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/topBar1Search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<ImageButton
android:id="@+id/cancelSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/cancel"
android:background="@drawable/button"
android:onClick="OnCancelSearchButtonClick"
android:src="@drawable/ic_cancel" />
<EditText
android:id="@+id/searchText"
android:background="@drawable/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/cancelSearch"
android:layout_toLeftOf="@+id/searchBack"
android:inputType="text"
android:hint="@string/search"
android:singleLine="true" />
<ImageButton
android:id="@+id/searchBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/searchForward"
android:contentDescription="@string/search_backwards"
android:background="@drawable/button"
android:src="@drawable/ic_arrow_left" />
<ImageButton
android:id="@+id/searchForward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/search_forwards"
android:background="@drawable/button"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/topBar2Annot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<ImageButton
android:id="@+id/cancelAnnotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/cancel"
android:background="@drawable/button"
android:onClick="OnCancelAnnotButtonClick"
android:src="@drawable/ic_cancel" />
<ImageButton
android:id="@+id/highlightButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/underlineButton"
android:contentDescription="@string/highlight"
android:background="@drawable/button"
android:onClick="OnHighlightButtonClick"
android:src="@drawable/ic_highlight" />
<ImageButton
android:id="@+id/underlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/strikeOutButton"
android:contentDescription="@string/underline"
android:background="@drawable/button"
android:onClick="OnUnderlineButtonClick"
android:src="@drawable/ic_underline" />
<ImageButton
android:id="@+id/strikeOutButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/inkButton"
android:contentDescription="@string/strike_out"
android:background="@drawable/button"
android:onClick="OnStrikeOutButtonClick"
android:src="@drawable/ic_strike" />
<ImageButton
android:id="@+id/inkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/ink"
android:background="@drawable/button"
android:onClick="OnInkButtonClick"
android:src="@drawable/ic_pen" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/topBar3Delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<ImageButton
android:id="@+id/cancelDeleteButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/cancel"
android:background="@drawable/button"
android:onClick="OnCancelDeleteButtonClick"
android:src="@drawable/ic_cancel" />
<TextView
android:id="@+id/deleteLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/cancelDeleteButton"
android:layout_toLeftOf="@+id/deleteButton"
android:gravity="center"
android:singleLine="true"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:text="@string/delete"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageButton
android:id="@+id/deleteButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/delete"
android:background="@drawable/button"
android:onClick="OnDeleteButtonClick"
android:src="@drawable/ic_trash" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/topBar4More"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<ImageButton
android:id="@+id/cancelMoreButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/cancel"
android:background="@drawable/button"
android:onClick="OnCancelMoreButtonClick"
android:src="@drawable/ic_cancel" />
<ImageButton
android:id="@+id/proofButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/printButton"
android:contentDescription="@string/proof"
android:background="@drawable/button"
android:onClick="OnProofButtonClick"
android:src="@drawable/ic_proof" />
<ImageButton
android:id="@+id/printButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/copyTextButton"
android:contentDescription="@string/print"
android:background="@drawable/button"
android:onClick="OnPrintButtonClick"
android:src="@drawable/ic_print" />
<ImageButton
android:id="@+id/copyTextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/editAnnotButton"
android:layout_alignWithParentIfMissing="true"
android:contentDescription="@string/copy_text_to_the_clipboard"
android:background="@drawable/button"
android:onClick="OnCopyTextButtonClick"
android:src="@drawable/ic_clipboard" />
<ImageButton
android:id="@+id/editAnnotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/edit_annotations"
android:background="@drawable/button"
android:onClick="OnEditAnnotButtonClick"
android:src="@drawable/ic_annotation" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/topBar5Accept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/toolbar" >
<ImageButton
android:id="@+id/cancelAcceptButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/cancel"
android:background="@drawable/button"
android:onClick="OnCancelAcceptButtonClick"
android:src="@drawable/ic_cancel" />
<TextView
android:id="@+id/annotType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/cancelAcceptButton"
android:layout_toLeftOf="@+id/acceptButton"
android:gravity="center"
android:singleLine="true"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageButton
android:id="@+id/acceptButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/accept"
android:background="@drawable/button"
android:onClick="OnAcceptButtonClick"
android:src="@drawable/ic_check" />
</RelativeLayout>
</ViewAnimator>
<RelativeLayout
android:id="@+id/lowerButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" >
<SeekBar
android:id="@+id/pageSlider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="0dp"
android:thumb="@drawable/seek_thumb"
android:progressDrawable="@drawable/seek_progress"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:background="@color/toolbar"
/>
<TextView
android:id="@+id/pageNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/pageSlider"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:background="@drawable/page_num"
android:textColor="#FFFFFF"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/pageSlider"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="@drawable/page_num"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFFFF" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/page"
android:singleLine="true"
android:layout_centerVertical="true"
android:paddingLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/title"
android:layout_alignBottom="@+id/title"
android:layout_alignParentRight="true"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true" />
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/icon"
android:paddingBottom="8dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:singleLine="false"
android:minLines="3"
android:inputType="textMultiLine"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</EditText>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="canvas">#404040</color>
<color name="toolbar">#C0000000</color>
<color name="page_indicator">#C0202020</color>
<color name="busy_indicator">#C0202020</color>
<color name="button_normal">#00000000</color>
<color name="button_pressed">#FF2572AC</color>
<color name="text_normal">#FFFFFF</color>
<color name="text_pressed">#FFFFFF</color>
<color name="text_border_normal">#000000</color>
<color name="text_border_pressed">#2572AC</color>
<color name="text_border_focused">#000000</color>
<color name="seek_thumb">#2572AC</color>
<color name="seek_progress">#FFFFFF</color>
<color name="muPDFReaderView_bg">#ffffff</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_media_warning">Storage media not present</string>
<string name="no_media_hint">Sharing the storage media with a PC can make it inaccessible</string>
<string name="cancel">Cancel</string>
<string name="search_backwards">Search backwards</string>
<string name="search_forwards">Search forwards</string>
<string name="search_document">Search document</string>
<string name="picker_title_App_Ver_Dir">%1$s %2$s: %3$s</string>
<string name="outline_title">Table of Contents</string>
<string name="enter_password">Enter password</string>
<string name="text_not_found">Text not found</string>
<string name="searching_">Searching&#8230;</string>
<string name="toggle_links">Highlight and enable links</string>
<string name="no_further_occurrences_found">No further occurrences found</string>
<string name="select">Select</string>
<string name="search">Search</string>
<string name="copy">Copy</string>
<string name="strike_out">Strike-out</string>
<string name="delete">Delete</string>
<string name="highlight">Highlight</string>
<string name="underline">Underline</string>
<string name="edit_annotations">Edit annotations</string>
<string name="ink">Ink</string>
<string name="save">Save</string>
<string name="proof">Proof</string>
<string name="separation">Separation</string>
<string name="print">Print</string>
<string name="dismiss">Dismiss</string>
<string name="parent_directory">[Up one level]</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="entering_reflow_mode">Entering reflow mode</string>
<string name="leaving_reflow_mode">Leaving reflow mode</string>
<string name="print_failed">Print failed</string>
<string name="select_text">Select text</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="no_text_selected">No text selected</string>
<string name="draw_annotation">Draw annotation</string>
<string name="nothing_to_save">Nothing to save</string>
<string name="document_has_changes_save_them_">Document has changes. Save them?</string>
<string name="cannot_open_document">Cannot open document</string>
<string name="cannot_open_document_Reason">Cannot open document: %1$s</string>
<string name="cannot_open_file_Path">Cannot open file: %1$s</string>
<string name="cannot_open_buffer">Cannot open buffer</string>
<string name="fill_out_text_field">Fill out text field</string>
<string name="okay">Okay</string>
<string name="choose_value">Choose value</string>
<string name="not_supported">Not supported</string>
<string name="copy_text_to_the_clipboard">Copy text to the clipboard</string>
<string name="more">More</string>
<string name="accept">Accept</string>
<string name="copy_text">copy text</string>
<string name="format_currently_not_supported">Format currently not supported</string>
<string name="toggle_reflow_mode">Toggle reflow mode</string>
<string name="select_certificate_and_sign">Select certificate and sign?</string>
<string name="signature_checked">Signature checked</string>
</resources>
<resources>
<style name="AppBaseTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen">
<item name="android:windowBackground">@drawable/tiled_background</item>
</style>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment