{
- private static final int CACHE_SIZE = 16;
- private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
- private int[] mKey = new int[CACHE_SIZE];
- private V[] mValue = (V[]) new Object[CACHE_SIZE];
- private int mCount; // number of items in this cache
-
- // Puts a value into the cache. If the cache is full, also returns
- // a less used item, otherwise returns null.
- public V put(int key, V value) {
- if (mCount == CACHE_SIZE) {
- V old = mValue[CACHE_SIZE - 1]; // remove the last item
- mKey[CACHE_SIZE - 1] = key;
- mValue[CACHE_SIZE - 1] = value;
- return old;
- } else {
- mKey[mCount] = key;
- mValue[mCount] = value;
- mCount++;
- return null;
- }
- }
-
- public V get(int key) {
- for (int i = 0; i < mCount; i++) {
- if (mKey[i] == key) {
- // Move the accessed item one position to the front, so it
- // will less likely to be removed when cache is full. Only
- // do this if the cache is starting to get full.
- if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
- int tmpKey = mKey[i];
- mKey[i] = mKey[i - 1];
- mKey[i - 1] = tmpKey;
-
- V tmpValue = mValue[i];
- mValue[i] = mValue[i - 1];
- mValue[i - 1] = tmpValue;
- }
- return mValue[i];
- }
- }
- return null;
- }
-
- public void clear() {
- for (int i = 0; i < mCount; i++) {
- mValue[i] = null; // make sure it's can be garbage-collected.
- }
- mCount = 0;
- }
-
- public int size() {
- return mCount;
- }
-
- public V valueAt(int i) {
- return mValue[i];
- }
- }
-
- private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
- int key = w;
- key = (key << 16) | h;
- NinePatchInstance instance = mInstanceCache.get(key);
-
- if (instance == null) {
- instance = new NinePatchInstance(this, w, h);
- NinePatchInstance removed = mInstanceCache.put(key, instance);
- if (removed != null) {
- removed.recycle(canvas);
- }
- }
-
- return instance;
- }
-
- @Override
- public void draw(GLCanvas canvas, int x, int y, int w, int h) {
- if (!isLoaded()) {
- mInstanceCache.clear();
- }
-
- if (w != 0 && h != 0) {
- findInstance(canvas, w, h).draw(canvas, this, x, y);
- }
- }
-
- @Override
- public void recycle() {
- super.recycle();
- GLCanvas canvas = mCanvasRef;
- if (canvas == null) return;
- int n = mInstanceCache.size();
- for (int i = 0; i < n; i++) {
- NinePatchInstance instance = mInstanceCache.valueAt(i);
- instance.recycle(canvas);
- }
- mInstanceCache.clear();
- }
-}
-
-// This keeps data for a specialization of NinePatchTexture with the size
-// (width, height). We pre-compute the coordinates for efficiency.
-class NinePatchInstance {
-
- @SuppressWarnings("unused")
- private static final String TAG = "NinePatchInstance";
-
- // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
- private static final int VERTEX_BUFFER_SIZE = 16 * 2;
-
- // We need 22 indices for a normal nine-patch image, plus 2 for each
- // transparent region. Current there are at most 1 transparent region.
- private static final int INDEX_BUFFER_SIZE = 22 + 2;
-
- private FloatBuffer mXyBuffer;
- private FloatBuffer mUvBuffer;
- private ByteBuffer mIndexBuffer;
-
- // Names for buffer names: xy, uv, index.
- private int mXyBufferName = -1;
- private int mUvBufferName;
- private int mIndexBufferName;
-
- private int mIdxCount;
-
- public NinePatchInstance(NinePatchTexture tex, int width, int height) {
- NinePatchChunk chunk = tex.getNinePatchChunk();
-
- if (width <= 0 || height <= 0) {
- throw new RuntimeException("invalid dimension");
- }
-
- // The code should be easily extended to handle the general cases by
- // allocating more space for buffers. But let's just handle the only
- // use case.
- if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
- throw new RuntimeException("unsupported nine patch");
- }
-
- float divX[] = new float[4];
- float divY[] = new float[4];
- float divU[] = new float[4];
- float divV[] = new float[4];
-
- int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
- int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
-
- prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
- }
-
- /**
- * Stretches the texture according to the nine-patch rules. It will
- * linearly distribute the strechy parts defined in the nine-patch chunk to
- * the target area.
- *
- *
- * source
- * /--------------^---------------\
- * u0 u1 u2 u3 u4 u5
- * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
- * | div0 div1 div2 div3 |
- * | | / / / /
- * | | / / / /
- * | | / / / /
- * |fffff|ssss|fff|sss|ffff| ---> x
- * x0 x1 x2 x3 x4 x5
- * \----------v------------/
- * target
- *
- * f: fixed segment
- * s: stretchy segment
- *
- *
- * @param div the stretch parts defined in nine-patch chunk
- * @param source the length of the texture
- * @param target the length on the drawing plan
- * @param u output, the positions of these dividers in the texture
- * coordinate
- * @param x output, the corresponding position of these dividers on the
- * drawing plan
- * @return the number of these dividers.
- */
- private static int stretch(
- float x[], float u[], int div[], int source, int target) {
- int textureSize = Utils.nextPowerOf2(source);
- float textureBound = (float) source / textureSize;
-
- float stretch = 0;
- for (int i = 0, n = div.length; i < n; i += 2) {
- stretch += div[i + 1] - div[i];
- }
-
- float remaining = target - source + stretch;
-
- float lastX = 0;
- float lastU = 0;
-
- x[0] = 0;
- u[0] = 0;
- for (int i = 0, n = div.length; i < n; i += 2) {
- // Make the stretchy segment a little smaller to prevent sampling
- // on neighboring fixed segments.
- // fixed segment
- x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
- u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
-
- // stretchy segment
- float partU = div[i + 1] - div[i];
- float partX = remaining * partU / stretch;
- remaining -= partX;
- stretch -= partU;
-
- lastX = x[i + 1] + partX;
- lastU = div[i + 1];
- x[i + 2] = lastX - 0.5f;
- u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
- }
- // the last fixed segment
- x[div.length + 1] = target;
- u[div.length + 1] = textureBound;
-
- // remove segments with length 0.
- int last = 0;
- for (int i = 1, n = div.length + 2; i < n; ++i) {
- if ((x[i] - x[last]) < 1f) continue;
- x[++last] = x[i];
- u[last] = u[i];
- }
- return last + 1;
- }
-
- private void prepareVertexData(float x[], float y[], float u[], float v[],
- int nx, int ny, int[] color) {
- /*
- * Given a 3x3 nine-patch image, the vertex order is defined as the
- * following graph:
- *
- * (0) (1) (2) (3)
- * | /| /| /|
- * | / | / | / |
- * (4) (5) (6) (7)
- * | \ | \ | \ |
- * | \| \| \|
- * (8) (9) (A) (B)
- * | /| /| /|
- * | / | / | / |
- * (C) (D) (E) (F)
- *
- * And we draw the triangle strip in the following index order:
- *
- * index: 04152637B6A5948C9DAEBF
- */
- int pntCount = 0;
- float xy[] = new float[VERTEX_BUFFER_SIZE];
- float uv[] = new float[VERTEX_BUFFER_SIZE];
- for (int j = 0; j < ny; ++j) {
- for (int i = 0; i < nx; ++i) {
- int xIndex = (pntCount++) << 1;
- int yIndex = xIndex + 1;
- xy[xIndex] = x[i];
- xy[yIndex] = y[j];
- uv[xIndex] = u[i];
- uv[yIndex] = v[j];
- }
- }
-
- int idxCount = 1;
- boolean isForward = false;
- byte index[] = new byte[INDEX_BUFFER_SIZE];
- for (int row = 0; row < ny - 1; row++) {
- --idxCount;
- isForward = !isForward;
-
- int start, end, inc;
- if (isForward) {
- start = 0;
- end = nx;
- inc = 1;
- } else {
- start = nx - 1;
- end = -1;
- inc = -1;
- }
-
- for (int col = start; col != end; col += inc) {
- int k = row * nx + col;
- if (col != start) {
- int colorIdx = row * (nx - 1) + col;
- if (isForward) colorIdx--;
- if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
- index[idxCount] = index[idxCount - 1];
- ++idxCount;
- index[idxCount++] = (byte) k;
- }
- }
-
- index[idxCount++] = (byte) k;
- index[idxCount++] = (byte) (k + nx);
- }
- }
-
- mIdxCount = idxCount;
-
- int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
- mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
- mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
- mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
-
- mXyBuffer.put(xy, 0, pntCount * 2).position(0);
- mUvBuffer.put(uv, 0, pntCount * 2).position(0);
- mIndexBuffer.put(index, 0, idxCount).position(0);
- }
-
- private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
- return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
- }
-
- private void prepareBuffers(GLCanvas canvas) {
- mXyBufferName = canvas.uploadBuffer(mXyBuffer);
- mUvBufferName = canvas.uploadBuffer(mUvBuffer);
- mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
-
- // These buffers are never used again.
- mXyBuffer = null;
- mUvBuffer = null;
- mIndexBuffer = null;
- }
-
- public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
- if (mXyBufferName == -1) {
- prepareBuffers(canvas);
- }
- canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
- }
-
- public void recycle(GLCanvas canvas) {
- if (mXyBuffer == null) {
- canvas.deleteBuffer(mXyBufferName);
- canvas.deleteBuffer(mUvBufferName);
- canvas.deleteBuffer(mIndexBufferName);
- mXyBufferName = -1;
- }
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+
+import com.hippo.ehviewer.gallery.common.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+// NinePatchTexture is a texture backed by a NinePatch resource.
+//
+// getPaddings() returns paddings specified in the NinePatch.
+// getNinePatchChunk() returns the layout data specified in the NinePatch.
+//
+public class NinePatchTexture extends ResourceTexture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "NinePatchTexture";
+ private NinePatchChunk mChunk;
+ private SmallCache mInstanceCache
+ = new SmallCache();
+
+ public NinePatchTexture(Context context, int resId) {
+ super(context, resId);
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ if (mBitmap != null) return mBitmap;
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ mContext.getResources(), mResId, options);
+ mBitmap = bitmap;
+ setSize(bitmap.getWidth(), bitmap.getHeight());
+ byte[] chunkData = bitmap.getNinePatchChunk();
+ mChunk = chunkData == null
+ ? null
+ : NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
+ if (mChunk == null) {
+ throw new RuntimeException("invalid nine-patch image: " + mResId);
+ }
+ return bitmap;
+ }
+
+ public Rect getPaddings() {
+ // get the paddings from nine patch
+ if (mChunk == null) onGetBitmap();
+ return mChunk.mPaddings;
+ }
+
+ public NinePatchChunk getNinePatchChunk() {
+ if (mChunk == null) onGetBitmap();
+ return mChunk;
+ }
+
+ // This is a simple cache for a small number of things. Linear search
+ // is used because the cache is small. It also tries to remove less used
+ // item when the cache is full by moving the often-used items to the front.
+ private static class SmallCache {
+ private static final int CACHE_SIZE = 16;
+ private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
+ private int[] mKey = new int[CACHE_SIZE];
+ private V[] mValue = (V[]) new Object[CACHE_SIZE];
+ private int mCount; // number of items in this cache
+
+ // Puts a value into the cache. If the cache is full, also returns
+ // a less used item, otherwise returns null.
+ public V put(int key, V value) {
+ if (mCount == CACHE_SIZE) {
+ V old = mValue[CACHE_SIZE - 1]; // remove the last item
+ mKey[CACHE_SIZE - 1] = key;
+ mValue[CACHE_SIZE - 1] = value;
+ return old;
+ } else {
+ mKey[mCount] = key;
+ mValue[mCount] = value;
+ mCount++;
+ return null;
+ }
+ }
+
+ public V get(int key) {
+ for (int i = 0; i < mCount; i++) {
+ if (mKey[i] == key) {
+ // Move the accessed item one position to the front, so it
+ // will less likely to be removed when cache is full. Only
+ // do this if the cache is starting to get full.
+ if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
+ int tmpKey = mKey[i];
+ mKey[i] = mKey[i - 1];
+ mKey[i - 1] = tmpKey;
+
+ V tmpValue = mValue[i];
+ mValue[i] = mValue[i - 1];
+ mValue[i - 1] = tmpValue;
+ }
+ return mValue[i];
+ }
+ }
+ return null;
+ }
+
+ public void clear() {
+ for (int i = 0; i < mCount; i++) {
+ mValue[i] = null; // make sure it's can be garbage-collected.
+ }
+ mCount = 0;
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public V valueAt(int i) {
+ return mValue[i];
+ }
+ }
+
+ private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
+ int key = w;
+ key = (key << 16) | h;
+ NinePatchInstance instance = mInstanceCache.get(key);
+
+ if (instance == null) {
+ instance = new NinePatchInstance(this, w, h);
+ NinePatchInstance removed = mInstanceCache.put(key, instance);
+ if (removed != null) {
+ removed.recycle(canvas);
+ }
+ }
+
+ return instance;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ if (!isLoaded()) {
+ mInstanceCache.clear();
+ }
+
+ if (w != 0 && h != 0) {
+ findInstance(canvas, w, h).draw(canvas, this, x, y);
+ }
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ GLCanvas canvas = mCanvasRef;
+ if (canvas == null) return;
+ int n = mInstanceCache.size();
+ for (int i = 0; i < n; i++) {
+ NinePatchInstance instance = mInstanceCache.valueAt(i);
+ instance.recycle(canvas);
+ }
+ mInstanceCache.clear();
+ }
+}
+
+// This keeps data for a specialization of NinePatchTexture with the size
+// (width, height). We pre-compute the coordinates for efficiency.
+class NinePatchInstance {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "NinePatchInstance";
+
+ // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
+ private static final int VERTEX_BUFFER_SIZE = 16 * 2;
+
+ // We need 22 indices for a normal nine-patch image, plus 2 for each
+ // transparent region. Current there are at most 1 transparent region.
+ private static final int INDEX_BUFFER_SIZE = 22 + 2;
+
+ private FloatBuffer mXyBuffer;
+ private FloatBuffer mUvBuffer;
+ private ByteBuffer mIndexBuffer;
+
+ // Names for buffer names: xy, uv, index.
+ private int mXyBufferName = -1;
+ private int mUvBufferName;
+ private int mIndexBufferName;
+
+ private int mIdxCount;
+
+ public NinePatchInstance(NinePatchTexture tex, int width, int height) {
+ NinePatchChunk chunk = tex.getNinePatchChunk();
+
+ if (width <= 0 || height <= 0) {
+ throw new RuntimeException("invalid dimension");
+ }
+
+ // The code should be easily extended to handle the general cases by
+ // allocating more space for buffers. But let's just handle the only
+ // use case.
+ if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
+ throw new RuntimeException("unsupported nine patch");
+ }
+
+ float divX[] = new float[4];
+ float divY[] = new float[4];
+ float divU[] = new float[4];
+ float divV[] = new float[4];
+
+ int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
+ int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
+
+ prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
+ }
+
+ /**
+ * Stretches the texture according to the nine-patch rules. It will
+ * linearly distribute the strechy parts defined in the nine-patch chunk to
+ * the target area.
+ *
+ *
+ * source
+ * /--------------^---------------\
+ * u0 u1 u2 u3 u4 u5
+ * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
+ * | div0 div1 div2 div3 |
+ * | | / / / /
+ * | | / / / /
+ * | | / / / /
+ * |fffff|ssss|fff|sss|ffff| ---> x
+ * x0 x1 x2 x3 x4 x5
+ * \----------v------------/
+ * target
+ *
+ * f: fixed segment
+ * s: stretchy segment
+ *
+ *
+ * @param div the stretch parts defined in nine-patch chunk
+ * @param source the length of the texture
+ * @param target the length on the drawing plan
+ * @param u output, the positions of these dividers in the texture
+ * coordinate
+ * @param x output, the corresponding position of these dividers on the
+ * drawing plan
+ * @return the number of these dividers.
+ */
+ private static int stretch(
+ float x[], float u[], int div[], int source, int target) {
+ int textureSize = Utils.nextPowerOf2(source);
+ float textureBound = (float) source / textureSize;
+
+ float stretch = 0;
+ for (int i = 0, n = div.length; i < n; i += 2) {
+ stretch += div[i + 1] - div[i];
+ }
+
+ float remaining = target - source + stretch;
+
+ float lastX = 0;
+ float lastU = 0;
+
+ x[0] = 0;
+ u[0] = 0;
+ for (int i = 0, n = div.length; i < n; i += 2) {
+ // Make the stretchy segment a little smaller to prevent sampling
+ // on neighboring fixed segments.
+ // fixed segment
+ x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
+ u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
+
+ // stretchy segment
+ float partU = div[i + 1] - div[i];
+ float partX = remaining * partU / stretch;
+ remaining -= partX;
+ stretch -= partU;
+
+ lastX = x[i + 1] + partX;
+ lastU = div[i + 1];
+ x[i + 2] = lastX - 0.5f;
+ u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
+ }
+ // the last fixed segment
+ x[div.length + 1] = target;
+ u[div.length + 1] = textureBound;
+
+ // remove segments with length 0.
+ int last = 0;
+ for (int i = 1, n = div.length + 2; i < n; ++i) {
+ if ((x[i] - x[last]) < 1f) continue;
+ x[++last] = x[i];
+ u[last] = u[i];
+ }
+ return last + 1;
+ }
+
+ private void prepareVertexData(float x[], float y[], float u[], float v[],
+ int nx, int ny, int[] color) {
+ /*
+ * Given a 3x3 nine-patch image, the vertex order is defined as the
+ * following graph:
+ *
+ * (0) (1) (2) (3)
+ * | /| /| /|
+ * | / | / | / |
+ * (4) (5) (6) (7)
+ * | \ | \ | \ |
+ * | \| \| \|
+ * (8) (9) (A) (B)
+ * | /| /| /|
+ * | / | / | / |
+ * (C) (D) (E) (F)
+ *
+ * And we draw the triangle strip in the following index order:
+ *
+ * index: 04152637B6A5948C9DAEBF
+ */
+ int pntCount = 0;
+ float xy[] = new float[VERTEX_BUFFER_SIZE];
+ float uv[] = new float[VERTEX_BUFFER_SIZE];
+ for (int j = 0; j < ny; ++j) {
+ for (int i = 0; i < nx; ++i) {
+ int xIndex = (pntCount++) << 1;
+ int yIndex = xIndex + 1;
+ xy[xIndex] = x[i];
+ xy[yIndex] = y[j];
+ uv[xIndex] = u[i];
+ uv[yIndex] = v[j];
+ }
+ }
+
+ int idxCount = 1;
+ boolean isForward = false;
+ byte index[] = new byte[INDEX_BUFFER_SIZE];
+ for (int row = 0; row < ny - 1; row++) {
+ --idxCount;
+ isForward = !isForward;
+
+ int start, end, inc;
+ if (isForward) {
+ start = 0;
+ end = nx;
+ inc = 1;
+ } else {
+ start = nx - 1;
+ end = -1;
+ inc = -1;
+ }
+
+ for (int col = start; col != end; col += inc) {
+ int k = row * nx + col;
+ if (col != start) {
+ int colorIdx = row * (nx - 1) + col;
+ if (isForward) colorIdx--;
+ if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
+ index[idxCount] = index[idxCount - 1];
+ ++idxCount;
+ index[idxCount++] = (byte) k;
+ }
+ }
+
+ index[idxCount++] = (byte) k;
+ index[idxCount++] = (byte) (k + nx);
+ }
+ }
+
+ mIdxCount = idxCount;
+
+ int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
+ mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+ mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+ mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
+
+ mXyBuffer.put(xy, 0, pntCount * 2).position(0);
+ mUvBuffer.put(uv, 0, pntCount * 2).position(0);
+ mIndexBuffer.put(index, 0, idxCount).position(0);
+ }
+
+ private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+ return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ }
+
+ private void prepareBuffers(GLCanvas canvas) {
+ mXyBufferName = canvas.uploadBuffer(mXyBuffer);
+ mUvBufferName = canvas.uploadBuffer(mUvBuffer);
+ mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
+
+ // These buffers are never used again.
+ mXyBuffer = null;
+ mUvBuffer = null;
+ mIndexBuffer = null;
+ }
+
+ public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
+ if (mXyBufferName == -1) {
+ prepareBuffers(canvas);
+ }
+ canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
+ }
+
+ public void recycle(GLCanvas canvas) {
+ if (mXyBuffer == null) {
+ canvas.deleteBuffer(mXyBufferName);
+ canvas.deleteBuffer(mUvBufferName);
+ canvas.deleteBuffer(mIndexBufferName);
+ mXyBufferName = -1;
+ }
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java
index ae6df2761..db71a4e2b 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java
@@ -1,53 +1,53 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import junit.framework.Assert;
-
-// ResourceTexture is a texture whose Bitmap is decoded from a resource.
-// By default ResourceTexture is not opaque.
-public class ResourceTexture extends UploadedTexture {
-
- protected final Context mContext;
- protected final int mResId;
-
- public ResourceTexture(Context context, int resId) {
- Assert.assertNotNull(context);
- mContext = context;
- mResId = resId;
- setOpaque(false);
- }
-
- @Override
- protected Bitmap onGetBitmap() {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- return BitmapFactory.decodeResource(
- mContext.getResources(), mResId, options);
- }
-
- @Override
- protected void onFreeBitmap(Bitmap bitmap) {
- if (!inFinalizer()) {
- bitmap.recycle();
- }
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import junit.framework.Assert;
+
+// ResourceTexture is a texture whose Bitmap is decoded from a resource.
+// By default ResourceTexture is not opaque.
+public class ResourceTexture extends UploadedTexture {
+
+ protected final Context mContext;
+ protected final int mResId;
+
+ public ResourceTexture(Context context, int resId) {
+ Assert.assertNotNull(context);
+ mContext = context;
+ mResId = resId;
+ setOpaque(false);
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(
+ mContext.getResources(), mResId, options);
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ if (!inFinalizer()) {
+ bitmap.recycle();
+ }
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java
index 263f982b0..7bcdd17b5 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java
@@ -1,88 +1,88 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.Typeface;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.FloatMath;
-
-// StringTexture is a texture shows the content of a specified String.
-//
-// To create a StringTexture, use the newInstance() method and specify
-// the String, the font size, and the color.
-public class StringTexture extends CanvasTexture {
- private final String mText;
- private final TextPaint mPaint;
- private final FontMetricsInt mMetrics;
-
- private StringTexture(String text, TextPaint paint,
- FontMetricsInt metrics, int width, int height) {
- super(width, height);
- mText = text;
- mPaint = paint;
- mMetrics = metrics;
- }
-
- public static TextPaint getDefaultPaint(float textSize, int color) {
- TextPaint paint = new TextPaint();
- paint.setTextSize(textSize);
- paint.setAntiAlias(true);
- paint.setColor(color);
- paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
- return paint;
- }
-
- public static StringTexture newInstance(
- String text, float textSize, int color) {
- return newInstance(text, getDefaultPaint(textSize, color));
- }
-
- public static StringTexture newInstance(
- String text, float textSize, int color,
- float lengthLimit, boolean isBold) {
- TextPaint paint = getDefaultPaint(textSize, color);
- if (isBold) {
- paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
- }
- if (lengthLimit > 0) {
- text = TextUtils.ellipsize(
- text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
- }
- return newInstance(text, paint);
- }
-
- private static StringTexture newInstance(String text, TextPaint paint) {
- FontMetricsInt metrics = paint.getFontMetricsInt();
- int width = (int) FloatMath.ceil(paint.measureText(text));
- int height = metrics.bottom - metrics.top;
- // The texture size needs to be at least 1x1.
- if (width <= 0) width = 1;
- if (height <= 0) height = 1;
- return new StringTexture(text, paint, metrics, width, height);
- }
-
- @Override
- protected void onDraw(Canvas canvas, Bitmap backing) {
- canvas.translate(0, -mMetrics.ascent);
- canvas.drawText(mText, 0, 0, mPaint);
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.FloatMath;
+
+// StringTexture is a texture shows the content of a specified String.
+//
+// To create a StringTexture, use the newInstance() method and specify
+// the String, the font size, and the color.
+public class StringTexture extends CanvasTexture {
+ private final String mText;
+ private final TextPaint mPaint;
+ private final FontMetricsInt mMetrics;
+
+ private StringTexture(String text, TextPaint paint,
+ FontMetricsInt metrics, int width, int height) {
+ super(width, height);
+ mText = text;
+ mPaint = paint;
+ mMetrics = metrics;
+ }
+
+ public static TextPaint getDefaultPaint(float textSize, int color) {
+ TextPaint paint = new TextPaint();
+ paint.setTextSize(textSize);
+ paint.setAntiAlias(true);
+ paint.setColor(color);
+ paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
+ return paint;
+ }
+
+ public static StringTexture newInstance(
+ String text, float textSize, int color) {
+ return newInstance(text, getDefaultPaint(textSize, color));
+ }
+
+ public static StringTexture newInstance(
+ String text, float textSize, int color,
+ float lengthLimit, boolean isBold) {
+ TextPaint paint = getDefaultPaint(textSize, color);
+ if (isBold) {
+ paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+ }
+ if (lengthLimit > 0) {
+ text = TextUtils.ellipsize(
+ text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
+ }
+ return newInstance(text, paint);
+ }
+
+ private static StringTexture newInstance(String text, TextPaint paint) {
+ FontMetricsInt metrics = paint.getFontMetricsInt();
+ int width = (int) FloatMath.ceil(paint.measureText(text));
+ int height = metrics.bottom - metrics.top;
+ // The texture size needs to be at least 1x1.
+ if (width <= 0) width = 1;
+ if (height <= 0) height = 1;
+ return new StringTexture(text, paint, metrics, width, height);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas, Bitmap backing) {
+ canvas.translate(0, -mMetrics.ascent);
+ canvas.drawText(mText, 0, 0, mPaint);
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/Texture.java b/src/com/hippo/ehviewer/gallery/glrenderer/Texture.java
index 12982cf55..2531a1e7c 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/Texture.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/Texture.java
@@ -1,44 +1,44 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
-
-
-// Texture is a rectangular image which can be drawn on GLCanvas.
-// The isOpaque() function gives a hint about whether the texture is opaque,
-// so the drawing can be done faster.
-//
-// This is the current texture hierarchy:
-//
-// Texture
-// -- ColorTexture
-// -- FadeInTexture
-// -- BasicTexture
-// -- UploadedTexture
-// -- BitmapTexture
-// -- Tile
-// -- ResourceTexture
-// -- NinePatchTexture
-// -- CanvasTexture
-// -- StringTexture
-//
-public interface Texture {
- public int getWidth();
- public int getHeight();
- public void draw(GLCanvas canvas, int x, int y);
- public void draw(GLCanvas canvas, int x, int y, int w, int h);
- public boolean isOpaque();
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.glrenderer;
+
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the texture is opaque,
+// so the drawing can be done faster.
+//
+// This is the current texture hierarchy:
+//
+// Texture
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+// -- UploadedTexture
+// -- BitmapTexture
+// -- Tile
+// -- ResourceTexture
+// -- NinePatchTexture
+// -- CanvasTexture
+// -- StringTexture
+//
+public interface Texture {
+ public int getWidth();
+ public int getHeight();
+ public void draw(GLCanvas canvas, int x, int y);
+ public void draw(GLCanvas canvas, int x, int y, int w, int h);
+ public boolean isOpaque();
+}
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java b/src/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java
index fea10db37..fab40fad7 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java
@@ -1,105 +1,105 @@
-/*
- * Copyright (C) 2012 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.hippo.ehviewer.gallery.glrenderer;
-
-import com.hippo.ehviewer.gallery.ui.GLRoot;
-import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
-
-import java.util.ArrayDeque;
-
-public class TextureUploader implements OnGLIdleListener {
- private static final int INIT_CAPACITY = 64;
- private static final int QUOTA_PER_FRAME = 1;
-
- private final ArrayDeque mFgTextures =
- new ArrayDeque(INIT_CAPACITY);
- private final ArrayDeque mBgTextures =
- new ArrayDeque(INIT_CAPACITY);
- private final GLRoot mGLRoot;
- private volatile boolean mIsQueued = false;
-
- public TextureUploader(GLRoot root) {
- mGLRoot = root;
- }
-
- public synchronized void clear() {
- while (!mFgTextures.isEmpty()) {
- mFgTextures.pop().setIsUploading(false);
- }
- while (!mBgTextures.isEmpty()) {
- mBgTextures.pop().setIsUploading(false);
- }
- }
-
- // caller should hold synchronized on "this"
- private void queueSelfIfNeed() {
- if (mIsQueued) return;
- mIsQueued = true;
- mGLRoot.addOnGLIdleListener(this);
- }
-
- public synchronized void addBgTexture(UploadedTexture t) {
- if (t.isContentValid()) return;
- mBgTextures.addLast(t);
- t.setIsUploading(true);
- queueSelfIfNeed();
- }
-
- public synchronized void addFgTexture(UploadedTexture t) {
- if (t.isContentValid()) return;
- mFgTextures.addLast(t);
- t.setIsUploading(true);
- queueSelfIfNeed();
- }
-
- private int upload(GLCanvas canvas, ArrayDeque deque,
- int uploadQuota, boolean isBackground) {
- while (uploadQuota > 0) {
- UploadedTexture t;
- synchronized (this) {
- if (deque.isEmpty()) break;
- t = deque.removeFirst();
- t.setIsUploading(false);
- if (t.isContentValid()) continue;
-
- // this has to be protected by the synchronized block
- // to prevent the inner bitmap get recycled
- t.updateContent(canvas);
- }
-
- // It will took some more time for a texture to be drawn for
- // the first time.
- // Thus, when scrolling, if a new column appears on screen,
- // it may cause a UI jank even these textures are uploaded.
- if (isBackground) t.draw(canvas, 0, 0);
- --uploadQuota;
- }
- return uploadQuota;
- }
-
- @Override
- public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
- int uploadQuota = QUOTA_PER_FRAME;
- uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
- if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
- upload(canvas, mBgTextures, uploadQuota, true);
- synchronized (this) {
- mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
- return mIsQueued;
- }
- }
-}
+/*
+ * Copyright (C) 2012 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.hippo.ehviewer.gallery.glrenderer;
+
+import com.hippo.ehviewer.gallery.ui.GLRoot;
+import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+ private static final int INIT_CAPACITY = 64;
+ private static final int QUOTA_PER_FRAME = 1;
+
+ private final ArrayDeque mFgTextures =
+ new ArrayDeque(INIT_CAPACITY);
+ private final ArrayDeque mBgTextures =
+ new ArrayDeque(INIT_CAPACITY);
+ private final GLRoot mGLRoot;
+ private volatile boolean mIsQueued = false;
+
+ public TextureUploader(GLRoot root) {
+ mGLRoot = root;
+ }
+
+ public synchronized void clear() {
+ while (!mFgTextures.isEmpty()) {
+ mFgTextures.pop().setIsUploading(false);
+ }
+ while (!mBgTextures.isEmpty()) {
+ mBgTextures.pop().setIsUploading(false);
+ }
+ }
+
+ // caller should hold synchronized on "this"
+ private void queueSelfIfNeed() {
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGLRoot.addOnGLIdleListener(this);
+ }
+
+ public synchronized void addBgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mBgTextures.addLast(t);
+ t.setIsUploading(true);
+ queueSelfIfNeed();
+ }
+
+ public synchronized void addFgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mFgTextures.addLast(t);
+ t.setIsUploading(true);
+ queueSelfIfNeed();
+ }
+
+ private int upload(GLCanvas canvas, ArrayDeque deque,
+ int uploadQuota, boolean isBackground) {
+ while (uploadQuota > 0) {
+ UploadedTexture t;
+ synchronized (this) {
+ if (deque.isEmpty()) break;
+ t = deque.removeFirst();
+ t.setIsUploading(false);
+ if (t.isContentValid()) continue;
+
+ // this has to be protected by the synchronized block
+ // to prevent the inner bitmap get recycled
+ t.updateContent(canvas);
+ }
+
+ // It will took some more time for a texture to be drawn for
+ // the first time.
+ // Thus, when scrolling, if a new column appears on screen,
+ // it may cause a UI jank even these textures are uploaded.
+ if (isBackground) t.draw(canvas, 0, 0);
+ --uploadQuota;
+ }
+ return uploadQuota;
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ int uploadQuota = QUOTA_PER_FRAME;
+ uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+ if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+ upload(canvas, mBgTextures, uploadQuota, true);
+ synchronized (this) {
+ mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+ return mIsQueued;
+ }
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java
index b551128f9..1599239f7 100644
--- a/src/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java
+++ b/src/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java
@@ -1,349 +1,349 @@
-/*
- * Copyright (C) 2012 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.hippo.ehviewer.gallery.glrenderer;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.RectF;
-import android.os.SystemClock;
-
-import com.hippo.ehviewer.gallery.ui.GLRoot;
-import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-// This class is similar to BitmapTexture, except the bitmap is
-// split into tiles. By doing so, we may increase the time required to
-// upload the whole bitmap but we reduce the time of uploading each tile
-// so it make the animation more smooth and prevents jank.
-public class TiledTexture implements Texture {
- private static final int CONTENT_SIZE = 254;
- private static final int BORDER_SIZE = 1;
- private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
- private static final int INIT_CAPACITY = 8;
-
- // We are targeting at 60fps, so we have 16ms for each frame.
- // In this 16ms, we use about 4~8 ms to upload tiles.
- private static final long UPLOAD_TILE_LIMIT = 4; // ms
-
- private static Tile sFreeTileHead = null;
- private static final Object sFreeTileLock = new Object();
-
- private static Bitmap sUploadBitmap;
- private static Canvas sCanvas;
- private static Paint sBitmapPaint;
- private static Paint sPaint;
-
- private int mUploadIndex = 0;
-
- private final Tile[] mTiles; // Can be modified in different threads.
- // Should be protected by "synchronized."
- private final int mWidth;
- private final int mHeight;
- private final RectF mSrcRect = new RectF();
- private final RectF mDestRect = new RectF();
-
- public static class Uploader implements OnGLIdleListener {
- private final ArrayDeque mTextures =
- new ArrayDeque(INIT_CAPACITY);
-
- private final GLRoot mGlRoot;
- private boolean mIsQueued = false;
-
- public Uploader(GLRoot glRoot) {
- mGlRoot = glRoot;
- }
-
- public synchronized void clear() {
- mTextures.clear();
- }
-
- public synchronized void addTexture(TiledTexture t) {
- if (t.isReady()) return;
- mTextures.addLast(t);
-
- if (mIsQueued) return;
- mIsQueued = true;
- mGlRoot.addOnGLIdleListener(this);
- }
-
- @Override
- public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
- ArrayDeque deque = mTextures;
- synchronized (this) {
- long now = SystemClock.uptimeMillis();
- long dueTime = now + UPLOAD_TILE_LIMIT;
- while (now < dueTime && !deque.isEmpty()) {
- TiledTexture t = deque.peekFirst();
- if (t.uploadNextTile(canvas)) {
- deque.removeFirst();
- mGlRoot.requestRender();
- }
- now = SystemClock.uptimeMillis();
- }
- mIsQueued = !mTextures.isEmpty();
-
- // return true to keep this listener in the queue
- return mIsQueued;
- }
- }
- }
-
- private static class Tile extends UploadedTexture {
- public int offsetX;
- public int offsetY;
- public Bitmap bitmap;
- public Tile nextFreeTile;
- public int contentWidth;
- public int contentHeight;
-
- @Override
- public void setSize(int width, int height) {
- contentWidth = width;
- contentHeight = height;
- mWidth = width + 2 * BORDER_SIZE;
- mHeight = height + 2 * BORDER_SIZE;
- mTextureWidth = TILE_SIZE;
- mTextureHeight = TILE_SIZE;
- }
-
- @Override
- protected Bitmap onGetBitmap() {
- int x = BORDER_SIZE - offsetX;
- int y = BORDER_SIZE - offsetY;
- int r = bitmap.getWidth() + x;
- int b = bitmap.getHeight() + y;
- sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
- bitmap = null;
-
- // draw borders if need
- if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
- if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
- if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
- if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
-
- return sUploadBitmap;
- }
-
- @Override
- protected void onFreeBitmap(Bitmap bitmap) {
- // do nothing
- }
- }
-
- private static void freeTile(Tile tile) {
- tile.invalidateContent();
- tile.bitmap = null;
- synchronized (sFreeTileLock) {
- tile.nextFreeTile = sFreeTileHead;
- sFreeTileHead = tile;
- }
- }
-
- private static Tile obtainTile() {
- synchronized (sFreeTileLock) {
- Tile result = sFreeTileHead;
- if (result == null) return new Tile();
- sFreeTileHead = result.nextFreeTile;
- result.nextFreeTile = null;
- return result;
- }
- }
-
- private boolean uploadNextTile(GLCanvas canvas) {
- if (mUploadIndex == mTiles.length) return true;
-
- synchronized (mTiles) {
- Tile next = mTiles[mUploadIndex++];
-
- // Make sure tile has not already been recycled by the time
- // this is called (race condition in onGLIdle)
- if (next.bitmap != null) {
- boolean hasBeenLoad = next.isLoaded();
- next.updateContent(canvas);
-
- // It will take some time for a texture to be drawn for the first
- // time. When scrolling, we need to draw several tiles on the screen
- // at the same time. It may cause a UI jank even these textures has
- // been uploaded.
- if (!hasBeenLoad) next.draw(canvas, 0, 0);
- }
- }
- return mUploadIndex == mTiles.length;
- }
-
- public TiledTexture(Bitmap bitmap) {
- mWidth = bitmap.getWidth();
- mHeight = bitmap.getHeight();
- ArrayList list = new ArrayList();
-
- for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
- for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
- Tile tile = obtainTile();
- tile.offsetX = x;
- tile.offsetY = y;
- tile.bitmap = bitmap;
- tile.setSize(
- Math.min(CONTENT_SIZE, mWidth - x),
- Math.min(CONTENT_SIZE, mHeight - y));
- list.add(tile);
- }
- }
- mTiles = list.toArray(new Tile[list.size()]);
- }
-
- public boolean isReady() {
- return mUploadIndex == mTiles.length;
- }
-
- // Can be called in UI thread.
- public void recycle() {
- synchronized (mTiles) {
- for (int i = 0, n = mTiles.length; i < n; ++i) {
- freeTile(mTiles[i]);
- }
- }
- }
-
- public static void freeResources() {
- sUploadBitmap = null;
- sCanvas = null;
- sBitmapPaint = null;
- sPaint = null;
- }
-
- public static void prepareResources() {
- sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
- sCanvas = new Canvas(sUploadBitmap);
- sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
- sPaint = new Paint();
- sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
- sPaint.setColor(Color.TRANSPARENT);
- }
-
- // We want to draw the "source" on the "target".
- // This method is to find the "output" rectangle which is
- // the corresponding area of the "src".
- // (x,y) target
- // (x0,y0) source +---------------+
- // +----------+ | |
- // | src | | output |
- // | +--+ | linear map | +----+ |
- // | +--+ | ----------> | | | |
- // | | by (scaleX, scaleY) | +----+ |
- // +----------+ | |
- // Texture +---------------+
- // Canvas
- private static void mapRect(RectF output,
- RectF src, float x0, float y0, float x, float y, float scaleX,
- float scaleY) {
- output.set(x + (src.left - x0) * scaleX,
- y + (src.top - y0) * scaleY,
- x + (src.right - x0) * scaleX,
- y + (src.bottom - y0) * scaleY);
- }
-
- // Draws a mixed color of this texture and a specified color onto the
- // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
- public void drawMixed(GLCanvas canvas, int color, float ratio,
- int x, int y, int width, int height) {
- RectF src = mSrcRect;
- RectF dest = mDestRect;
- float scaleX = (float) width / mWidth;
- float scaleY = (float) height / mHeight;
- synchronized (mTiles) {
- for (int i = 0, n = mTiles.length; i < n; ++i) {
- Tile t = mTiles[i];
- src.set(0, 0, t.contentWidth, t.contentHeight);
- src.offset(t.offsetX, t.offsetY);
- mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
- src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
- canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
- }
- }
- }
-
- // Draws the texture on to the specified rectangle.
- @Override
- public void draw(GLCanvas canvas, int x, int y, int width, int height) {
- RectF src = mSrcRect;
- RectF dest = mDestRect;
- float scaleX = (float) width / mWidth;
- float scaleY = (float) height / mHeight;
- synchronized (mTiles) {
- for (int i = 0, n = mTiles.length; i < n; ++i) {
- Tile t = mTiles[i];
- src.set(0, 0, t.contentWidth, t.contentHeight);
- src.offset(t.offsetX, t.offsetY);
- mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
- src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
- canvas.drawTexture(t, mSrcRect, mDestRect);
- }
- }
- }
-
- // Draws a sub region of this texture on to the specified rectangle.
- public void draw(GLCanvas canvas, RectF source, RectF target) {
- RectF src = mSrcRect;
- RectF dest = mDestRect;
- float x0 = source.left;
- float y0 = source.top;
- float x = target.left;
- float y = target.top;
- float scaleX = target.width() / source.width();
- float scaleY = target.height() / source.height();
-
- synchronized (mTiles) {
- for (int i = 0, n = mTiles.length; i < n; ++i) {
- Tile t = mTiles[i];
- src.set(0, 0, t.contentWidth, t.contentHeight);
- src.offset(t.offsetX, t.offsetY);
- if (!src.intersect(source)) continue;
- mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
- src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
- canvas.drawTexture(t, src, dest);
- }
- }
- }
-
- @Override
- public int getWidth() {
- return mWidth;
- }
-
- @Override
- public int getHeight() {
- return mHeight;
- }
-
- @Override
- public void draw(GLCanvas canvas, int x, int y) {
- draw(canvas, x, y, mWidth, mHeight);
- }
-
- @Override
- public boolean isOpaque() {
- return false;
- }
-}
+/*
+ * Copyright (C) 2012 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.hippo.ehviewer.gallery.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.os.SystemClock;
+
+import com.hippo.ehviewer.gallery.ui.GLRoot;
+import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+// This class is similar to BitmapTexture, except the bitmap is
+// split into tiles. By doing so, we may increase the time required to
+// upload the whole bitmap but we reduce the time of uploading each tile
+// so it make the animation more smooth and prevents jank.
+public class TiledTexture implements Texture {
+ private static final int CONTENT_SIZE = 254;
+ private static final int BORDER_SIZE = 1;
+ private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
+ private static final int INIT_CAPACITY = 8;
+
+ // We are targeting at 60fps, so we have 16ms for each frame.
+ // In this 16ms, we use about 4~8 ms to upload tiles.
+ private static final long UPLOAD_TILE_LIMIT = 4; // ms
+
+ private static Tile sFreeTileHead = null;
+ private static final Object sFreeTileLock = new Object();
+
+ private static Bitmap sUploadBitmap;
+ private static Canvas sCanvas;
+ private static Paint sBitmapPaint;
+ private static Paint sPaint;
+
+ private int mUploadIndex = 0;
+
+ private final Tile[] mTiles; // Can be modified in different threads.
+ // Should be protected by "synchronized."
+ private final int mWidth;
+ private final int mHeight;
+ private final RectF mSrcRect = new RectF();
+ private final RectF mDestRect = new RectF();
+
+ public static class Uploader implements OnGLIdleListener {
+ private final ArrayDeque mTextures =
+ new ArrayDeque(INIT_CAPACITY);
+
+ private final GLRoot mGlRoot;
+ private boolean mIsQueued = false;
+
+ public Uploader(GLRoot glRoot) {
+ mGlRoot = glRoot;
+ }
+
+ public synchronized void clear() {
+ mTextures.clear();
+ }
+
+ public synchronized void addTexture(TiledTexture t) {
+ if (t.isReady()) return;
+ mTextures.addLast(t);
+
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGlRoot.addOnGLIdleListener(this);
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ ArrayDeque deque = mTextures;
+ synchronized (this) {
+ long now = SystemClock.uptimeMillis();
+ long dueTime = now + UPLOAD_TILE_LIMIT;
+ while (now < dueTime && !deque.isEmpty()) {
+ TiledTexture t = deque.peekFirst();
+ if (t.uploadNextTile(canvas)) {
+ deque.removeFirst();
+ mGlRoot.requestRender();
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mIsQueued = !mTextures.isEmpty();
+
+ // return true to keep this listener in the queue
+ return mIsQueued;
+ }
+ }
+ }
+
+ private static class Tile extends UploadedTexture {
+ public int offsetX;
+ public int offsetY;
+ public Bitmap bitmap;
+ public Tile nextFreeTile;
+ public int contentWidth;
+ public int contentHeight;
+
+ @Override
+ public void setSize(int width, int height) {
+ contentWidth = width;
+ contentHeight = height;
+ mWidth = width + 2 * BORDER_SIZE;
+ mHeight = height + 2 * BORDER_SIZE;
+ mTextureWidth = TILE_SIZE;
+ mTextureHeight = TILE_SIZE;
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ int x = BORDER_SIZE - offsetX;
+ int y = BORDER_SIZE - offsetY;
+ int r = bitmap.getWidth() + x;
+ int b = bitmap.getHeight() + y;
+ sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
+ bitmap = null;
+
+ // draw borders if need
+ if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
+ if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
+ if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
+ if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
+
+ return sUploadBitmap;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ // do nothing
+ }
+ }
+
+ private static void freeTile(Tile tile) {
+ tile.invalidateContent();
+ tile.bitmap = null;
+ synchronized (sFreeTileLock) {
+ tile.nextFreeTile = sFreeTileHead;
+ sFreeTileHead = tile;
+ }
+ }
+
+ private static Tile obtainTile() {
+ synchronized (sFreeTileLock) {
+ Tile result = sFreeTileHead;
+ if (result == null) return new Tile();
+ sFreeTileHead = result.nextFreeTile;
+ result.nextFreeTile = null;
+ return result;
+ }
+ }
+
+ private boolean uploadNextTile(GLCanvas canvas) {
+ if (mUploadIndex == mTiles.length) return true;
+
+ synchronized (mTiles) {
+ Tile next = mTiles[mUploadIndex++];
+
+ // Make sure tile has not already been recycled by the time
+ // this is called (race condition in onGLIdle)
+ if (next.bitmap != null) {
+ boolean hasBeenLoad = next.isLoaded();
+ next.updateContent(canvas);
+
+ // It will take some time for a texture to be drawn for the first
+ // time. When scrolling, we need to draw several tiles on the screen
+ // at the same time. It may cause a UI jank even these textures has
+ // been uploaded.
+ if (!hasBeenLoad) next.draw(canvas, 0, 0);
+ }
+ }
+ return mUploadIndex == mTiles.length;
+ }
+
+ public TiledTexture(Bitmap bitmap) {
+ mWidth = bitmap.getWidth();
+ mHeight = bitmap.getHeight();
+ ArrayList list = new ArrayList();
+
+ for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
+ for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
+ Tile tile = obtainTile();
+ tile.offsetX = x;
+ tile.offsetY = y;
+ tile.bitmap = bitmap;
+ tile.setSize(
+ Math.min(CONTENT_SIZE, mWidth - x),
+ Math.min(CONTENT_SIZE, mHeight - y));
+ list.add(tile);
+ }
+ }
+ mTiles = list.toArray(new Tile[list.size()]);
+ }
+
+ public boolean isReady() {
+ return mUploadIndex == mTiles.length;
+ }
+
+ // Can be called in UI thread.
+ public void recycle() {
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ freeTile(mTiles[i]);
+ }
+ }
+ }
+
+ public static void freeResources() {
+ sUploadBitmap = null;
+ sCanvas = null;
+ sBitmapPaint = null;
+ sPaint = null;
+ }
+
+ public static void prepareResources() {
+ sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
+ sCanvas = new Canvas(sUploadBitmap);
+ sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+ sPaint = new Paint();
+ sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+ sPaint.setColor(Color.TRANSPARENT);
+ }
+
+ // We want to draw the "source" on the "target".
+ // This method is to find the "output" rectangle which is
+ // the corresponding area of the "src".
+ // (x,y) target
+ // (x0,y0) source +---------------+
+ // +----------+ | |
+ // | src | | output |
+ // | +--+ | linear map | +----+ |
+ // | +--+ | ----------> | | | |
+ // | | by (scaleX, scaleY) | +----+ |
+ // +----------+ | |
+ // Texture +---------------+
+ // Canvas
+ private static void mapRect(RectF output,
+ RectF src, float x0, float y0, float x, float y, float scaleX,
+ float scaleY) {
+ output.set(x + (src.left - x0) * scaleX,
+ y + (src.top - y0) * scaleY,
+ x + (src.right - x0) * scaleX,
+ y + (src.bottom - y0) * scaleY);
+ }
+
+ // Draws a mixed color of this texture and a specified color onto the
+ // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
+ public void drawMixed(GLCanvas canvas, int color, float ratio,
+ int x, int y, int width, int height) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float scaleX = (float) width / mWidth;
+ float scaleY = (float) height / mHeight;
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
+ }
+ }
+ }
+
+ // Draws the texture on to the specified rectangle.
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float scaleX = (float) width / mWidth;
+ float scaleY = (float) height / mHeight;
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawTexture(t, mSrcRect, mDestRect);
+ }
+ }
+ }
+
+ // Draws a sub region of this texture on to the specified rectangle.
+ public void draw(GLCanvas canvas, RectF source, RectF target) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float x0 = source.left;
+ float y0 = source.top;
+ float x = target.left;
+ float y = target.top;
+ float scaleX = target.width() / source.width();
+ float scaleY = target.height() / source.height();
+
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ if (!src.intersect(source)) continue;
+ mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawTexture(t, src, dest);
+ }
+ }
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/ui/AnimationTime.java b/src/com/hippo/ehviewer/gallery/ui/AnimationTime.java
index 921787eac..19bb91ca2 100644
--- a/src/com/hippo/ehviewer/gallery/ui/AnimationTime.java
+++ b/src/com/hippo/ehviewer/gallery/ui/AnimationTime.java
@@ -1,45 +1,45 @@
-
-/*
- * Copyright (C) 2012 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.hippo.ehviewer.gallery.ui;
-
-import android.os.SystemClock;
-
-//
-// The animation time should ideally be the vsync time the frame will be
-// displayed, but that is an unknown time in the future. So we use the system
-// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
-// as a approximation.
-//
-public class AnimationTime {
- private static volatile long sTime;
-
- // Sets current time as the animation time.
- public static void update() {
- sTime = SystemClock.uptimeMillis();
- }
-
- // Returns the animation time.
- public static long get() {
- return sTime;
- }
-
- public static long startTime() {
- sTime = SystemClock.uptimeMillis();
- return sTime;
- }
-}
+
+/*
+ * Copyright (C) 2012 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.hippo.ehviewer.gallery.ui;
+
+import android.os.SystemClock;
+
+//
+// The animation time should ideally be the vsync time the frame will be
+// displayed, but that is an unknown time in the future. So we use the system
+// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
+// as a approximation.
+//
+public class AnimationTime {
+ private static volatile long sTime;
+
+ // Sets current time as the animation time.
+ public static void update() {
+ sTime = SystemClock.uptimeMillis();
+ }
+
+ // Returns the animation time.
+ public static long get() {
+ return sTime;
+ }
+
+ public static long startTime() {
+ sTime = SystemClock.uptimeMillis();
+ return sTime;
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/ui/DownUpDetector.java b/src/com/hippo/ehviewer/gallery/ui/DownUpDetector.java
index 05f496637..1b0a2deb0 100644
--- a/src/com/hippo/ehviewer/gallery/ui/DownUpDetector.java
+++ b/src/com/hippo/ehviewer/gallery/ui/DownUpDetector.java
@@ -1,61 +1,61 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
-
-import android.view.MotionEvent;
-
-public class DownUpDetector {
- public interface DownUpListener {
- void onDown(MotionEvent e);
- void onUp(MotionEvent e);
- }
-
- private boolean mStillDown;
- private DownUpListener mListener;
-
- public DownUpDetector(DownUpListener listener) {
- mListener = listener;
- }
-
- private void setState(boolean down, MotionEvent e) {
- if (down == mStillDown) return;
- mStillDown = down;
- if (down) {
- mListener.onDown(e);
- } else {
- mListener.onUp(e);
- }
- }
-
- public void onTouchEvent(MotionEvent ev) {
- switch (ev.getAction() & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN:
- setState(true, ev);
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_POINTER_DOWN: // Multitouch event - abort.
- setState(false, ev);
- break;
- }
- }
-
- public boolean isDown() {
- return mStillDown;
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
+
+import android.view.MotionEvent;
+
+public class DownUpDetector {
+ public interface DownUpListener {
+ void onDown(MotionEvent e);
+ void onUp(MotionEvent e);
+ }
+
+ private boolean mStillDown;
+ private DownUpListener mListener;
+
+ public DownUpDetector(DownUpListener listener) {
+ mListener = listener;
+ }
+
+ private void setState(boolean down, MotionEvent e) {
+ if (down == mStillDown) return;
+ mStillDown = down;
+ if (down) {
+ mListener.onDown(e);
+ } else {
+ mListener.onUp(e);
+ }
+ }
+
+ public void onTouchEvent(MotionEvent ev) {
+ switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ setState(true, ev);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_POINTER_DOWN: // Multitouch event - abort.
+ setState(false, ev);
+ break;
+ }
+ }
+
+ public boolean isDown() {
+ return mStillDown;
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/ui/GLRoot.java b/src/com/hippo/ehviewer/gallery/ui/GLRoot.java
index 565e7fd27..9a56a4395 100644
--- a/src/com/hippo/ehviewer/gallery/ui/GLRoot.java
+++ b/src/com/hippo/ehviewer/gallery/ui/GLRoot.java
@@ -1,53 +1,53 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
-
-import android.content.Context;
-import android.graphics.Matrix;
-
-import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
-import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
-
-public interface GLRoot {
-
- // Listener will be called when GL is idle AND before each frame.
- // Mainly used for uploading textures.
- public static interface OnGLIdleListener {
- public boolean onGLIdle(
- GLCanvas canvas, boolean renderRequested);
- }
-
- public void addOnGLIdleListener(OnGLIdleListener listener);
- public void registerLaunchedAnimation(CanvasAnimation animation);
- public void requestRenderForced();
- public void requestRender();
- public void requestLayoutContentPane();
-
- public void lockRenderThread();
- public void unlockRenderThread();
-
- public void setContentPane(GLView content);
- public void setOrientationSource(OrientationSource source);
- public int getDisplayRotation();
- public int getCompensation();
- public Matrix getCompensationMatrix();
- public void freeze();
- public void unfreeze();
- public void setLightsOutMode(boolean enabled);
-
- public Context getContext();
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
+
+import android.content.Context;
+import android.graphics.Matrix;
+
+import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
+import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
+
+public interface GLRoot {
+
+ // Listener will be called when GL is idle AND before each frame.
+ // Mainly used for uploading textures.
+ public static interface OnGLIdleListener {
+ public boolean onGLIdle(
+ GLCanvas canvas, boolean renderRequested);
+ }
+
+ public void addOnGLIdleListener(OnGLIdleListener listener);
+ public void registerLaunchedAnimation(CanvasAnimation animation);
+ public void requestRenderForced();
+ public void requestRender();
+ public void requestLayoutContentPane();
+
+ public void lockRenderThread();
+ public void unlockRenderThread();
+
+ public void setContentPane(GLView content);
+ public void setOrientationSource(OrientationSource source);
+ public int getDisplayRotation();
+ public int getCompensation();
+ public Matrix getCompensationMatrix();
+ public void freeze();
+ public void unfreeze();
+ public void setLightsOutMode(boolean enabled);
+
+ public Context getContext();
+}
diff --git a/src/com/hippo/ehviewer/gallery/ui/GLView.java b/src/com/hippo/ehviewer/gallery/ui/GLView.java
index ae8fc8267..180a2fd7d 100644
--- a/src/com/hippo/ehviewer/gallery/ui/GLView.java
+++ b/src/com/hippo/ehviewer/gallery/ui/GLView.java
@@ -1,447 +1,447 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
-
-import android.annotation.SuppressLint;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import com.hippo.ehviewer.util.Log;
-import android.view.MotionEvent;
-
-import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
-import com.hippo.ehviewer.gallery.common.Utils;
-import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
-
-import java.util.ArrayList;
-
-// GLView is a UI component. It can render to a GLCanvas and accept touch
-// events. A GLView may have zero or more child GLView and they form a tree
-// structure. The rendering and event handling will pass through the tree
-// structure.
-//
-// A GLView tree should be attached to a GLRoot before event dispatching and
-// rendering happens. GLView asks GLRoot to re-render or re-layout the
-// GLView hierarchy using requestRender() and requestLayoutContentPane().
-//
-// The render() method is called in a separate thread. Before calling
-// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the
-// rendering thread running at the same time. If there are other entry points
-// from main thread (like a Handler) in your GLView, you need to call
-// lockRendering() if the rendering thread should not run at the same time.
-//
-public class GLView {
- private static final String TAG = "GLView";
-
- public static final int VISIBLE = 0;
- public static final int INVISIBLE = 1;
-
- private static final int FLAG_INVISIBLE = 1;
- private static final int FLAG_SET_MEASURED_SIZE = 2;
- private static final int FLAG_LAYOUT_REQUESTED = 4;
-
- public interface OnClickListener {
- void onClick(GLView v);
- }
-
- protected final Rect mBounds = new Rect();
- protected final Rect mPaddings = new Rect();
-
- private GLRoot mRoot;
- protected GLView mParent;
- private ArrayList mComponents;
- private GLView mMotionTarget;
-
- private CanvasAnimation mAnimation;
-
- private int mViewFlags = 0;
-
- protected int mMeasuredWidth = 0;
- protected int mMeasuredHeight = 0;
-
- private int mLastWidthSpec = -1;
- private int mLastHeightSpec = -1;
-
- protected int mScrollY = 0;
- protected int mScrollX = 0;
- protected int mScrollHeight = 0;
- protected int mScrollWidth = 0;
-
- private float [] mBackgroundColor;
-
- public void startAnimation(CanvasAnimation animation) {
- GLRoot root = getGLRoot();
- if (root == null) throw new IllegalStateException();
- mAnimation = animation;
- if (mAnimation != null) {
- mAnimation.start();
- root.registerLaunchedAnimation(mAnimation);
- }
- invalidate();
- }
-
- // Sets the visiblity of this GLView (either GLView.VISIBLE or
- // GLView.INVISIBLE).
- public void setVisibility(int visibility) {
- if (visibility == getVisibility()) return;
- if (visibility == VISIBLE) {
- mViewFlags &= ~FLAG_INVISIBLE;
- } else {
- mViewFlags |= FLAG_INVISIBLE;
- }
- onVisibilityChanged(visibility);
- invalidate();
- }
-
- // Returns GLView.VISIBLE or GLView.INVISIBLE
- public int getVisibility() {
- return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;
- }
-
- // This should only be called on the content pane (the topmost GLView).
- public void attachToRoot(GLRoot root) {
- Utils.assertTrue(mParent == null && mRoot == null);
- onAttachToRoot(root);
- }
-
- // This should only be called on the content pane (the topmost GLView).
- public void detachFromRoot() {
- Utils.assertTrue(mParent == null && mRoot != null);
- onDetachFromRoot();
- }
-
- // Returns the number of children of the GLView.
- public int getComponentCount() {
- return mComponents == null ? 0 : mComponents.size();
- }
-
- // Returns the children for the given index.
- public GLView getComponent(int index) {
- if (mComponents == null) {
- throw new ArrayIndexOutOfBoundsException(index);
- }
- return mComponents.get(index);
- }
-
- // Adds a child to this GLView.
- public void addComponent(GLView component) {
- // Make sure the component doesn't have a parent currently.
- if (component.mParent != null) throw new IllegalStateException();
-
- // Build parent-child links
- if (mComponents == null) {
- mComponents = new ArrayList();
- }
- mComponents.add(component);
- component.mParent = this;
-
- // If this is added after we have a root, tell the component.
- if (mRoot != null) {
- component.onAttachToRoot(mRoot);
- }
- }
-
- // Removes a child from this GLView.
- public boolean removeComponent(GLView component) {
- if (mComponents == null) return false;
- if (mComponents.remove(component)) {
- removeOneComponent(component);
- return true;
- }
- return false;
- }
-
- // Removes all children of this GLView.
- public void removeAllComponents() {
- for (int i = 0, n = mComponents.size(); i < n; ++i) {
- removeOneComponent(mComponents.get(i));
- }
- mComponents.clear();
- }
-
- private void removeOneComponent(GLView component) {
- if (mMotionTarget == component) {
- long now = SystemClock.uptimeMillis();
- MotionEvent cancelEvent = MotionEvent.obtain(
- now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- dispatchTouchEvent(cancelEvent);
- cancelEvent.recycle();
- }
- component.onDetachFromRoot();
- component.mParent = null;
- }
-
- public Rect bounds() {
- return mBounds;
- }
-
- public int getWidth() {
- return mBounds.right - mBounds.left;
- }
-
- public int getHeight() {
- return mBounds.bottom - mBounds.top;
- }
-
- public GLRoot getGLRoot() {
- return mRoot;
- }
-
- // Request re-rendering of the view hierarchy.
- // This is used for animation or when the contents changed.
- public void invalidate() {
- GLRoot root = getGLRoot();
- if (root != null) root.requestRender();
- }
-
- // Request re-layout of the view hierarchy.
- public void requestLayout() {
- mViewFlags |= FLAG_LAYOUT_REQUESTED;
- mLastHeightSpec = -1;
- mLastWidthSpec = -1;
- if (mParent != null) {
- mParent.requestLayout();
- } else {
- // Is this a content pane ?
- GLRoot root = getGLRoot();
- if (root != null) root.requestLayoutContentPane();
- }
- }
-
- protected void render(GLCanvas canvas) {
- renderBackground(canvas);
- canvas.save();
- for (int i = 0, n = getComponentCount(); i < n; ++i) {
- renderChild(canvas, getComponent(i));
- }
- canvas.restore();
- }
-
- public float [] getBackgroundColor() {
- return mBackgroundColor;
- }
-
- public void setBackgroundColor(float [] color) {
- mBackgroundColor = color;
- }
-
- protected void renderBackground(GLCanvas view) {
- if (mBackgroundColor != null) {
- view.clearBuffer(mBackgroundColor);
- }
- }
-
- protected void renderChild(GLCanvas canvas, GLView component) {
- if (component.getVisibility() != GLView.VISIBLE
- && component.mAnimation == null) return;
-
- int xoffset = component.mBounds.left - mScrollX;
- int yoffset = component.mBounds.top - mScrollY;
-
- canvas.translate(xoffset, yoffset);
-
- CanvasAnimation anim = component.mAnimation;
- if (anim != null) {
- canvas.save(anim.getCanvasSaveFlags());
- if (anim.calculate(AnimationTime.get())) {
- invalidate();
- } else {
- component.mAnimation = null;
- }
- anim.apply(canvas);
- }
- component.render(canvas);
- if (anim != null) canvas.restore();
- canvas.translate(-xoffset, -yoffset);
- }
-
- protected boolean onTouch(MotionEvent event) {
- return false;
- }
-
- protected boolean dispatchTouchEvent(MotionEvent event,
- int x, int y, GLView component, boolean checkBounds) {
- Rect rect = component.mBounds;
- int left = rect.left;
- int top = rect.top;
- if (!checkBounds || rect.contains(x, y)) {
- event.offsetLocation(-left, -top);
- if (component.dispatchTouchEvent(event)) {
- event.offsetLocation(left, top);
- return true;
- }
- event.offsetLocation(left, top);
- }
- return false;
- }
-
- protected boolean dispatchTouchEvent(MotionEvent event) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int action = event.getAction();
- if (mMotionTarget != null) {
- if (action == MotionEvent.ACTION_DOWN) {
- MotionEvent cancel = MotionEvent.obtain(event);
- cancel.setAction(MotionEvent.ACTION_CANCEL);
- dispatchTouchEvent(cancel, x, y, mMotionTarget, false);
- mMotionTarget = null;
- } else {
- dispatchTouchEvent(event, x, y, mMotionTarget, false);
- if (action == MotionEvent.ACTION_CANCEL
- || action == MotionEvent.ACTION_UP) {
- mMotionTarget = null;
- }
- return true;
- }
- }
- if (action == MotionEvent.ACTION_DOWN) {
- // in the reverse rendering order
- for (int i = getComponentCount() - 1; i >= 0; --i) {
- GLView component = getComponent(i);
- if (component.getVisibility() != GLView.VISIBLE) continue;
- if (dispatchTouchEvent(event, x, y, component, true)) {
- mMotionTarget = component;
- return true;
- }
- }
- }
- return onTouch(event);
- }
-
- public Rect getPaddings() {
- return mPaddings;
- }
-
- @SuppressLint("WrongCall")
- public void layout(int left, int top, int right, int bottom) {
- boolean sizeChanged = setBounds(left, top, right, bottom);
- mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
- // We call onLayout no matter sizeChanged is true or not because the
- // orientation may change without changing the size of the View (for
- // example, rotate the device by 180 degrees), and we want to handle
- // orientation change in onLayout.
- onLayout(sizeChanged, left, top, right, bottom);
- }
-
- private boolean setBounds(int left, int top, int right, int bottom) {
- boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
- || (bottom - top) != (mBounds.bottom - mBounds.top);
- mBounds.set(left, top, right, bottom);
- return sizeChanged;
- }
-
- @SuppressLint("WrongCall")
- public void measure(int widthSpec, int heightSpec) {
- if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
- && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
- return;
- }
-
- mLastWidthSpec = widthSpec;
- mLastHeightSpec = heightSpec;
-
- mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
- onMeasure(widthSpec, heightSpec);
- if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
- throw new IllegalStateException(getClass().getName()
- + " should call setMeasuredSize() in onMeasure()");
- }
- }
-
- protected void onMeasure(int widthSpec, int heightSpec) {
- }
-
- protected void setMeasuredSize(int width, int height) {
- mViewFlags |= FLAG_SET_MEASURED_SIZE;
- mMeasuredWidth = width;
- mMeasuredHeight = height;
- }
-
- public int getMeasuredWidth() {
- return mMeasuredWidth;
- }
-
- public int getMeasuredHeight() {
- return mMeasuredHeight;
- }
-
- protected void onLayout(
- boolean changeSize, int left, int top, int right, int bottom) {
- }
-
- /**
- * Gets the bounds of the given descendant that relative to this view.
- */
- public boolean getBoundsOf(GLView descendant, Rect out) {
- int xoffset = 0;
- int yoffset = 0;
- GLView view = descendant;
- while (view != this) {
- if (view == null) return false;
- Rect bounds = view.mBounds;
- xoffset += bounds.left;
- yoffset += bounds.top;
- view = view.mParent;
- }
- out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
- yoffset + descendant.getHeight());
- return true;
- }
-
- protected void onVisibilityChanged(int visibility) {
- for (int i = 0, n = getComponentCount(); i < n; ++i) {
- GLView child = getComponent(i);
- if (child.getVisibility() == GLView.VISIBLE) {
- child.onVisibilityChanged(visibility);
- }
- }
- }
-
- protected void onAttachToRoot(GLRoot root) {
- mRoot = root;
- for (int i = 0, n = getComponentCount(); i < n; ++i) {
- getComponent(i).onAttachToRoot(root);
- }
- }
-
- protected void onDetachFromRoot() {
- for (int i = 0, n = getComponentCount(); i < n; ++i) {
- getComponent(i).onDetachFromRoot();
- }
- mRoot = null;
- }
-
- public void lockRendering() {
- if (mRoot != null) {
- mRoot.lockRenderThread();
- }
- }
-
- public void unlockRendering() {
- if (mRoot != null) {
- mRoot.unlockRenderThread();
- }
- }
-
- // This is for debugging only.
- // Dump the view hierarchy into log.
- void dumpTree(String prefix) {
- Log.d(TAG, prefix + getClass().getSimpleName());
- for (int i = 0, n = getComponentCount(); i < n; ++i) {
- getComponent(i).dumpTree(prefix + "....");
- }
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.gallery.ui;
+
+import android.annotation.SuppressLint;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import com.hippo.ehviewer.util.Log;
+import android.view.MotionEvent;
+
+import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
+import com.hippo.ehviewer.gallery.common.Utils;
+import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
+
+import java.util.ArrayList;
+
+// GLView is a UI component. It can render to a GLCanvas and accept touch
+// events. A GLView may have zero or more child GLView and they form a tree
+// structure. The rendering and event handling will pass through the tree
+// structure.
+//
+// A GLView tree should be attached to a GLRoot before event dispatching and
+// rendering happens. GLView asks GLRoot to re-render or re-layout the
+// GLView hierarchy using requestRender() and requestLayoutContentPane().
+//
+// The render() method is called in a separate thread. Before calling
+// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the
+// rendering thread running at the same time. If there are other entry points
+// from main thread (like a Handler) in your GLView, you need to call
+// lockRendering() if the rendering thread should not run at the same time.
+//
+public class GLView {
+ private static final String TAG = "GLView";
+
+ public static final int VISIBLE = 0;
+ public static final int INVISIBLE = 1;
+
+ private static final int FLAG_INVISIBLE = 1;
+ private static final int FLAG_SET_MEASURED_SIZE = 2;
+ private static final int FLAG_LAYOUT_REQUESTED = 4;
+
+ public interface OnClickListener {
+ void onClick(GLView v);
+ }
+
+ protected final Rect mBounds = new Rect();
+ protected final Rect mPaddings = new Rect();
+
+ private GLRoot mRoot;
+ protected GLView mParent;
+ private ArrayList mComponents;
+ private GLView mMotionTarget;
+
+ private CanvasAnimation mAnimation;
+
+ private int mViewFlags = 0;
+
+ protected int mMeasuredWidth = 0;
+ protected int mMeasuredHeight = 0;
+
+ private int mLastWidthSpec = -1;
+ private int mLastHeightSpec = -1;
+
+ protected int mScrollY = 0;
+ protected int mScrollX = 0;
+ protected int mScrollHeight = 0;
+ protected int mScrollWidth = 0;
+
+ private float [] mBackgroundColor;
+
+ public void startAnimation(CanvasAnimation animation) {
+ GLRoot root = getGLRoot();
+ if (root == null) throw new IllegalStateException();
+ mAnimation = animation;
+ if (mAnimation != null) {
+ mAnimation.start();
+ root.registerLaunchedAnimation(mAnimation);
+ }
+ invalidate();
+ }
+
+ // Sets the visiblity of this GLView (either GLView.VISIBLE or
+ // GLView.INVISIBLE).
+ public void setVisibility(int visibility) {
+ if (visibility == getVisibility()) return;
+ if (visibility == VISIBLE) {
+ mViewFlags &= ~FLAG_INVISIBLE;
+ } else {
+ mViewFlags |= FLAG_INVISIBLE;
+ }
+ onVisibilityChanged(visibility);
+ invalidate();
+ }
+
+ // Returns GLView.VISIBLE or GLView.INVISIBLE
+ public int getVisibility() {
+ return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;
+ }
+
+ // This should only be called on the content pane (the topmost GLView).
+ public void attachToRoot(GLRoot root) {
+ Utils.assertTrue(mParent == null && mRoot == null);
+ onAttachToRoot(root);
+ }
+
+ // This should only be called on the content pane (the topmost GLView).
+ public void detachFromRoot() {
+ Utils.assertTrue(mParent == null && mRoot != null);
+ onDetachFromRoot();
+ }
+
+ // Returns the number of children of the GLView.
+ public int getComponentCount() {
+ return mComponents == null ? 0 : mComponents.size();
+ }
+
+ // Returns the children for the given index.
+ public GLView getComponent(int index) {
+ if (mComponents == null) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return mComponents.get(index);
+ }
+
+ // Adds a child to this GLView.
+ public void addComponent(GLView component) {
+ // Make sure the component doesn't have a parent currently.
+ if (component.mParent != null) throw new IllegalStateException();
+
+ // Build parent-child links
+ if (mComponents == null) {
+ mComponents = new ArrayList();
+ }
+ mComponents.add(component);
+ component.mParent = this;
+
+ // If this is added after we have a root, tell the component.
+ if (mRoot != null) {
+ component.onAttachToRoot(mRoot);
+ }
+ }
+
+ // Removes a child from this GLView.
+ public boolean removeComponent(GLView component) {
+ if (mComponents == null) return false;
+ if (mComponents.remove(component)) {
+ removeOneComponent(component);
+ return true;
+ }
+ return false;
+ }
+
+ // Removes all children of this GLView.
+ public void removeAllComponents() {
+ for (int i = 0, n = mComponents.size(); i < n; ++i) {
+ removeOneComponent(mComponents.get(i));
+ }
+ mComponents.clear();
+ }
+
+ private void removeOneComponent(GLView component) {
+ if (mMotionTarget == component) {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ dispatchTouchEvent(cancelEvent);
+ cancelEvent.recycle();
+ }
+ component.onDetachFromRoot();
+ component.mParent = null;
+ }
+
+ public Rect bounds() {
+ return mBounds;
+ }
+
+ public int getWidth() {
+ return mBounds.right - mBounds.left;
+ }
+
+ public int getHeight() {
+ return mBounds.bottom - mBounds.top;
+ }
+
+ public GLRoot getGLRoot() {
+ return mRoot;
+ }
+
+ // Request re-rendering of the view hierarchy.
+ // This is used for animation or when the contents changed.
+ public void invalidate() {
+ GLRoot root = getGLRoot();
+ if (root != null) root.requestRender();
+ }
+
+ // Request re-layout of the view hierarchy.
+ public void requestLayout() {
+ mViewFlags |= FLAG_LAYOUT_REQUESTED;
+ mLastHeightSpec = -1;
+ mLastWidthSpec = -1;
+ if (mParent != null) {
+ mParent.requestLayout();
+ } else {
+ // Is this a content pane ?
+ GLRoot root = getGLRoot();
+ if (root != null) root.requestLayoutContentPane();
+ }
+ }
+
+ protected void render(GLCanvas canvas) {
+ renderBackground(canvas);
+ canvas.save();
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ renderChild(canvas, getComponent(i));
+ }
+ canvas.restore();
+ }
+
+ public float [] getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ public void setBackgroundColor(float [] color) {
+ mBackgroundColor = color;
+ }
+
+ protected void renderBackground(GLCanvas view) {
+ if (mBackgroundColor != null) {
+ view.clearBuffer(mBackgroundColor);
+ }
+ }
+
+ protected void renderChild(GLCanvas canvas, GLView component) {
+ if (component.getVisibility() != GLView.VISIBLE
+ && component.mAnimation == null) return;
+
+ int xoffset = component.mBounds.left - mScrollX;
+ int yoffset = component.mBounds.top - mScrollY;
+
+ canvas.translate(xoffset, yoffset);
+
+ CanvasAnimation anim = component.mAnimation;
+ if (anim != null) {
+ canvas.save(anim.getCanvasSaveFlags());
+ if (anim.calculate(AnimationTime.get())) {
+ invalidate();
+ } else {
+ component.mAnimation = null;
+ }
+ anim.apply(canvas);
+ }
+ component.render(canvas);
+ if (anim != null) canvas.restore();
+ canvas.translate(-xoffset, -yoffset);
+ }
+
+ protected boolean onTouch(MotionEvent event) {
+ return false;
+ }
+
+ protected boolean dispatchTouchEvent(MotionEvent event,
+ int x, int y, GLView component, boolean checkBounds) {
+ Rect rect = component.mBounds;
+ int left = rect.left;
+ int top = rect.top;
+ if (!checkBounds || rect.contains(x, y)) {
+ event.offsetLocation(-left, -top);
+ if (component.dispatchTouchEvent(event)) {
+ event.offsetLocation(left, top);
+ return true;
+ }
+ event.offsetLocation(left, top);
+ }
+ return false;
+ }
+
+ protected boolean dispatchTouchEvent(MotionEvent event) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int action = event.getAction();
+ if (mMotionTarget != null) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ MotionEvent cancel = MotionEvent.obtain(event);
+ cancel.setAction(MotionEvent.ACTION_CANCEL);
+ dispatchTouchEvent(cancel, x, y, mMotionTarget, false);
+ mMotionTarget = null;
+ } else {
+ dispatchTouchEvent(event, x, y, mMotionTarget, false);
+ if (action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_UP) {
+ mMotionTarget = null;
+ }
+ return true;
+ }
+ }
+ if (action == MotionEvent.ACTION_DOWN) {
+ // in the reverse rendering order
+ for (int i = getComponentCount() - 1; i >= 0; --i) {
+ GLView component = getComponent(i);
+ if (component.getVisibility() != GLView.VISIBLE) continue;
+ if (dispatchTouchEvent(event, x, y, component, true)) {
+ mMotionTarget = component;
+ return true;
+ }
+ }
+ }
+ return onTouch(event);
+ }
+
+ public Rect getPaddings() {
+ return mPaddings;
+ }
+
+ @SuppressLint("WrongCall")
+ public void layout(int left, int top, int right, int bottom) {
+ boolean sizeChanged = setBounds(left, top, right, bottom);
+ mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
+ // We call onLayout no matter sizeChanged is true or not because the
+ // orientation may change without changing the size of the View (for
+ // example, rotate the device by 180 degrees), and we want to handle
+ // orientation change in onLayout.
+ onLayout(sizeChanged, left, top, right, bottom);
+ }
+
+ private boolean setBounds(int left, int top, int right, int bottom) {
+ boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
+ || (bottom - top) != (mBounds.bottom - mBounds.top);
+ mBounds.set(left, top, right, bottom);
+ return sizeChanged;
+ }
+
+ @SuppressLint("WrongCall")
+ public void measure(int widthSpec, int heightSpec) {
+ if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
+ && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
+ return;
+ }
+
+ mLastWidthSpec = widthSpec;
+ mLastHeightSpec = heightSpec;
+
+ mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
+ onMeasure(widthSpec, heightSpec);
+ if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
+ throw new IllegalStateException(getClass().getName()
+ + " should call setMeasuredSize() in onMeasure()");
+ }
+ }
+
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ }
+
+ protected void setMeasuredSize(int width, int height) {
+ mViewFlags |= FLAG_SET_MEASURED_SIZE;
+ mMeasuredWidth = width;
+ mMeasuredHeight = height;
+ }
+
+ public int getMeasuredWidth() {
+ return mMeasuredWidth;
+ }
+
+ public int getMeasuredHeight() {
+ return mMeasuredHeight;
+ }
+
+ protected void onLayout(
+ boolean changeSize, int left, int top, int right, int bottom) {
+ }
+
+ /**
+ * Gets the bounds of the given descendant that relative to this view.
+ */
+ public boolean getBoundsOf(GLView descendant, Rect out) {
+ int xoffset = 0;
+ int yoffset = 0;
+ GLView view = descendant;
+ while (view != this) {
+ if (view == null) return false;
+ Rect bounds = view.mBounds;
+ xoffset += bounds.left;
+ yoffset += bounds.top;
+ view = view.mParent;
+ }
+ out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
+ yoffset + descendant.getHeight());
+ return true;
+ }
+
+ protected void onVisibilityChanged(int visibility) {
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ GLView child = getComponent(i);
+ if (child.getVisibility() == GLView.VISIBLE) {
+ child.onVisibilityChanged(visibility);
+ }
+ }
+ }
+
+ protected void onAttachToRoot(GLRoot root) {
+ mRoot = root;
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ getComponent(i).onAttachToRoot(root);
+ }
+ }
+
+ protected void onDetachFromRoot() {
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ getComponent(i).onDetachFromRoot();
+ }
+ mRoot = null;
+ }
+
+ public void lockRendering() {
+ if (mRoot != null) {
+ mRoot.lockRenderThread();
+ }
+ }
+
+ public void unlockRendering() {
+ if (mRoot != null) {
+ mRoot.unlockRenderThread();
+ }
+ }
+
+ // This is for debugging only.
+ // Dump the view hierarchy into log.
+ void dumpTree(String prefix) {
+ Log.d(TAG, prefix + getClass().getSimpleName());
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ getComponent(i).dumpTree(prefix + "....");
+ }
+ }
+}
diff --git a/src/com/hippo/ehviewer/gallery/ui/OrientationSource.java b/src/com/hippo/ehviewer/gallery/ui/OrientationSource.java
index b4825a7bf..2765a130c 100644
--- a/src/com/hippo/ehviewer/gallery/ui/OrientationSource.java
+++ b/src/com/hippo/ehviewer/gallery/ui/OrientationSource.java
@@ -1,22 +1,22 @@
-/*
- * Copyright (C) 2012 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.hippo.ehviewer.gallery.ui;
-
-public interface OrientationSource {
- public int getDisplayRotation();
- public int getCompensation();
-}
+/*
+ * Copyright (C) 2012 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.hippo.ehviewer.gallery.ui;
+
+public interface OrientationSource {
+ public int getDisplayRotation();
+ public int getCompensation();
+}
diff --git a/src/com/hippo/ehviewer/gallery/util/MotionEventHelper.java b/src/com/hippo/ehviewer/gallery/util/MotionEventHelper.java
index 84d4afd6e..ea562148a 100644
--- a/src/com/hippo/ehviewer/gallery/util/MotionEventHelper.java
+++ b/src/com/hippo/ehviewer/gallery/util/MotionEventHelper.java
@@ -1,120 +1,120 @@
-/*
- * Copyright (C) 2012 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.hippo.ehviewer.gallery.util;
-
-import android.annotation.TargetApi;
-import android.graphics.Matrix;
-import android.util.FloatMath;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-
-import com.hippo.ehviewer.gallery.common.ApiHelper;
-
-public final class MotionEventHelper {
- private MotionEventHelper() {}
-
- public static MotionEvent transformEvent(MotionEvent e, Matrix m) {
- // We try to use the new transform method if possible because it uses
- // less memory.
- if (ApiHelper.HAS_MOTION_EVENT_TRANSFORM) {
- return transformEventNew(e, m);
- } else {
- return transformEventOld(e, m);
- }
- }
-
- @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
- private static MotionEvent transformEventNew(MotionEvent e, Matrix m) {
- MotionEvent newEvent = MotionEvent.obtain(e);
- newEvent.transform(m);
- return newEvent;
- }
-
- // This is copied from Input.cpp in the android framework.
- private static MotionEvent transformEventOld(MotionEvent e, Matrix m) {
- long downTime = e.getDownTime();
- long eventTime = e.getEventTime();
- int action = e.getAction();
- int pointerCount = e.getPointerCount();
- int[] pointerIds = getPointerIds(e);
- PointerCoords[] pointerCoords = getPointerCoords(e);
- int metaState = e.getMetaState();
- float xPrecision = e.getXPrecision();
- float yPrecision = e.getYPrecision();
- int deviceId = e.getDeviceId();
- int edgeFlags = e.getEdgeFlags();
- int source = e.getSource();
- int flags = e.getFlags();
-
- // Copy the x and y coordinates into an array, map them, and copy back.
- float[] xy = new float[pointerCoords.length * 2];
- for (int i = 0; i < pointerCount;i++) {
- xy[2 * i] = pointerCoords[i].x;
- xy[2 * i + 1] = pointerCoords[i].y;
- }
- m.mapPoints(xy);
- for (int i = 0; i < pointerCount;i++) {
- pointerCoords[i].x = xy[2 * i];
- pointerCoords[i].y = xy[2 * i + 1];
- pointerCoords[i].orientation = transformAngle(
- m, pointerCoords[i].orientation);
- }
-
- MotionEvent n = MotionEvent.obtain(downTime, eventTime, action,
- pointerCount, pointerIds, pointerCoords, metaState, xPrecision,
- yPrecision, deviceId, edgeFlags, source, flags);
-
- return n;
- }
-
- private static int[] getPointerIds(MotionEvent e) {
- int n = e.getPointerCount();
- int[] r = new int[n];
- for (int i = 0; i < n; i++) {
- r[i] = e.getPointerId(i);
- }
- return r;
- }
-
- private static PointerCoords[] getPointerCoords(MotionEvent e) {
- int n = e.getPointerCount();
- PointerCoords[] r = new PointerCoords[n];
- for (int i = 0; i < n; i++) {
- r[i] = new PointerCoords();
- e.getPointerCoords(i, r[i]);
- }
- return r;
- }
-
- private static float transformAngle(Matrix m, float angleRadians) {
- // Construct and transform a vector oriented at the specified clockwise
- // angle from vertical. Coordinate system: down is increasing Y, right is
- // increasing X.
- float[] v = new float[2];
- v[0] = FloatMath.sin(angleRadians);
- v[1] = -FloatMath.cos(angleRadians);
- m.mapVectors(v);
-
- // Derive the transformed vector's clockwise angle from vertical.
- float result = (float) Math.atan2(v[0], -v[1]);
- if (result < -Math.PI / 2) {
- result += Math.PI;
- } else if (result > Math.PI / 2) {
- result -= Math.PI;
- }
- return result;
- }
-}
+/*
+ * Copyright (C) 2012 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.hippo.ehviewer.gallery.util;
+
+import android.annotation.TargetApi;
+import android.graphics.Matrix;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+
+import com.hippo.ehviewer.gallery.common.ApiHelper;
+
+public final class MotionEventHelper {
+ private MotionEventHelper() {}
+
+ public static MotionEvent transformEvent(MotionEvent e, Matrix m) {
+ // We try to use the new transform method if possible because it uses
+ // less memory.
+ if (ApiHelper.HAS_MOTION_EVENT_TRANSFORM) {
+ return transformEventNew(e, m);
+ } else {
+ return transformEventOld(e, m);
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ private static MotionEvent transformEventNew(MotionEvent e, Matrix m) {
+ MotionEvent newEvent = MotionEvent.obtain(e);
+ newEvent.transform(m);
+ return newEvent;
+ }
+
+ // This is copied from Input.cpp in the android framework.
+ private static MotionEvent transformEventOld(MotionEvent e, Matrix m) {
+ long downTime = e.getDownTime();
+ long eventTime = e.getEventTime();
+ int action = e.getAction();
+ int pointerCount = e.getPointerCount();
+ int[] pointerIds = getPointerIds(e);
+ PointerCoords[] pointerCoords = getPointerCoords(e);
+ int metaState = e.getMetaState();
+ float xPrecision = e.getXPrecision();
+ float yPrecision = e.getYPrecision();
+ int deviceId = e.getDeviceId();
+ int edgeFlags = e.getEdgeFlags();
+ int source = e.getSource();
+ int flags = e.getFlags();
+
+ // Copy the x and y coordinates into an array, map them, and copy back.
+ float[] xy = new float[pointerCoords.length * 2];
+ for (int i = 0; i < pointerCount;i++) {
+ xy[2 * i] = pointerCoords[i].x;
+ xy[2 * i + 1] = pointerCoords[i].y;
+ }
+ m.mapPoints(xy);
+ for (int i = 0; i < pointerCount;i++) {
+ pointerCoords[i].x = xy[2 * i];
+ pointerCoords[i].y = xy[2 * i + 1];
+ pointerCoords[i].orientation = transformAngle(
+ m, pointerCoords[i].orientation);
+ }
+
+ MotionEvent n = MotionEvent.obtain(downTime, eventTime, action,
+ pointerCount, pointerIds, pointerCoords, metaState, xPrecision,
+ yPrecision, deviceId, edgeFlags, source, flags);
+
+ return n;
+ }
+
+ private static int[] getPointerIds(MotionEvent e) {
+ int n = e.getPointerCount();
+ int[] r = new int[n];
+ for (int i = 0; i < n; i++) {
+ r[i] = e.getPointerId(i);
+ }
+ return r;
+ }
+
+ private static PointerCoords[] getPointerCoords(MotionEvent e) {
+ int n = e.getPointerCount();
+ PointerCoords[] r = new PointerCoords[n];
+ for (int i = 0; i < n; i++) {
+ r[i] = new PointerCoords();
+ e.getPointerCoords(i, r[i]);
+ }
+ return r;
+ }
+
+ private static float transformAngle(Matrix m, float angleRadians) {
+ // Construct and transform a vector oriented at the specified clockwise
+ // angle from vertical. Coordinate system: down is increasing Y, right is
+ // increasing X.
+ float[] v = new float[2];
+ v[0] = FloatMath.sin(angleRadians);
+ v[1] = -FloatMath.cos(angleRadians);
+ m.mapVectors(v);
+
+ // Derive the transformed vector's clockwise angle from vertical.
+ float result = (float) Math.atan2(v[0], -v[1]);
+ if (result < -Math.PI / 2) {
+ result += Math.PI;
+ } else if (result > Math.PI / 2) {
+ result -= Math.PI;
+ }
+ return result;
+ }
+}
diff --git a/src/com/hippo/ehviewer/network/Constant.java b/src/com/hippo/ehviewer/network/Constant.java
index 227abb011..8656d3a98 100644
--- a/src/com/hippo/ehviewer/network/Constant.java
+++ b/src/com/hippo/ehviewer/network/Constant.java
@@ -1,14 +1,14 @@
-package com.hippo.ehviewer.network;
-
-public class Constant {
-
- public static final int HTTP_TEMP_REDIRECT = 307;
-
- public static String defaultUserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36";
- public static String userAgent = System.getProperty("http.agent", defaultUserAgent);
- public static final int DEFAULT_TIMEOUT = 5 * 1000;
- public static final int MAX_REDIRECTS = 5;
-
- public static final int BUFFER_SIZE = 4096;
-
-}
+package com.hippo.ehviewer.network;
+
+public class Constant {
+
+ public static final int HTTP_TEMP_REDIRECT = 307;
+
+ public static String defaultUserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36";
+ public static String userAgent = System.getProperty("http.agent", defaultUserAgent);
+ public static final int DEFAULT_TIMEOUT = 5 * 1000;
+ public static final int MAX_REDIRECTS = 5;
+
+ public static final int BUFFER_SIZE = 4096;
+
+}
diff --git a/src/com/hippo/ehviewer/util/Crash.java b/src/com/hippo/ehviewer/util/Crash.java
index 4ffa7ee91..da3fa214e 100644
--- a/src/com/hippo/ehviewer/util/Crash.java
+++ b/src/com/hippo/ehviewer/util/Crash.java
@@ -1,196 +1,196 @@
-package com.hippo.ehviewer.util;
-
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Build;
-import android.os.Environment;
-import com.hippo.ehviewer.util.Log;
-
-public class Crash {
- private static final String TAG = "Crash";
-
- private static Context mContext;
-
- private static String externalCrashFilePath = "/EhViewer/crash";
- private static String internalCrashFilePath = "/crash";
-
- private static final String NEW_CRAHS = "new_crash";
- private static final String LAST_CRASH_POSITION = "last_crash_position";
- private static final String LAST_CRASH_NAME = "last_crash_name";
-
- private static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
-
- private static boolean mInit = false;
-
- /**
- * Init Crash
- *
- * @param context Application context
- */
- public static void init(Context context) {
- if (mInit)
- return;
- mInit = true;
-
- mContext = context;
- }
-
- /**
- * Is init
- * @return True if init
- */
- public static boolean isInit() {
- return mInit;
- }
-
- /**
- * Save throwable infomation to file
- *
- * @param ex The throwable to store
- */
- public static void saveCrashInfo2File(Throwable ex) {
- StringBuffer sb = new StringBuffer();
- collectDeviceInfo(sb);
-
- Writer writer = new StringWriter();
- PrintWriter printWriter = new PrintWriter(writer);
- ex.printStackTrace(printWriter);
- Throwable cause = ex.getCause();
- while (cause != null) {
- cause.printStackTrace(printWriter);
- cause = cause.getCause();
- }
- printWriter.close();
- String result = writer.toString();
- Log.e(TAG, result);
- sb.append("======== CrashInfo ========\n");
- sb.append(result);
- try {
- long timestamp = System.currentTimeMillis();
- String time = formatter.format(new Date());
- String fileName = "crash-" + time + "-" + timestamp + ".log";
- String path = null;
- boolean position = false;
- if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- path = Environment.getExternalStorageDirectory() + externalCrashFilePath;
- position = true;
- }
- else {
- path = mContext.getFilesDir() + internalCrashFilePath;
- position = false;
- }
- File dir = new File(path);
- if (!dir.exists()) {
- dir.mkdirs();
- }
- FileOutputStream fos = new FileOutputStream(new File(path, fileName));
- fos.write(sb.toString().getBytes());
- fos.close();
- // Record info for next time upload
- SharedPreferences configPre = mContext.getSharedPreferences("config", 0);
- SharedPreferences.Editor editor = configPre.edit();
- editor.putBoolean(NEW_CRAHS, true);
- editor.putBoolean(LAST_CRASH_POSITION, position);
- editor.putString(LAST_CRASH_NAME, fileName);
- editor.commit();
- } catch (Exception e) {
- Log.e(TAG, "An error occured while writing crash file...", e);
- }
- }
-
- private static void collectDeviceInfo(StringBuffer sb) {
- try {
- PackageManager pm = mContext.getPackageManager();
- PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
- if (pi != null) {
- String versionName = pi.versionName == null ? "null" : pi.versionName;
- String versionCode = String.valueOf(pi.versionCode);
- sb.append("======== PackageInfo ========\n");
- sb.append("PackageName=" + pi.packageName + "\n");
- sb.append("VersionName=" + versionName + "\n");
- sb.append("VersionCode=" + versionCode + "\n");
- sb.append("\n");
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "an error occured when collect package info", e);
- }
- sb.append("======== DeviceInfo ========\n");
- sb.append("BOARD=" + Build.BOARD + "\n");
- sb.append("BOOTLOADER=" + Build.BOOTLOADER + "\n");
- sb.append("CPU_ABI=" + Build.CPU_ABI + "\n");
- sb.append("CPU_ABI2=" + Build.CPU_ABI2 + "\n");
- sb.append("DEVICE=" + Build.DEVICE + "\n");
- sb.append("DISPLAY=" + Build.DISPLAY + "\n");
- sb.append("FINGERPRINT=" + Build.FINGERPRINT + "\n");
- sb.append("HARDWARE=" + Build.HARDWARE + "\n");
- sb.append("HOST=" + Build.HOST + "\n");
- sb.append("ID=" + Build.ID + "\n");
- sb.append("MANUFACTURER=" + Build.MANUFACTURER + "\n");
- sb.append("MODEL=" + Build.MODEL + "\n");
- sb.append("PRODUCT=" + Build.PRODUCT + "\n");
- sb.append("RADIO=" + Build.getRadioVersion() + "\n");
- sb.append("SERIAL=" + Build.SERIAL + "\n");
- sb.append("TAGS=" + Build.TAGS + "\n");
- sb.append("TYPE=" + Build.TYPE + "\n");
- sb.append("USER=" + Build.USER + "\n");
- sb.append("CODENAME=" + Build.VERSION.CODENAME + "\n");
- sb.append("INCREMENTAL=" + Build.VERSION.INCREMENTAL + "\n");
- sb.append("RELEASE=" + Build.VERSION.RELEASE + "\n");
- sb.append("SDK=" + Build.VERSION.SDK_INT + "\n");
- sb.append("\n");
- }
-
- /**
- * Check is there last crash
- *
- * @return Return the crash String or null if there is no
- */
- public static String getLastCrash() {
- SharedPreferences configPre = mContext.getSharedPreferences("config", 0);
- if (!configPre.getBoolean(NEW_CRAHS, false))
- return null;
-
- boolean position = configPre.getBoolean(LAST_CRASH_POSITION, false);
- String lastCrashName;
- if ((lastCrashName = configPre.getString(LAST_CRASH_NAME, null)) == null)
- return null;
-
- File lastCrashFile;
- if (position) {
- if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
- return null;
- lastCrashFile = new File(Environment.getExternalStorageDirectory() + externalCrashFilePath, lastCrashName);
- }
- else {
- lastCrashFile = new File(mContext.getFilesDir() + internalCrashFilePath, lastCrashName);
- }
- if (!lastCrashFile.isFile())
- return null;
-
- try {
- byte[] buffer = new byte[(int) lastCrashFile.length()];
- DataInputStream din = new DataInputStream(new FileInputStream(lastCrashFile));
- din.readFully(buffer);
- din.close();
- configPre.edit().putBoolean(NEW_CRAHS, false).commit();
- return new String(buffer);
- } catch (Exception e) {
- Log.e(TAG, "An error occured while reading crash file...", e);
- }
- return null;
- }
-}
+package com.hippo.ehviewer.util;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.os.Environment;
+import com.hippo.ehviewer.util.Log;
+
+public class Crash {
+ private static final String TAG = "Crash";
+
+ private static Context mContext;
+
+ private static String externalCrashFilePath = "/EhViewer/crash";
+ private static String internalCrashFilePath = "/crash";
+
+ private static final String NEW_CRAHS = "new_crash";
+ private static final String LAST_CRASH_POSITION = "last_crash_position";
+ private static final String LAST_CRASH_NAME = "last_crash_name";
+
+ private static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
+
+ private static boolean mInit = false;
+
+ /**
+ * Init Crash
+ *
+ * @param context Application context
+ */
+ public static void init(Context context) {
+ if (mInit)
+ return;
+ mInit = true;
+
+ mContext = context;
+ }
+
+ /**
+ * Is init
+ * @return True if init
+ */
+ public static boolean isInit() {
+ return mInit;
+ }
+
+ /**
+ * Save throwable infomation to file
+ *
+ * @param ex The throwable to store
+ */
+ public static void saveCrashInfo2File(Throwable ex) {
+ StringBuffer sb = new StringBuffer();
+ collectDeviceInfo(sb);
+
+ Writer writer = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(writer);
+ ex.printStackTrace(printWriter);
+ Throwable cause = ex.getCause();
+ while (cause != null) {
+ cause.printStackTrace(printWriter);
+ cause = cause.getCause();
+ }
+ printWriter.close();
+ String result = writer.toString();
+ Log.e(TAG, result);
+ sb.append("======== CrashInfo ========\n");
+ sb.append(result);
+ try {
+ long timestamp = System.currentTimeMillis();
+ String time = formatter.format(new Date());
+ String fileName = "crash-" + time + "-" + timestamp + ".log";
+ String path = null;
+ boolean position = false;
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ path = Environment.getExternalStorageDirectory() + externalCrashFilePath;
+ position = true;
+ }
+ else {
+ path = mContext.getFilesDir() + internalCrashFilePath;
+ position = false;
+ }
+ File dir = new File(path);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ FileOutputStream fos = new FileOutputStream(new File(path, fileName));
+ fos.write(sb.toString().getBytes());
+ fos.close();
+ // Record info for next time upload
+ SharedPreferences configPre = mContext.getSharedPreferences("config", 0);
+ SharedPreferences.Editor editor = configPre.edit();
+ editor.putBoolean(NEW_CRAHS, true);
+ editor.putBoolean(LAST_CRASH_POSITION, position);
+ editor.putString(LAST_CRASH_NAME, fileName);
+ editor.commit();
+ } catch (Exception e) {
+ Log.e(TAG, "An error occured while writing crash file...", e);
+ }
+ }
+
+ private static void collectDeviceInfo(StringBuffer sb) {
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
+ if (pi != null) {
+ String versionName = pi.versionName == null ? "null" : pi.versionName;
+ String versionCode = String.valueOf(pi.versionCode);
+ sb.append("======== PackageInfo ========\n");
+ sb.append("PackageName=" + pi.packageName + "\n");
+ sb.append("VersionName=" + versionName + "\n");
+ sb.append("VersionCode=" + versionCode + "\n");
+ sb.append("\n");
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "an error occured when collect package info", e);
+ }
+ sb.append("======== DeviceInfo ========\n");
+ sb.append("BOARD=" + Build.BOARD + "\n");
+ sb.append("BOOTLOADER=" + Build.BOOTLOADER + "\n");
+ sb.append("CPU_ABI=" + Build.CPU_ABI + "\n");
+ sb.append("CPU_ABI2=" + Build.CPU_ABI2 + "\n");
+ sb.append("DEVICE=" + Build.DEVICE + "\n");
+ sb.append("DISPLAY=" + Build.DISPLAY + "\n");
+ sb.append("FINGERPRINT=" + Build.FINGERPRINT + "\n");
+ sb.append("HARDWARE=" + Build.HARDWARE + "\n");
+ sb.append("HOST=" + Build.HOST + "\n");
+ sb.append("ID=" + Build.ID + "\n");
+ sb.append("MANUFACTURER=" + Build.MANUFACTURER + "\n");
+ sb.append("MODEL=" + Build.MODEL + "\n");
+ sb.append("PRODUCT=" + Build.PRODUCT + "\n");
+ sb.append("RADIO=" + Build.getRadioVersion() + "\n");
+ sb.append("SERIAL=" + Build.SERIAL + "\n");
+ sb.append("TAGS=" + Build.TAGS + "\n");
+ sb.append("TYPE=" + Build.TYPE + "\n");
+ sb.append("USER=" + Build.USER + "\n");
+ sb.append("CODENAME=" + Build.VERSION.CODENAME + "\n");
+ sb.append("INCREMENTAL=" + Build.VERSION.INCREMENTAL + "\n");
+ sb.append("RELEASE=" + Build.VERSION.RELEASE + "\n");
+ sb.append("SDK=" + Build.VERSION.SDK_INT + "\n");
+ sb.append("\n");
+ }
+
+ /**
+ * Check is there last crash
+ *
+ * @return Return the crash String or null if there is no
+ */
+ public static String getLastCrash() {
+ SharedPreferences configPre = mContext.getSharedPreferences("config", 0);
+ if (!configPre.getBoolean(NEW_CRAHS, false))
+ return null;
+
+ boolean position = configPre.getBoolean(LAST_CRASH_POSITION, false);
+ String lastCrashName;
+ if ((lastCrashName = configPre.getString(LAST_CRASH_NAME, null)) == null)
+ return null;
+
+ File lastCrashFile;
+ if (position) {
+ if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
+ return null;
+ lastCrashFile = new File(Environment.getExternalStorageDirectory() + externalCrashFilePath, lastCrashName);
+ }
+ else {
+ lastCrashFile = new File(mContext.getFilesDir() + internalCrashFilePath, lastCrashName);
+ }
+ if (!lastCrashFile.isFile())
+ return null;
+
+ try {
+ byte[] buffer = new byte[(int) lastCrashFile.length()];
+ DataInputStream din = new DataInputStream(new FileInputStream(lastCrashFile));
+ din.readFully(buffer);
+ din.close();
+ configPre.edit().putBoolean(NEW_CRAHS, false).commit();
+ return new String(buffer);
+ } catch (Exception e) {
+ Log.e(TAG, "An error occured while reading crash file...", e);
+ }
+ return null;
+ }
+}
diff --git a/src/com/hippo/ehviewer/util/Future.java b/src/com/hippo/ehviewer/util/Future.java
index c0d565634..661471a36 100644
--- a/src/com/hippo/ehviewer/util/Future.java
+++ b/src/com/hippo/ehviewer/util/Future.java
@@ -1,35 +1,35 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.util;
-
-// This Future differs from the java.util.concurrent.Future in these aspects:
-//
-// - Once cancel() is called, isCancelled() always returns true. It is a sticky
-// flag used to communicate to the implementation. The implmentation may
-// ignore that flag. Regardless whether the Future is cancelled, a return
-// value will be provided to get(). The implementation may choose to return
-// null if it finds the Future is cancelled.
-//
-// - get() does not throw exceptions.
-//
-public interface Future {
- public void cancel();
- public boolean isCancelled();
- public boolean isDone();
- public T get();
- public void waitDone();
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.util;
+
+// This Future differs from the java.util.concurrent.Future in these aspects:
+//
+// - Once cancel() is called, isCancelled() always returns true. It is a sticky
+// flag used to communicate to the implementation. The implmentation may
+// ignore that flag. Regardless whether the Future is cancelled, a return
+// value will be provided to get(). The implementation may choose to return
+// null if it finds the Future is cancelled.
+//
+// - get() does not throw exceptions.
+//
+public interface Future {
+ public void cancel();
+ public boolean isCancelled();
+ public boolean isDone();
+ public T get();
+ public void waitDone();
+}
diff --git a/src/com/hippo/ehviewer/util/FutureListener.java b/src/com/hippo/ehviewer/util/FutureListener.java
index 280f64451..54a1a916f 100644
--- a/src/com/hippo/ehviewer/util/FutureListener.java
+++ b/src/com/hippo/ehviewer/util/FutureListener.java
@@ -1,21 +1,21 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.util;
-
-public interface FutureListener {
- public void onFutureDone(Future future);
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.util;
+
+public interface FutureListener {
+ public void onFutureDone(Future future);
+}
diff --git a/src/com/hippo/ehviewer/util/IntArray.java b/src/com/hippo/ehviewer/util/IntArray.java
index ef32a7cad..b107cbff6 100644
--- a/src/com/hippo/ehviewer/util/IntArray.java
+++ b/src/com/hippo/ehviewer/util/IntArray.java
@@ -1,60 +1,60 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.util;
-
-public class IntArray {
- private static final int INIT_CAPACITY = 8;
-
- private int mData[] = new int[INIT_CAPACITY];
- private int mSize = 0;
-
- public void add(int value) {
- if (mData.length == mSize) {
- int temp[] = new int[mSize + mSize];
- System.arraycopy(mData, 0, temp, 0, mSize);
- mData = temp;
- }
- mData[mSize++] = value;
- }
-
- public int removeLast() {
- mSize--;
- return mData[mSize];
- }
-
- public int size() {
- return mSize;
- }
-
- // For testing only
- public int[] toArray(int[] result) {
- if (result == null || result.length < mSize) {
- result = new int[mSize];
- }
- System.arraycopy(mData, 0, result, 0, mSize);
- return result;
- }
-
- public int[] getInternalArray() {
- return mData;
- }
-
- public void clear() {
- mSize = 0;
- if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.util;
+
+public class IntArray {
+ private static final int INIT_CAPACITY = 8;
+
+ private int mData[] = new int[INIT_CAPACITY];
+ private int mSize = 0;
+
+ public void add(int value) {
+ if (mData.length == mSize) {
+ int temp[] = new int[mSize + mSize];
+ System.arraycopy(mData, 0, temp, 0, mSize);
+ mData = temp;
+ }
+ mData[mSize++] = value;
+ }
+
+ public int removeLast() {
+ mSize--;
+ return mData[mSize];
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ // For testing only
+ public int[] toArray(int[] result) {
+ if (result == null || result.length < mSize) {
+ result = new int[mSize];
+ }
+ System.arraycopy(mData, 0, result, 0, mSize);
+ return result;
+ }
+
+ public int[] getInternalArray() {
+ return mData;
+ }
+
+ public void clear() {
+ mSize = 0;
+ if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
+ }
+}
diff --git a/src/com/hippo/ehviewer/util/PriorityThreadFactory.java b/src/com/hippo/ehviewer/util/PriorityThreadFactory.java
index 877e8f1bf..438bae8c8 100644
--- a/src/com/hippo/ehviewer/util/PriorityThreadFactory.java
+++ b/src/com/hippo/ehviewer/util/PriorityThreadFactory.java
@@ -1,49 +1,49 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.util;
-
-
-import android.os.Process;
-
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * A thread factory that creates threads with a given thread priority.
- */
-public class PriorityThreadFactory implements ThreadFactory {
-
- private final int mPriority;
- private final AtomicInteger mNumber = new AtomicInteger();
- private final String mName;
-
- public PriorityThreadFactory(String name, int priority) {
- mName = name;
- mPriority = priority;
- }
-
- @Override
- public Thread newThread(Runnable r) {
- return new Thread(r, mName + '-' + mNumber.getAndIncrement()) {
- @Override
- public void run() {
- Process.setThreadPriority(mPriority);
- super.run();
- }
- };
- }
-
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.util;
+
+
+import android.os.Process;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A thread factory that creates threads with a given thread priority.
+ */
+public class PriorityThreadFactory implements ThreadFactory {
+
+ private final int mPriority;
+ private final AtomicInteger mNumber = new AtomicInteger();
+ private final String mName;
+
+ public PriorityThreadFactory(String name, int priority) {
+ mName = name;
+ mPriority = priority;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, mName + '-' + mNumber.getAndIncrement()) {
+ @Override
+ public void run() {
+ Process.setThreadPriority(mPriority);
+ super.run();
+ }
+ };
+ }
+
+}
diff --git a/src/com/hippo/ehviewer/util/ThreadPool.java b/src/com/hippo/ehviewer/util/ThreadPool.java
index 694f71b1c..7d30abb58 100644
--- a/src/com/hippo/ehviewer/util/ThreadPool.java
+++ b/src/com/hippo/ehviewer/util/ThreadPool.java
@@ -1,268 +1,268 @@
-/*
- * Copyright (C) 2010 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.hippo.ehviewer.util;
-
-import com.hippo.ehviewer.util.Log;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-public class ThreadPool {
- @SuppressWarnings("unused")
- private static final String TAG = "ThreadPool";
- private static final int CORE_POOL_SIZE = 4;
- private static final int MAX_POOL_SIZE = 8;
- private static final int KEEP_ALIVE_TIME = 10; // 10 seconds
-
- // Resource type
- public static final int MODE_NONE = 0;
- public static final int MODE_CPU = 1;
- public static final int MODE_NETWORK = 2;
-
- public static final JobContext JOB_CONTEXT_STUB = new JobContextStub();
-
- ResourceCounter mCpuCounter = new ResourceCounter(2);
- ResourceCounter mNetworkCounter = new ResourceCounter(2);
-
- // A Job is like a Callable, but it has an addition JobContext parameter.
- public interface Job {
- public T run(JobContext jc);
- }
-
- public interface JobContext {
- boolean isCancelled();
- void setCancelListener(CancelListener listener);
- boolean setMode(int mode);
- }
-
- private static class JobContextStub implements JobContext {
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- @Override
- public void setCancelListener(CancelListener listener) {
- }
-
- @Override
- public boolean setMode(int mode) {
- return true;
- }
- }
-
- public interface CancelListener {
- public void onCancel();
- }
-
- private static class ResourceCounter {
- public int value;
- public ResourceCounter(int v) {
- value = v;
- }
- }
-
- private final Executor mExecutor;
-
- public ThreadPool() {
- this(CORE_POOL_SIZE, MAX_POOL_SIZE);
- }
-
- public ThreadPool(int initPoolSize, int maxPoolSize) {
- mExecutor = new ThreadPoolExecutor(
- initPoolSize, maxPoolSize, KEEP_ALIVE_TIME,
- TimeUnit.SECONDS, new LinkedBlockingQueue(),
- new PriorityThreadFactory("thread-pool",
- android.os.Process.THREAD_PRIORITY_DEFAULT));
- }
-
- // Submit a job to the thread pool. The listener will be called when the
- // job is finished (or cancelled).
- public Future submit(Job job, FutureListener listener) {
- Worker w = new Worker(job, listener);
- mExecutor.execute(w);
- return w;
- }
-
- public Future submit(Job job) {
- return submit(job, null);
- }
-
- private class Worker implements Runnable, Future, JobContext {
- @SuppressWarnings("hiding")
- private static final String TAG = "Worker";
- private Job mJob;
- private FutureListener mListener;
- private CancelListener mCancelListener;
- private ResourceCounter mWaitOnResource;
- private volatile boolean mIsCancelled;
- private boolean mIsDone;
- private T mResult;
- private int mMode;
-
- public Worker(Job job, FutureListener listener) {
- mJob = job;
- mListener = listener;
- }
-
- // This is called by a thread in the thread pool.
- @Override
- public void run() {
- T result = null;
-
- // A job is in CPU mode by default. setMode returns false
- // if the job is cancelled.
- if (setMode(MODE_CPU)) {
- try {
- result = mJob.run(this);
- } catch (Throwable ex) {
- Log.w(TAG, "Exception in running a job", ex);
- }
- }
-
- synchronized(this) {
- setMode(MODE_NONE);
- mResult = result;
- mIsDone = true;
- notifyAll();
- }
- if (mListener != null) mListener.onFutureDone(this);
- }
-
- // Below are the methods for Future.
- @Override
- public synchronized void cancel() {
- if (mIsCancelled) return;
- mIsCancelled = true;
- if (mWaitOnResource != null) {
- synchronized (mWaitOnResource) {
- mWaitOnResource.notifyAll();
- }
- }
- if (mCancelListener != null) {
- mCancelListener.onCancel();
- }
- }
-
- @Override
- public boolean isCancelled() {
- return mIsCancelled;
- }
-
- @Override
- public synchronized boolean isDone() {
- return mIsDone;
- }
-
- @Override
- public synchronized T get() {
- while (!mIsDone) {
- try {
- wait();
- } catch (Exception ex) {
- Log.w(TAG, "ingore exception", ex);
- // ignore.
- }
- }
- return mResult;
- }
-
- @Override
- public void waitDone() {
- get();
- }
-
- // Below are the methods for JobContext (only called from the
- // thread running the job)
- @Override
- public synchronized void setCancelListener(CancelListener listener) {
- mCancelListener = listener;
- if (mIsCancelled && mCancelListener != null) {
- mCancelListener.onCancel();
- }
- }
-
- @Override
- public boolean setMode(int mode) {
- // Release old resource
- ResourceCounter rc = modeToCounter(mMode);
- if (rc != null) releaseResource(rc);
- mMode = MODE_NONE;
-
- // Acquire new resource
- rc = modeToCounter(mode);
- if (rc != null) {
- if (!acquireResource(rc)) {
- return false;
- }
- mMode = mode;
- }
-
- return true;
- }
-
- private ResourceCounter modeToCounter(int mode) {
- if (mode == MODE_CPU) {
- return mCpuCounter;
- } else if (mode == MODE_NETWORK) {
- return mNetworkCounter;
- } else {
- return null;
- }
- }
-
- private boolean acquireResource(ResourceCounter counter) {
- while (true) {
- synchronized (this) {
- if (mIsCancelled) {
- mWaitOnResource = null;
- return false;
- }
- mWaitOnResource = counter;
- }
-
- synchronized (counter) {
- if (counter.value > 0) {
- counter.value--;
- break;
- } else {
- try {
- counter.wait();
- } catch (InterruptedException ex) {
- // ignore.
- }
- }
- }
- }
-
- synchronized (this) {
- mWaitOnResource = null;
- }
-
- return true;
- }
-
- private void releaseResource(ResourceCounter counter) {
- synchronized (counter) {
- counter.value++;
- counter.notifyAll();
- }
- }
- }
-}
+/*
+ * Copyright (C) 2010 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.hippo.ehviewer.util;
+
+import com.hippo.ehviewer.util.Log;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPool {
+ @SuppressWarnings("unused")
+ private static final String TAG = "ThreadPool";
+ private static final int CORE_POOL_SIZE = 4;
+ private static final int MAX_POOL_SIZE = 8;
+ private static final int KEEP_ALIVE_TIME = 10; // 10 seconds
+
+ // Resource type
+ public static final int MODE_NONE = 0;
+ public static final int MODE_CPU = 1;
+ public static final int MODE_NETWORK = 2;
+
+ public static final JobContext JOB_CONTEXT_STUB = new JobContextStub();
+
+ ResourceCounter mCpuCounter = new ResourceCounter(2);
+ ResourceCounter mNetworkCounter = new ResourceCounter(2);
+
+ // A Job is like a Callable, but it has an addition JobContext parameter.
+ public interface Job {
+ public T run(JobContext jc);
+ }
+
+ public interface JobContext {
+ boolean isCancelled();
+ void setCancelListener(CancelListener listener);
+ boolean setMode(int mode);
+ }
+
+ private static class JobContextStub implements JobContext {
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void setCancelListener(CancelListener listener) {
+ }
+
+ @Override
+ public boolean setMode(int mode) {
+ return true;
+ }
+ }
+
+ public interface CancelListener {
+ public void onCancel();
+ }
+
+ private static class ResourceCounter {
+ public int value;
+ public ResourceCounter(int v) {
+ value = v;
+ }
+ }
+
+ private final Executor mExecutor;
+
+ public ThreadPool() {
+ this(CORE_POOL_SIZE, MAX_POOL_SIZE);
+ }
+
+ public ThreadPool(int initPoolSize, int maxPoolSize) {
+ mExecutor = new ThreadPoolExecutor(
+ initPoolSize, maxPoolSize, KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS, new LinkedBlockingQueue(),
+ new PriorityThreadFactory("thread-pool",
+ android.os.Process.THREAD_PRIORITY_DEFAULT));
+ }
+
+ // Submit a job to the thread pool. The listener will be called when the
+ // job is finished (or cancelled).
+ public Future submit(Job job, FutureListener listener) {
+ Worker w = new Worker(job, listener);
+ mExecutor.execute(w);
+ return w;
+ }
+
+ public Future submit(Job job) {
+ return submit(job, null);
+ }
+
+ private class Worker implements Runnable, Future, JobContext {
+ @SuppressWarnings("hiding")
+ private static final String TAG = "Worker";
+ private Job mJob;
+ private FutureListener mListener;
+ private CancelListener mCancelListener;
+ private ResourceCounter mWaitOnResource;
+ private volatile boolean mIsCancelled;
+ private boolean mIsDone;
+ private T mResult;
+ private int mMode;
+
+ public Worker(Job job, FutureListener listener) {
+ mJob = job;
+ mListener = listener;
+ }
+
+ // This is called by a thread in the thread pool.
+ @Override
+ public void run() {
+ T result = null;
+
+ // A job is in CPU mode by default. setMode returns false
+ // if the job is cancelled.
+ if (setMode(MODE_CPU)) {
+ try {
+ result = mJob.run(this);
+ } catch (Throwable ex) {
+ Log.w(TAG, "Exception in running a job", ex);
+ }
+ }
+
+ synchronized(this) {
+ setMode(MODE_NONE);
+ mResult = result;
+ mIsDone = true;
+ notifyAll();
+ }
+ if (mListener != null) mListener.onFutureDone(this);
+ }
+
+ // Below are the methods for Future.
+ @Override
+ public synchronized void cancel() {
+ if (mIsCancelled) return;
+ mIsCancelled = true;
+ if (mWaitOnResource != null) {
+ synchronized (mWaitOnResource) {
+ mWaitOnResource.notifyAll();
+ }
+ }
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mIsCancelled;
+ }
+
+ @Override
+ public synchronized boolean isDone() {
+ return mIsDone;
+ }
+
+ @Override
+ public synchronized T get() {
+ while (!mIsDone) {
+ try {
+ wait();
+ } catch (Exception ex) {
+ Log.w(TAG, "ingore exception", ex);
+ // ignore.
+ }
+ }
+ return mResult;
+ }
+
+ @Override
+ public void waitDone() {
+ get();
+ }
+
+ // Below are the methods for JobContext (only called from the
+ // thread running the job)
+ @Override
+ public synchronized void setCancelListener(CancelListener listener) {
+ mCancelListener = listener;
+ if (mIsCancelled && mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
+ }
+
+ @Override
+ public boolean setMode(int mode) {
+ // Release old resource
+ ResourceCounter rc = modeToCounter(mMode);
+ if (rc != null) releaseResource(rc);
+ mMode = MODE_NONE;
+
+ // Acquire new resource
+ rc = modeToCounter(mode);
+ if (rc != null) {
+ if (!acquireResource(rc)) {
+ return false;
+ }
+ mMode = mode;
+ }
+
+ return true;
+ }
+
+ private ResourceCounter modeToCounter(int mode) {
+ if (mode == MODE_CPU) {
+ return mCpuCounter;
+ } else if (mode == MODE_NETWORK) {
+ return mNetworkCounter;
+ } else {
+ return null;
+ }
+ }
+
+ private boolean acquireResource(ResourceCounter counter) {
+ while (true) {
+ synchronized (this) {
+ if (mIsCancelled) {
+ mWaitOnResource = null;
+ return false;
+ }
+ mWaitOnResource = counter;
+ }
+
+ synchronized (counter) {
+ if (counter.value > 0) {
+ counter.value--;
+ break;
+ } else {
+ try {
+ counter.wait();
+ } catch (InterruptedException ex) {
+ // ignore.
+ }
+ }
+ }
+ }
+
+ synchronized (this) {
+ mWaitOnResource = null;
+ }
+
+ return true;
+ }
+
+ private void releaseResource(ResourceCounter counter) {
+ synchronized (counter) {
+ counter.value++;
+ counter.notifyAll();
+ }
+ }
+ }
+}
diff --git a/src/com/hippo/ehviewer/util/TimeRunner.java b/src/com/hippo/ehviewer/util/TimeRunner.java
index 9ded6f4a1..c61f030fb 100644
--- a/src/com/hippo/ehviewer/util/TimeRunner.java
+++ b/src/com/hippo/ehviewer/util/TimeRunner.java
@@ -1,119 +1,119 @@
-package com.hippo.ehviewer.util;
-
-import android.annotation.SuppressLint;
-import android.os.Handler;
-import android.os.Message;
-
-public abstract class TimeRunner {
-
- private static final int INTERVAL = 5;
-
- private static final int RUNNING = 0;
- private static final int START = 1;
- private static final int END = 2;
-
- private boolean mRunInThread = true;
- private boolean mListenerInThread = true;
-
- private OnTimeListener mListener;
-
- public interface OnTimeListener {
- public void onStart();
- public void onEnd();
- }
-
- // TODO
- @SuppressLint("HandlerLeak")
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case RUNNING:
- run((Float)msg.obj, msg.arg1);
- break;
- case START:
- mListener.onStart();
- break;
- case END:
- mListener.onEnd();
- break;
- }
- }
- };
-
- private int mDuration = 0;
- private int mDelay = 0;
-
- public void setOnTimerListener(OnTimeListener listener) {
- mListener = listener;
- }
-
- public void setDuration(int duration) {
- mDuration = duration;
- }
-
- public void setRunInThread(boolean runInThread) {
- mRunInThread = runInThread;
- }
-
- public void setListenerInThread(boolean listenerInThread) {
- mListenerInThread = listenerInThread;
- }
-
- public void start() {
- start(0);
- }
-
- public void start(int delay) {
- mDelay = delay;
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(mDelay);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (mListener != null) {
- if (mListenerInThread) {
- mListener.onStart();
- } else {
- Message msg = new Message();
- msg.what = START;
- mHandler.sendMessage(msg);
- }
- }
-
- long startTime = System.currentTimeMillis();
- for (int runningTime = 0; runningTime < mDuration; runningTime = (int)(System.currentTimeMillis() - startTime)) {
- if (mRunInThread) {
- TimeRunner.this.run(runningTime/(float)mDuration, runningTime);
- } else {
- Message msg = new Message();
- msg.what = RUNNING;
- msg.obj = runningTime/(float)mDuration;
- msg.arg1 = runningTime;
- mHandler.sendMessage(msg);
- }
- try {
- Thread.sleep(INTERVAL);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- if (mListener != null) {
- if (mListenerInThread) {
- mListener.onEnd();
- } else {
- Message msg = new Message();
- msg.what = END;
- mHandler.sendMessage(msg);
- }
- }
- }
- }).start();
- }
-
- protected abstract void run(float interpolatedTime, int runningTime);
-}
+package com.hippo.ehviewer.util;
+
+import android.annotation.SuppressLint;
+import android.os.Handler;
+import android.os.Message;
+
+public abstract class TimeRunner {
+
+ private static final int INTERVAL = 5;
+
+ private static final int RUNNING = 0;
+ private static final int START = 1;
+ private static final int END = 2;
+
+ private boolean mRunInThread = true;
+ private boolean mListenerInThread = true;
+
+ private OnTimeListener mListener;
+
+ public interface OnTimeListener {
+ public void onStart();
+ public void onEnd();
+ }
+
+ // TODO
+ @SuppressLint("HandlerLeak")
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case RUNNING:
+ run((Float)msg.obj, msg.arg1);
+ break;
+ case START:
+ mListener.onStart();
+ break;
+ case END:
+ mListener.onEnd();
+ break;
+ }
+ }
+ };
+
+ private int mDuration = 0;
+ private int mDelay = 0;
+
+ public void setOnTimerListener(OnTimeListener listener) {
+ mListener = listener;
+ }
+
+ public void setDuration(int duration) {
+ mDuration = duration;
+ }
+
+ public void setRunInThread(boolean runInThread) {
+ mRunInThread = runInThread;
+ }
+
+ public void setListenerInThread(boolean listenerInThread) {
+ mListenerInThread = listenerInThread;
+ }
+
+ public void start() {
+ start(0);
+ }
+
+ public void start(int delay) {
+ mDelay = delay;
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(mDelay);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if (mListener != null) {
+ if (mListenerInThread) {
+ mListener.onStart();
+ } else {
+ Message msg = new Message();
+ msg.what = START;
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ long startTime = System.currentTimeMillis();
+ for (int runningTime = 0; runningTime < mDuration; runningTime = (int)(System.currentTimeMillis() - startTime)) {
+ if (mRunInThread) {
+ TimeRunner.this.run(runningTime/(float)mDuration, runningTime);
+ } else {
+ Message msg = new Message();
+ msg.what = RUNNING;
+ msg.obj = runningTime/(float)mDuration;
+ msg.arg1 = runningTime;
+ mHandler.sendMessage(msg);
+ }
+ try {
+ Thread.sleep(INTERVAL);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (mListener != null) {
+ if (mListenerInThread) {
+ mListener.onEnd();
+ } else {
+ Message msg = new Message();
+ msg.what = END;
+ mHandler.sendMessage(msg);
+ }
+ }
+ }
+ }).start();
+ }
+
+ protected abstract void run(float interpolatedTime, int runningTime);
+}
diff --git a/src/com/hippo/ehviewer/widget/HfListView.java b/src/com/hippo/ehviewer/widget/HfListView.java
index b284bd80d..d0127d8f5 100644
--- a/src/com/hippo/ehviewer/widget/HfListView.java
+++ b/src/com/hippo/ehviewer/widget/HfListView.java
@@ -1,201 +1,201 @@
-package com.hippo.ehviewer.widget;
-
-import com.hippo.ehviewer.R;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-/**
- * TODO disable pull refreshing when footer refreshing
- *
- * @author Hippo
- *
- */
-public class HfListView extends SwipeRefreshLayout
- implements AbsListView.OnScrollListener {
-
- private final static int FOOTER_REFRESHING = 0;
- private final static int FOOTER_SUCCESS = 1;
- private final static int FOOTER_FAIL = 2;
-
- private Context mContext;
- private FswListView mListView;
-
- private View mFooter;
- private TextView mFooterTipTextView;
- private ProgressBar mFooterProgressBar;
- private int footerState = FOOTER_SUCCESS;
- private OnFooterRefreshListener mFooterRefreshListener;
-
- private OnFitSystemWindowsListener mListener;
-
- // Footer String to show
- private String mFooterRefreshStr;
- private String mFooterSuccessStr;
- private String mFooterFailStr;
-
- public HfListView(Context context) {
- super(context);
- mContext = context;
- init();
- }
-
- public HfListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- init();
- }
-
- private void init() {
- mListView = new FswListView(mContext);
- ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- addView(mListView, lp);
-
- mListView.setOnScrollListener(this);
- mFooter = (LinearLayout)LayoutInflater.from(mContext)
- .inflate(R.layout.pull_list_view_footer, null);
- mFooterTipTextView = (TextView)mFooter.findViewById(R.id.footer_tip_text);
- mFooterProgressBar = (ProgressBar)mFooter.findViewById(R.id.footer_progressBar);
- mFooter.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!isAnyRefreshing() && footerState == FOOTER_FAIL)
- footerRefresh();
- }
- });
- changeFooterViewByState();
- mListView.addFooterView(mFooter);
- }
-
- public FswListView getListView() {
- return mListView;
- }
-
- public void setOnFooterRefreshListener(OnFooterRefreshListener l) {
- mFooterRefreshListener = l;
- }
-
- private void footerRefresh() {
- if (mFooterRefreshListener.onFooterRefresh()) {
- footerState = FOOTER_REFRESHING;
- changeFooterViewByState();
- }
- }
-
- /**
- * Set footer UI String
- *
- * @param refreshStr
- * @param successStr
- * @param failStr
- */
- public void setFooterString(String refreshStr, String successStr, String failStr) {
- mFooterRefreshStr = refreshStr;
- mFooterSuccessStr = successStr;
- mFooterFailStr = failStr;
-
- switch (footerState) {
- case FOOTER_REFRESHING:
- mFooterTipTextView.setText(mFooterRefreshStr);
- break;
- case FOOTER_SUCCESS:
- mFooterTipTextView.setText(mFooterSuccessStr);
- break;
- case FOOTER_FAIL:
- mFooterTipTextView.setText(mFooterFailStr);
- break;
- }
- }
-
- /**
- * Refresh footer UI
- */
- private void changeFooterViewByState() {
- switch (footerState) {
- case FOOTER_REFRESHING:
- mFooterTipTextView.setText(mFooterRefreshStr);
- mFooterProgressBar.setVisibility(View.VISIBLE);
- break;
- case FOOTER_SUCCESS:
- mFooterTipTextView.setText(mFooterSuccessStr);
- mFooterProgressBar.setVisibility(View.GONE);
- break;
- case FOOTER_FAIL:
- mFooterTipTextView.setText(mFooterFailStr);
- mFooterProgressBar.setVisibility(View.GONE);
- break;
- }
- }
-
- public void setHeaderRefreshComplete() {
- super.setRefreshing(false);
- }
-
- public void setFooterRefreshComplete(boolean isSuccess) {
- if (isSuccess)
- footerState = FOOTER_SUCCESS;
- else
- footerState = FOOTER_FAIL;
- changeFooterViewByState();
- }
-
- public void setAnyRefreshComplete() {
- setAnyRefreshComplete(true);
- }
-
- public void setAnyRefreshComplete(boolean isSuccess) {
- setHeaderRefreshComplete();
- setFooterRefreshComplete(isSuccess);
- }
-
- /**
- * @return True if actionbar is refreshing
- */
- public boolean isHeaderRefreshing() {
- return super.isRefreshing();
- }
-
- /**
- * @return True if footer is refreshing
- */
- public boolean isFooterRefreshing() {
- return footerState == FOOTER_REFRESHING;
- }
-
- /**
- * @return True if actionbar or footer is refreshing
- */
- public boolean isAnyRefreshing() {
- return isHeaderRefreshing() | isFooterRefreshing();
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {}
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- if (firstVisibleItem + visibleItemCount == totalItemCount
- && !isAnyRefreshing() && totalItemCount > 1
- && footerState != FOOTER_FAIL)
- footerRefresh();
- }
-
- public interface OnFooterRefreshListener {
- /**
- * @return True if this refresh action is vaild
- */
- boolean onFooterRefresh();
- }
-}
+package com.hippo.ehviewer.widget;
+
+import com.hippo.ehviewer.R;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * TODO disable pull refreshing when footer refreshing
+ *
+ * @author Hippo
+ *
+ */
+public class HfListView extends SwipeRefreshLayout
+ implements AbsListView.OnScrollListener {
+
+ private final static int FOOTER_REFRESHING = 0;
+ private final static int FOOTER_SUCCESS = 1;
+ private final static int FOOTER_FAIL = 2;
+
+ private Context mContext;
+ private FswListView mListView;
+
+ private View mFooter;
+ private TextView mFooterTipTextView;
+ private ProgressBar mFooterProgressBar;
+ private int footerState = FOOTER_SUCCESS;
+ private OnFooterRefreshListener mFooterRefreshListener;
+
+ private OnFitSystemWindowsListener mListener;
+
+ // Footer String to show
+ private String mFooterRefreshStr;
+ private String mFooterSuccessStr;
+ private String mFooterFailStr;
+
+ public HfListView(Context context) {
+ super(context);
+ mContext = context;
+ init();
+ }
+
+ public HfListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ init();
+ }
+
+ private void init() {
+ mListView = new FswListView(mContext);
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ addView(mListView, lp);
+
+ mListView.setOnScrollListener(this);
+ mFooter = (LinearLayout)LayoutInflater.from(mContext)
+ .inflate(R.layout.pull_list_view_footer, null);
+ mFooterTipTextView = (TextView)mFooter.findViewById(R.id.footer_tip_text);
+ mFooterProgressBar = (ProgressBar)mFooter.findViewById(R.id.footer_progressBar);
+ mFooter.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isAnyRefreshing() && footerState == FOOTER_FAIL)
+ footerRefresh();
+ }
+ });
+ changeFooterViewByState();
+ mListView.addFooterView(mFooter);
+ }
+
+ public FswListView getListView() {
+ return mListView;
+ }
+
+ public void setOnFooterRefreshListener(OnFooterRefreshListener l) {
+ mFooterRefreshListener = l;
+ }
+
+ private void footerRefresh() {
+ if (mFooterRefreshListener.onFooterRefresh()) {
+ footerState = FOOTER_REFRESHING;
+ changeFooterViewByState();
+ }
+ }
+
+ /**
+ * Set footer UI String
+ *
+ * @param refreshStr
+ * @param successStr
+ * @param failStr
+ */
+ public void setFooterString(String refreshStr, String successStr, String failStr) {
+ mFooterRefreshStr = refreshStr;
+ mFooterSuccessStr = successStr;
+ mFooterFailStr = failStr;
+
+ switch (footerState) {
+ case FOOTER_REFRESHING:
+ mFooterTipTextView.setText(mFooterRefreshStr);
+ break;
+ case FOOTER_SUCCESS:
+ mFooterTipTextView.setText(mFooterSuccessStr);
+ break;
+ case FOOTER_FAIL:
+ mFooterTipTextView.setText(mFooterFailStr);
+ break;
+ }
+ }
+
+ /**
+ * Refresh footer UI
+ */
+ private void changeFooterViewByState() {
+ switch (footerState) {
+ case FOOTER_REFRESHING:
+ mFooterTipTextView.setText(mFooterRefreshStr);
+ mFooterProgressBar.setVisibility(View.VISIBLE);
+ break;
+ case FOOTER_SUCCESS:
+ mFooterTipTextView.setText(mFooterSuccessStr);
+ mFooterProgressBar.setVisibility(View.GONE);
+ break;
+ case FOOTER_FAIL:
+ mFooterTipTextView.setText(mFooterFailStr);
+ mFooterProgressBar.setVisibility(View.GONE);
+ break;
+ }
+ }
+
+ public void setHeaderRefreshComplete() {
+ super.setRefreshing(false);
+ }
+
+ public void setFooterRefreshComplete(boolean isSuccess) {
+ if (isSuccess)
+ footerState = FOOTER_SUCCESS;
+ else
+ footerState = FOOTER_FAIL;
+ changeFooterViewByState();
+ }
+
+ public void setAnyRefreshComplete() {
+ setAnyRefreshComplete(true);
+ }
+
+ public void setAnyRefreshComplete(boolean isSuccess) {
+ setHeaderRefreshComplete();
+ setFooterRefreshComplete(isSuccess);
+ }
+
+ /**
+ * @return True if actionbar is refreshing
+ */
+ public boolean isHeaderRefreshing() {
+ return super.isRefreshing();
+ }
+
+ /**
+ * @return True if footer is refreshing
+ */
+ public boolean isFooterRefreshing() {
+ return footerState == FOOTER_REFRESHING;
+ }
+
+ /**
+ * @return True if actionbar or footer is refreshing
+ */
+ public boolean isAnyRefreshing() {
+ return isHeaderRefreshing() | isFooterRefreshing();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {}
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem,
+ int visibleItemCount, int totalItemCount) {
+ if (firstVisibleItem + visibleItemCount == totalItemCount
+ && !isAnyRefreshing() && totalItemCount > 1
+ && footerState != FOOTER_FAIL)
+ footerRefresh();
+ }
+
+ public interface OnFooterRefreshListener {
+ /**
+ * @return True if this refresh action is vaild
+ */
+ boolean onFooterRefresh();
+ }
+}