diff options
Diffstat (limited to 'src/org/cyanogenmod')
50 files changed, 8265 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java b/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java new file mode 100644 index 0000000..a7bcd59 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.view.ViewConfiguration; + +/** + * A helper class with useful methods for deal with android. + */ +public final class AndroidHelper { + + /** + * Method that returns if the device is a tablet + * + * @param ctx The current context + * @return boolean If device is a table + */ + public static boolean isTablet(Context ctx) { + Configuration configuration = ctx.getResources().getConfiguration(); + return (configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) + >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } + + /** + * Method that returns if an option menu has to be displayed + * + * @param ctx The current context + * @return boolean If an option menu has to be displayed + */ + public static boolean showOptionsMenu(Context ctx) { + // Show overflow button? + return !ViewConfiguration.get(ctx).hasPermanentMenuKey(); + } + + /** + * This method converts dp unit to equivalent device specific value in pixels. + * + * @param ctx The current context + * @param dp A value in dp (Device independent pixels) unit + * @return float A float value to represent Pixels equivalent to dp according to device + */ + public static float convertDpToPixel(Context ctx, float dp) { + Resources resources = ctx.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * (metrics.densityDpi / 160f); + } + + /** + * This method converts device specific pixels to device independent pixels. + * + * @param ctx The current context + * @param px A value in px (pixels) unit + * @return A float value to represent dp equivalent to px value + */ + public static float convertPixelsToDp(Context ctx, float px) { + Resources resources = ctx.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return px / (metrics.densityDpi / 160f); + } + + /** + * Calculate the dimension of the status bar + * + * @param context The current context + * @return The height of the status bar + */ + public static int calculateStatusBarHeight(Context context) { + int result = 0; + if (!(context instanceof Activity)) { + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + } + return result; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java b/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java new file mode 100644 index 0000000..efbd45d --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.graphics.Matrix; +import android.media.ExifInterface; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * A helper class for deal with Bitmaps + */ +public class BitmapUtils { + + /** + * Method that decodes a bitmap + * + * @param bitmap The bitmap buffer to decode + * @return Bitmap The decoded bitmap + */ + public static Bitmap decodeBitmap(InputStream bitmap) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferQualityOverSpeed = false; + options.inPreferredConfig = Bitmap.Config.RGB_565; + return BitmapFactory.decodeStream(bitmap, null, options); + } + + /** + * Method that decodes a bitmap + * + * @param file The bitmap file to decode + * @param reqWidth The request width + * @param reqHeight The request height + * @return Bitmap The decoded bitmap + */ + public static Bitmap decodeBitmap(File file, int reqWidth, int reqHeight) { + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inPreferQualityOverSpeed = false; + options.inPreferredConfig = Bitmap.Config.RGB_565; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + + // Calculate inSampleSize + options.inSampleSize = calculateBitmapRatio(options, reqWidth, reqHeight); + + // Decode the bitmap with inSampleSize set + options.inJustDecodeBounds = false; + Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + if (bitmap == null) { + return null; + } + + //Test if the file has exif format + return decodeExifBitmap(file, bitmap); + } + + /** + * Method that decodes an Exif bitmap + * + * @param file The file to decode + * @param bitmap The bitmap reference + * @return Bitmap The decoded bitmap + */ + private static Bitmap decodeExifBitmap(File file, Bitmap bitmap) { + try { + // Try to load the bitmap as a bitmap file + ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); + Matrix matrix = new Matrix(); + if (orientation == 6) { + matrix.postRotate(90); + } else if (orientation == 3) { + matrix.postRotate(180); + } else if (orientation == 8) { + matrix.postRotate(270); + } + // Rotate the bitmap + return Bitmap.createBitmap( + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } catch (IOException e) { + // Ignore + } + return bitmap; + } + + /** + * Method that calculate the bitmap size prior to decode + * + * @param options The bitmap factory options + * @param reqWidth The request width + * @param reqHeight The request height + * @return int The picture ratio + */ + private static int calculateBitmapRatio(Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + // Calculate ratios of height and width to requested height and width + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + + // Choose the smallest ratio as inSampleSize value, this will guarantee + // a final image with both dimensions larger than or equal to the + // requested height and width. + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + } + + return inSampleSize; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/Colors.java b/src/org/cyanogenmod/wallpapers/photophase/Colors.java new file mode 100644 index 0000000..3c3ffb9 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/Colors.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.content.res.Resources; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider; + +/** + * A class that defines some wallpaper GLColor colors. + */ +public class Colors { + + private static GLColor sBackground = new GLColor(0); + private static GLColor sOverlay = new GLColor(0); + + /** + * This method should be called on initialization for load the preferences color + */ + public static void register(Context ctx) { + Resources res = ctx.getResources(); + sBackground = PreferencesProvider.Preferences.General.getBackgroundColor(); + sOverlay = new GLColor(res.getColor(R.color.wallpaper_overlay_color)); + } + + public static GLColor getBackground() { + return sBackground; + } + + public static void setBackground(GLColor background) { + Colors.sBackground = background; + } + + public static GLColor getOverlay() { + return sOverlay; + } + + public static void setOverlay(GLColor overlay) { + Colors.sOverlay = overlay; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java new file mode 100644 index 0000000..9effe8c --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.service.wallpaper.WallpaperService; +import android.view.SurfaceHolder; + +/** + * An abstract class for using a {@link GLSurfaceView} inside a {@link WallpaperService}. + */ +public abstract class EGLWallpaperService extends WallpaperService { + + /** + * A listener interface for the {@link GLESWallpaperService.GLESEngine} engine class. + */ + public interface EGLEngineListener { + // No methods + } + + /** + * An EGL implementation of {@link android.service.wallpaper.WallpaperService.Engine} that + * uses {@link GLSurfaceView}. + */ + public class EGLEngine extends Engine { + /** + * The internal {@link GLSurfaceView}. + */ + class WallpaperGLSurfaceView extends GLSurfaceView { + + /** + * @see GLSurfaceView + */ + WallpaperGLSurfaceView(Context context) { + super(context); + } + + /** + * {@inheritDoc} + */ + @Override + public SurfaceHolder getHolder() { + return getSurfaceHolder(); + } + + /** + * Should be called when the {@link GLSurfaceView} is not needed anymore. + */ + public void onDestroy() { + super.onDetachedFromWindow(); + } + } + + private WallpaperGLSurfaceView mGlSurfaceView; + + /** + * Method that sets the EGL engine listener + * + * @param listener The EGL engine listener + */ + public void setEGLEngineListener(EGLEngineListener listener) { + // No methods + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + mGlSurfaceView = createWallpaperGLSurfaceView(); + } + + /** + * Method that returns the internal {@link GLSurfaceView} + * + * @return GLSurfaceView The internal {@link GLSurfaceView}. + */ + protected GLSurfaceView getGlSurfaceView() { + return mGlSurfaceView; + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + mGlSurfaceView.onDestroy(); + } + + /** + * Override this method if a {@link GLSurfaceView} wrapper is needed, for example + * if you need implements some eve + * + * @return WallpaperGLSurfaceView The specialized EGL {@link GLSurfaceView}. + */ + public WallpaperGLSurfaceView createWallpaperGLSurfaceView() { + return new WallpaperGLSurfaceView(EGLWallpaperService.this); + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java b/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java new file mode 100644 index 0000000..398785f --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that represent a FIFO queue with a fixed size. When the queue reach the maximum defined + * size then extract the next element from the queue. + * @param <T> The type of object to hold. + */ +@SuppressWarnings("unchecked") +public class FixedQueue<T> { + + /** + * An exception thrown when the queue hasn't more elements + */ + public static class EmptyQueueException extends Exception { + private static final long serialVersionUID = 1L; + } + + private final Object[] mQueue; + private final int mSize; + private int mHead; + private int mTail; + + /** + * Constructor of <code>FixedQueue</code> + * + * @param size The size of the queue. The limit of objects in queue. Beyond this limits + * the older objects are recycled. + */ + public FixedQueue(int size) { + super(); + this.mQueue = new Object[size]; + this.mSize = size; + this.mHead = 0; + this.mTail = 0; + } + + /** + * Method that inserts a new object to the queue. + * + * @param o The object to insert + * @return The object inserted (for concatenation purpose) + */ + public T insert(T o) { + synchronized (this.mQueue) { + if (o == null) throw new NullPointerException(); + if (this.mQueue[this.mHead] != null) { + try { + noSynchronizedRemove(); + } catch (Throwable ex) {/**NON BLOCK**/} + } + this.mQueue[this.mHead] = o; + this.mHead++; + if (this.mHead >= this.mSize) { + this.mHead = 0; + } + return o; + } + } + + /** + * Method that extract the first element in the queue + * + * @return The item extracted + * @throws EmptyQueueException If the queue hasn't element + */ + public T remove() throws EmptyQueueException { + synchronized (this.mQueue) { + return noSynchronizedRemove(); + } + } + + /** + * Method that extract all the items from the queue + * + * @return The items extracted + * @throws EmptyQueueException If the queue hasn't element + */ + public List<T> removeAll() throws EmptyQueueException { + synchronized (this.mQueue) { + if (isEmpty()) throw new EmptyQueueException(); + List<T> l = new ArrayList<T>(); + while (!isEmpty()) { + l.add(noSynchronizedRemove()); + } + return l; + } + } + + /** + * Method that retrieves the first element in the queue. This method doesn't remove the item + * from queue. + * + * @return The item retrieved + * @throws EmptyQueueException If the queue hasn't element + */ + public T peek() throws EmptyQueueException { + synchronized (this.mQueue) { + T o = (T)this.mQueue[this.mTail]; + if (o == null) throw new EmptyQueueException(); + return o; + } + + } + + /** + * Method that retrieves all the items from the queue. This method doesn't remove any item + * from queue. + * + * @return The items retrieved + * @throws EmptyQueueException If the queue hasn't element + */ + public List<T> peekAll() throws EmptyQueueException { + synchronized (this.mQueue) { + if (isEmpty()) throw new EmptyQueueException(); + List<T> l = new ArrayList<T>(); + int head = this.mHead; + int tail = this.mTail; + do { + l.add((T)this.mQueue[tail]); + tail++; + if (tail >= this.mSize) { + tail = 0; + } + } while (head != tail); + return l; + } + } + + /** + * Method that returns if the queue is empty + * + * @return boolean If the queue is empty + */ + public boolean isEmpty() { + synchronized (this.mQueue) { + return this.mQueue[this.mTail] == null; + } + } + + /** + * Method that returns the number of items in the queue + * + * @return int The number of items + */ + public int items() { + int cc = 0; + int head = this.mHead; + int tail = this.mTail; + do { + if (this.mQueue[tail] == null) { + return cc; + } + cc++; + tail++; + if (tail >= this.mSize) { + tail = 0; + } + } while (head != tail); + return cc; + } + + /** + * Method that remove one item without synchronization (for be called from + * synchronized method). + * + * @return The item extracted + * @throws EmptyQueueException If the queue hasn't element + */ + private T noSynchronizedRemove() throws EmptyQueueException { + T o = (T)this.mQueue[this.mTail]; + if (o == null) throw new EmptyQueueException(); + this.mQueue[this.mTail] = null; + this.mTail++; + if (this.mTail >= this.mSize) { + this.mTail = 0; + } + return o; + } + + /** + * Method that returns the size of this queue. + * + * @return The size of this queue + */ + public int size() { + return mSize; + } +}
\ No newline at end of file diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java new file mode 100644 index 0000000..fdd8b5b --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + + + + +/** + * An abstract implementation of {@link EGLWallpaperService} based on <code>GLES</code>. + */ +public abstract class GLES20WallpaperService extends GLESWallpaperService { + + /** + * {@inheritDoc} + */ + @Override + public Engine onCreateEngine() { + return new GLES20Engine(); + } + + /** + * A class that extends the {@link GLESWallpaperService.GLESEngine} to add support for + * <code>GLES20</code>. + */ + class GLES20Engine extends GLESWallpaperService.GLESEngine { + /** + * {@inheritDoc} + */ + @Override + void initialize() { + // Request an OpenGL ES 2.x compatible context. + getGlSurfaceView().setEGLContextClientVersion(2); + getGlSurfaceView().setEGLConfigChooser(false); + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java b/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java new file mode 100644 index 0000000..7634a63 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.opengl.GLSurfaceView; + +/** + * A class responsible of dispatch GLES commands inside the main GLThread. + */ +public class GLESSurfaceDispatcher { + + private final GLSurfaceView mSurface; + + /** + * Constructor of <code>GLESSurfaceDispatcher</code> + * + * @param v The associated GLES surface view + */ + public GLESSurfaceDispatcher(GLSurfaceView v) { + super(); + mSurface = v; + } + + /** + * Method that dispatch a GLES commands inside the main GLThread. + * + * @param r The runnable that execute the GLES commands + */ + public void dispatch(Runnable r) { + this.mSurface.queueEvent(r); + } + + /** + * Method that set the render mode + * + * @param mode The GLES render mode + */ + public void setRenderMode(int mode) { + if (mSurface.getRenderMode() != mode) { + mSurface.setRenderMode(mode); + } + } + + /** + * Method that requests a render to the surface. + */ + public void requestRender() { + mSurface.requestRender(); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java b/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java new file mode 100644 index 0000000..a24e585 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Rect; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.effects.Effect; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + + +/** + * A helper class with some useful methods for deal with GLES. + */ +public final class GLESUtil { + + private static final String TAG = "GLESUtil"; + + private static final boolean DEBUG = true; + + /** + * A helper class to deal with OpenGL float colors. + */ + public static class GLColor { + + private static final float MAX_COLOR = 255.0f; + + /** + * Red + */ + public float r; + /** + * Green + */ + public float g; + /** + * Blue + */ + public float b; + /** + * Alpha + */ + public float a; + + /** + * Constructor of <code>GLColor</code> from ARGB + * + * @param a Alpha + * @param r Red + * @param g Green + * @param b Alpha + */ + public GLColor(int a, int r, int g, int b) { + this.a = a / MAX_COLOR; + this.r = r / MAX_COLOR; + this.g = g / MAX_COLOR; + this.b = b / MAX_COLOR; + } + + /** + * Constructor of <code>GLColor</code> from ARGB. + * + * @param argb An #AARRGGBB string + */ + public GLColor(String argb) { + int color = Color.parseColor(argb); + this.a = Color.alpha(color) / MAX_COLOR; + this.r = Color.red(color) / MAX_COLOR; + this.g = Color.green(color) / MAX_COLOR; + this.b = Color.blue(color) / MAX_COLOR; + } + + /** + * Constructor of <code>GLColor</code> from ARGB. + * + * @param argb An #AARRGGBB string + */ + public GLColor(int argb) { + this.a = Color.alpha(argb) / MAX_COLOR; + this.r = Color.red(argb) / MAX_COLOR; + this.g = Color.green(argb) / MAX_COLOR; + this.b = Color.blue(argb) / MAX_COLOR; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(a); + result = prime * result + Float.floatToIntBits(b); + result = prime * result + Float.floatToIntBits(g); + result = prime * result + Float.floatToIntBits(r); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GLColor other = (GLColor) obj; + if (Float.floatToIntBits(a) != Float.floatToIntBits(other.a)) + return false; + if (Float.floatToIntBits(b) != Float.floatToIntBits(other.b)) + return false; + if (Float.floatToIntBits(g) != Float.floatToIntBits(other.g)) + return false; + if (Float.floatToIntBits(r) != Float.floatToIntBits(other.r)) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "#"+Integer.toHexString(Color.argb((int)a, (int)r, (int)g, (int)b)); + } + } + + /** + * Class that holds some information about a GLES texture + */ + public static class GLESTextureInfo { + /** + * Handle of the texture + */ + public int handle = 0; + /** + * The bitmap reference + */ + public Bitmap bitmap; + } + + /** + * Method that load a vertex shader and returns its handler identifier. + * + * @param src The source shader + * @return int The handler identifier of the shader + */ + public static int loadVertexShader(String src) { + return loadShader(src, GLES20.GL_VERTEX_SHADER); + } + + /** + * Method that load a fragment shader and returns its handler identifier. + * + * @param src The source shader + * @return int The handler identifier of the shader + */ + public static int loadFragmentShader(String src) { + return loadShader(src, GLES20.GL_FRAGMENT_SHADER); + } + + /** + * Method that load a shader and returns its handler identifier. + * + * @param src The source shader + * @param type The type of shader + * @return int The handler identifier of the shader + */ + public static int loadShader(String src, int type) { + int[] compiled = new int[1]; + // Create, load and compile the shader + int shader = GLES20.glCreateShader(type); + GLESUtil.glesCheckError("glCreateShader"); + if (shader <= 0) { + String msg = "Cannot create a shader"; + if (DEBUG) Log.e(TAG, msg); + return 0; + } + GLES20.glShaderSource(shader, src); + GLESUtil.glesCheckError("glShaderSource"); + GLES20.glCompileShader(shader); + GLESUtil.glesCheckError("glesCheckError"); + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + GLESUtil.glesCheckError("glesCheckError"); + if (compiled[0] <= 0) { + String msg = "Shader compilation error trace:\n" + GLES20.glGetShaderInfoLog(shader); + Log.e(TAG, msg); + return 0; + } + return shader; + } + + /** + * Method that create a new program from its shaders (vertex and fragment) + * + * @param res A resources reference + * @param vertexShaderId The vertex shader glsl resource + * @param fragmentShaderId The fragment shader glsl resource + * @return int The handler identifier of the program + */ + public static int createProgram(Resources res, int vertexShaderId, int fragmentShaderId) { + return createProgram( + readResource(res, vertexShaderId), + readResource(res, fragmentShaderId)); + } + + /** + * Method that create a new program from its shaders (vertex and fragment) + * + * @param vertexShaderSrc The vertex shader + * @param fragmentShaderSrc The fragment shader + * @return int The handler identifier of the program. + */ + public static int createProgram(String vertexShaderSrc, String fragmentShaderSrc) { + int vshader = 0; + int fshader = 0; + int progid = 0; + int[] link = new int[1]; + + try { + // Check that we have valid shaders + if (vertexShaderSrc == null || fragmentShaderSrc == null) { + return 0; + } + + // Load the vertex and fragment shaders + vshader = loadVertexShader(vertexShaderSrc); + fshader = loadFragmentShader(fragmentShaderSrc); + + // Create the programa ref + progid = GLES20.glCreateProgram(); + GLESUtil.glesCheckError("glCreateProgram"); + if (progid <= 0) { + String msg = "Cannot create a program"; + Log.e(TAG, msg); + return 0; + } + + // Attach the shaders + GLES20.glAttachShader(progid, vshader); + GLESUtil.glesCheckError("glAttachShader"); + GLES20.glAttachShader(progid, fshader); + GLESUtil.glesCheckError("glAttachShader"); + + // Link the program + GLES20.glLinkProgram(progid); + GLESUtil.glesCheckError("glLinkProgram"); + + GLES20.glGetProgramiv(progid, GLES20.GL_LINK_STATUS, link, 0); + GLESUtil.glesCheckError("glGetProgramiv"); + if (link[0] <= 0) { + String msg = "Program compilation error trace:\n" + GLES20.glGetProgramInfoLog(progid); + Log.e(TAG, msg); + return 0; + } + + // Return the program + return progid; + + } finally { + // Delete the shaders + if (vshader != 0) { + GLES20.glDeleteShader(vshader); + GLESUtil.glesCheckError("glDeleteShader"); + } + if (fshader != 0) { + GLES20.glDeleteShader(fshader); + GLESUtil.glesCheckError("glDeleteShader"); + } + } + } + + /** + * Method that loads a texture from a file. + * + * @param file The image file + * @param dimensions The desired dimensions + * @param effect The effect to apply to the image + * @param recycle If the bitmap should be recycled + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture( + File file, Rect dimensions, Effect effect, boolean recycle) { + Bitmap bitmap = null; + try { + // Decode and associate the bitmap (invert the desired dimensions) + bitmap = BitmapUtils.decodeBitmap(file, dimensions.height(), dimensions.width()); + if (bitmap == null) { + String msg = "Failed to decode the file bitmap"; + if (DEBUG) Log.e(TAG, msg); + return new GLESTextureInfo(); + } + bitmap = effect.apply(bitmap); + + if (DEBUG) Log.d(TAG, "image: " + file.getAbsolutePath()); + return loadTexture(bitmap); + + } catch (Exception e) { + String msg = "Failed to generate a valid texture from file: " + file.getAbsolutePath(); + if (DEBUG) Log.e(TAG, msg, e); + return new GLESTextureInfo(); + + } finally { + // Recycle the bitmap + if (bitmap != null && recycle) { + bitmap.recycle(); + } + } + } + + /** + * Method that loads a texture from a resource context. + * + * @param ctx The current context + * @param resourceId The resource identifier + * @param effect The effect to apply to the image + * @param recycle If the bitmap should be recycled + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture( + Context ctx, int resourceId, Effect effect, boolean recycle) { + Bitmap bitmap = null; + InputStream raw = null; + try { + // Decode and associate the bitmap + raw = ctx.getResources().openRawResource(resourceId); + bitmap = BitmapUtils.decodeBitmap(raw); + if (bitmap == null) { + String msg = "Failed to decode the resource bitmap"; + if (DEBUG) Log.e(TAG, msg); + return new GLESTextureInfo(); + } + + if (DEBUG) Log.d(TAG, "resourceId: " + resourceId); + return loadTexture(bitmap); + + } catch (Exception e) { + String msg = "Failed to generate a valid texture from resource: " + resourceId; + if (DEBUG) Log.e(TAG, msg, e); + return new GLESTextureInfo(); + + } finally { + // Close the buffer + try { + if (raw != null) { + raw.close(); + } + } catch (IOException e) { + // Ignore. + } + // Recycle the bitmap + if (bitmap != null && recycle) { + bitmap.recycle(); + } + } + } + + /** + * Method that loads texture from a bitmap reference. + * + * @param bitmap The bitmap reference + * @return GLESTextureInfo The texture info + */ + public static GLESTextureInfo loadTexture(Bitmap bitmap) { + // Check that we have a valid image name reference + if (bitmap == null) { + return new GLESTextureInfo(); + } + + int[] textureNames = new int[1]; + GLES20.glGenTextures(1, textureNames, 0); + GLESUtil.glesCheckError("glGenTextures"); + if (textureNames[0] <= 0) { + String msg = "Failed to generate a valid texture"; + if (DEBUG) Log.e(TAG, msg); + return new GLESTextureInfo(); + } + + // Bind the texture to the name + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureNames[0]); + GLESUtil.glesCheckError("glBindTexture"); + + // Set the texture properties + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLESUtil.glesCheckError("glTexParameteri"); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLESUtil.glesCheckError("glTexParameteri"); + + // Load the texture + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + if (!GLES20.glIsTexture(textureNames[0])) { + String msg = "Failed to load a valid texture"; + if (DEBUG) Log.e(TAG, msg); + return new GLESTextureInfo(); + } + + // Return the texture handle identifier and the associated info + GLESTextureInfo ti = new GLESTextureInfo(); + ti.handle = textureNames[0]; + ti.bitmap = bitmap; + return ti; + } + + /** + * Method that checks if an GLES error is present + * + * @param func The GLES function to check + * @return boolean If there was an error + */ + public static boolean glesCheckError(String func) { + int error = GLES20.glGetError(); + if (error != 0) { + if (DEBUG) { + Log.e(TAG, "GLES20 Error (" + glesGetErrorModule() + ") (" + func + "): " + + GLUtils.getEGLErrorString(error)); + } + return true; + } + return false; + } + + /** + * Method that returns the line and module that generates the current error + * + * @return String The line and module + */ + private static String glesGetErrorModule() { + try { + return String.valueOf(Thread.currentThread().getStackTrace()[4]); + } catch (IndexOutOfBoundsException ioobEx) { + // Ignore + } + return ""; + } + + /** + * Method that read a resource. + * + * @param res The resources reference + * @param resId The resource identifier + * @return String The shader source + * @throws IOException If an error occurs while loading the resource + */ + private static String readResource(Resources res, int resId) { + InputStream is = res.openRawResource(resId); + try { + final int BUFFER = 1024; + byte[] data = new byte[BUFFER]; + int read = 0; + StringBuilder sb = new StringBuilder(); + while ((read = is.read(data, 0, BUFFER)) != -1) { + sb.append(new String(data, 0 ,read)); + } + return sb.toString(); + } catch (Exception e) { + String msg = "Failed to read the resource " + resId; + if (DEBUG) Log.e(TAG, msg); + return null; + } finally { + try { + is.close(); + } catch (Exception ex) { + // Ignore + } + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java new file mode 100644 index 0000000..5c94519 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.opengl.GLSurfaceView; +import android.opengl.GLSurfaceView.Renderer; +import android.view.SurfaceHolder; + + +/** + * An abstract implementation of {@link EGLWallpaperService} based on <code>GLES</code>. + */ +public abstract class GLESWallpaperService extends EGLWallpaperService { + + /** + * A listener interface for the {@link GLESWallpaperService.GLESEngine} engine class. + */ + public interface GLESEngineListener extends EGLEngineListener { + /** + * Method invoked when the EGL surface is starting to initialize. + * + * @param view GLSurfaceView The EGL view + */ + void onInitializeEGLView(GLSurfaceView view); + + /** + * Method invoked when the EGL surface is recycled. + * + * @param view GLSurfaceView The EGL view + * @param renderer The renderer associated + */ + void onDestroyEGLView(GLSurfaceView view, Renderer renderer); + + /** + * Method invoked when the EGL surface was initialized. + * + * @param view GLSurfaceView The EGL view + */ + void onEGLViewInitialized(GLSurfaceView view); + + /** + * Method invoked when the EGL context is paused + * + * @param renderer The renderer associated + */ + void onPause(Renderer renderer); + + /** + * Method invoked when the EGL context is resumed + * + * @param renderer The renderer associated + */ + void onResume(Renderer renderer); + } + + /** + * {@inheritDoc} + */ + @Override + public Engine onCreateEngine() { + return new GLESEngine(); + } + + /** + * A class that extends the {@link EGLWallpaperService.EGLEngine} to add support for + * <code>GLES</code>. + */ + class GLESEngine extends EGLWallpaperService.EGLEngine { + + private GLESEngineListener mListener = null; + private WallpaperGLSurfaceView mWallpaperGLSurfaceView = null; + private Renderer mRenderer = null; + + private boolean mRendererHasBeenSet; + private boolean mPauseOnPreview; + + /** + * Method that sets the EGL engine listener + * + * @param listener The EGL engine listener + */ + public void setGLESEngineListener(GLESEngineListener listener) { + mListener = listener; + setEGLEngineListener(listener); + } + + /** + * Method that sets the {@link GLSurfaceView} to use. + * + * @param wallpaperGLSurfaceView A {@link GLSurfaceView} + */ + public void setWallpaperGLSurfaceView(WallpaperGLSurfaceView wallpaperGLSurfaceView) { + mWallpaperGLSurfaceView = wallpaperGLSurfaceView; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + + // Notify initialization + if (mListener != null) { + mListener.onInitializeEGLView(getGlSurfaceView()); + } + + // Initialize the GLES context + initialize(); + + // Set the renderer to our user-defined renderer. + mRenderer = getNewRenderer(getGlSurfaceView()); + getGlSurfaceView().setRenderer(mRenderer); + mRendererHasBeenSet = true; + + // Notify that the EGL is initialized + if (mListener != null) { + mListener.onEGLViewInitialized(getGlSurfaceView()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + + // Notify initialization + if (mListener != null) { + mListener.onDestroyEGLView(getGlSurfaceView(), mRenderer); + } + mRenderer = null; + } + + /** + * Method that initializes + */ + void initialize() { + // Request an OpenGL ES 1.x compatible context. + getGlSurfaceView().setEGLContextClientVersion(1); + } + + /** + * {@inheritDoc} + */ + @Override + public WallpaperGLSurfaceView createWallpaperGLSurfaceView() { + // Check whether to use a proprietary GLSurfaceView reference or an internal one + if (mWallpaperGLSurfaceView != null) { + return mWallpaperGLSurfaceView; + } + return super.createWallpaperGLSurfaceView(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onVisibilityChanged(boolean visible) { + super.onVisibilityChanged(visible); + if (mRendererHasBeenSet) { + if (visible) { + getGlSurfaceView().onResume(); + getGlSurfaceView().setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + mListener.onResume(mRenderer); + } else { + // Check that the user is not previewing the live wallpaper; if they are, then + // if they open up a settings dialog that appears over the preview, we don’t + // want to pause rendering + boolean preview = isPreview(); + if (!preview || (preview && !mPauseOnPreview)) { + getGlSurfaceView().setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + getGlSurfaceView().onPause(); + mListener.onPause(mRenderer); + } + } + } + } + + /** + * Method that determines if the surface view should be paused on preview mode + * + * @return boolean whether the surface view should be paused on preview mode + */ + public boolean isPauseOnPreview() { + return mPauseOnPreview; + } + + /** + * Method that sets if the surface view should be paused on preview mode + * + * @param pauseOnPreview whether the surface view should be paused on preview mode + */ + public void setPauseOnPreview(boolean pauseOnPreview) { + this.mPauseOnPreview = pauseOnPreview; + } + } + + /** + * Method that return a new EGL renderer. + * + * @param view The view that will be associated to the renderer + * @return Renderer The new EGL renderer. + */ + public abstract Renderer getNewRenderer(GLSurfaceView view); +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java b/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java new file mode 100644 index 0000000..b1b4d2d --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A class that load asynchronously the paths of all media stored in the device. + * This class only seek at the specified paths + */ +public class MediaPictureDiscoverer { + + private static final String TAG = "MediaPictureDiscoverer"; + + private static final boolean DEBUG = false; + + /** + * An interface that is called when new data is ready. + */ + public interface OnMediaPictureDiscoveredListener { + /** + * Called when the data is ready + * + * @param mpc The reference to the discoverer + * @param images All the images paths found + */ + void onMediaDiscovered(MediaPictureDiscoverer mpc, File[] images); + } + + /** + * The asynchronous task for query the MediaStore + */ + private class AsyncDiscoverTask extends AsyncTask<Void, Void, List<File> > { + + private final ContentResolver mFinalContentResolver; + private final OnMediaPictureDiscoveredListener mFinalCallback; + private final Set<String> mFilter; + + /** + * Constructor of <code>AsyncDiscoverTask</code> + * + * @param cr The {@link ContentResolver} + * @param filter The filter of pictures and albums to retrieve + * @param cb The {@link OnMediaPictureDiscoveredListener} listener + */ + public AsyncDiscoverTask(ContentResolver cr, Set<String> filter, + OnMediaPictureDiscoveredListener cb) { + super(); + mFinalContentResolver = cr; + mFinalCallback = cb; + mFilter = filter; + } + + /** + * {@inheritDoc} + */ + @Override + protected List<File> doInBackground(Void...params) { + try { + // The columns to read + final String[] projection = {MediaStore.MediaColumns.DATA}; + + // Query external content + List<File> paths = + getPictures( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, + null, + null); + if (DEBUG) { + int cc = paths.size(); + Log.v(TAG, "Pictures found (" + cc + "):"); + for (int i = 0; i < cc; i++) { + Log.v(TAG, "\t" + paths.get(i)); + } + } + return paths; + + } catch (Exception e) { + Log.e(TAG, "AsyncDiscoverTask failed.", e); + + // Return and empty list + return new ArrayList<File>(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPostExecute(List<File> result) { + if (mFinalCallback != null) { + mFinalCallback.onMediaDiscovered( + MediaPictureDiscoverer.this, result.toArray(new File[result.size()])); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onCancelled(List<File> result) { + // Nothing found + if (mFinalCallback != null) { + mFinalCallback.onMediaDiscovered( + MediaPictureDiscoverer.this, new File[]{}); + } + } + + /** + * Method that return all the media store pictures for the content uri + * + * @param uri The content uri where to search + * @param projection The field data to return + * @param where A filter + * @param args The filter arguments + * @return List<File> The pictures found + */ + private List<File> getPictures( + Uri uri, String[] projection, String where, String[] args) { + List<File> paths = new ArrayList<File>(); + Cursor c = mFinalContentResolver.query(uri, projection, where, args, null); + if (c != null) { + try { + while (c.moveToNext()) { + // Only valid files (those i can read) + String p = c.getString(0); + if (p != null) { + File f = new File(p); + if (f.isFile() && f.canRead() && matchFilter(f)) { + paths.add(f); + } + } + } + } finally { + try { + c.close(); + } catch (Exception e) { + // Ignore: handle exception + } + } + } + return paths; + } + + /** + * Method that checks if the picture match the preferences filter + * + * @param picture The picture to check + * @return boolean whether the picture match the filter + */ + private boolean matchFilter(File picture) { + Iterator<String> it = mFilter.iterator(); + boolean noFilter = true; + while (it.hasNext()) { + noFilter = false; + File filter = new File(it.next()); + if (filter.isDirectory()) { + // Album match + if (filter.compareTo(picture.getParentFile()) == 0) { + return true; + } + } else { + // Picture match + if (filter.compareTo(picture) == 0) { + return true; + } + } + } + return noFilter; + } + } + + private final Context mContext; + private final OnMediaPictureDiscoveredListener mCallback; + + private AsyncDiscoverTask mTask; + + /** + * Constructor of <code>MediaPictureDiscoverer</code>. + * + * @param ctx The current context + * @param callback A callback to returns the data when it gets ready + */ + public MediaPictureDiscoverer(Context ctx, OnMediaPictureDiscoveredListener callback) { + super(); + mContext = ctx; + mCallback = callback; + } + + /** + * Method that request a new reload of the media store picture data. + * + * @param filter The filter of pictures and albums where to search images + */ + public synchronized void discover(Set<String> filter) { + if (mTask != null && !mTask.isCancelled()) { + mTask.cancel(true); + } + mTask = new AsyncDiscoverTask(mContext.getContentResolver(), filter, mCallback); + mTask.execute(); + } + + /** + * Method that destroy the references of this class + */ + public void recycle() { + if (mTask != null && !mTask.isCancelled()) { + mTask.cancel(true); + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java new file mode 100644 index 0000000..26ba641 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.graphics.Bitmap; +import android.opengl.GLES20; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + + +/** + * A GLES square geometry that represents one photo frame for show in the wallpaper. + */ +public class PhotoFrame implements TextureRequestor { + + /** + * @hide + */ + public static final int COORDS_PER_VERTER = 3; + + // The photo frame is always a rectangle, so here applies 2 triangle order + private static final short[] VERTEX_ORDER = { 0, 1, 2, 0, 2, 3 }; + + // The default texture coordinates (fit to frame) + private static final float[] DEFAULT_TEXTURE_COORDS = { + 0, 0, // top left + 0, 1, // bottom left + 1, 1, // bottom right + 1, 0 // top right + }; + + private final TextureManager mTextureManager; + + private final float mFrameWidth, mFrameHeight; + private final float mPictureWidth, mPictureHeight; + private final float[] mFrameVertex; + private final float[] mPictureVertex; + + private FloatBuffer mPictureVertexBuffer; + private ShortBuffer mVertexOrderBuffer; + private FloatBuffer mTextureBuffer; + + private GLESTextureInfo mTextureInfo; + + private final GLColor mBackgroundColor; + + private boolean mLoaded; + + private final Object mSync = new Object(); + + /** + * Constructor of <code>PhotoFrame</code>. + * + * @param ctx The current context + * @param textureManager The texture manager + * @param frameVertex A 4 dimension array with the coordinates per vertex plus padding + * @param pictureVertex A 4 dimension array with the coordinates per vertex + * @param color Background color + */ + public PhotoFrame(Context ctx, TextureManager textureManager, + float[] frameVertex, float[] pictureVertex, GLColor color) { + super(); + mLoaded = false; + mBackgroundColor = color; + mTextureManager = textureManager; + + // Save dimensions + mFrameVertex = frameVertex; + mFrameWidth = frameVertex[9] - frameVertex[0]; + mFrameHeight = frameVertex[4] - frameVertex[1]; + mPictureVertex = pictureVertex; + mPictureWidth = pictureVertex[9] - pictureVertex[0]; + mPictureHeight = pictureVertex[4] - pictureVertex[1]; + + // Initialize vertex byte buffer for shape coordinates + ByteBuffer bb = ByteBuffer.allocateDirect(pictureVertex.length * 4); // (# of coordinate values * 4 bytes per float) + bb.order(ByteOrder.nativeOrder()); + mPictureVertexBuffer = bb.asFloatBuffer(); + mPictureVertexBuffer.put(pictureVertex); + mPictureVertexBuffer.position(0); + + // Initialize vertex byte buffer for shape coordinates order + bb = ByteBuffer.allocateDirect(VERTEX_ORDER.length * 2); // (# of coordinate values * 2 bytes per short) + bb.order(ByteOrder.nativeOrder()); + mVertexOrderBuffer = bb.asShortBuffer(); + mVertexOrderBuffer.put(VERTEX_ORDER); + mVertexOrderBuffer.position(0); + + // Load the texture + mTextureInfo = null; + + // Request a new image for this frame + textureManager.request(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void setTextureHandle(GLESTextureInfo ti) { + // If the picture is invalid request a new texture + if (ti == null || ti.handle <= 0) { + mTextureManager.request(this); + return; + } + + // Full frame picture + // TODO Apply some type of ratio correction to the texture coordinates + setTextureHandle(ti, DEFAULT_TEXTURE_COORDS); + mLoaded = true; + } + + /** + * Internal method that expose the texture coordinates to set + * + * @param ti The texture info + * @param textureCoords The texture coordinates + */ + private void setTextureHandle(GLESTextureInfo ti, final float[] textureCoords) { + // Recycle the previous handle + if (mTextureInfo != null && mTextureInfo.handle != 0) { + int[] textures = new int[]{mTextureInfo.handle}; + GLES20.glDeleteTextures(1, textures, 0); + GLESUtil.glesCheckError("glDeleteTextures"); + } + + // Initialize vertex byte buffer for shape coordinates + ByteBuffer bb = ByteBuffer.allocateDirect(textureCoords.length * 4); // (# of coordinate values * 4 bytes per float) + bb.order(ByteOrder.nativeOrder()); + synchronized (mSync) { + // Synchronize buffer swap + mTextureBuffer = bb.asFloatBuffer(); + mTextureBuffer.put(textureCoords); + mTextureBuffer.position(0); + } + mTextureInfo = ti; + } + + /** + * Method that returns the frame vertex + * + * @return float[] The frame vertex + */ + public float[] getFrameVertex() { + return mFrameVertex; + } + + /** + * Method that returns the picture vertex + * + * @return float[] The picture vertex + */ + public float[] getPictureVertex() { + return mPictureVertex; + } + + /** + * Method that returns the picture vertex buffer + * + * @return FloatBuffer The picture vertex buffer + */ + public FloatBuffer getPictureVertexBuffer() { + return mPictureVertexBuffer; + } + + /** + * Method that returns the vertex order buffer + * + * @return ShortBuffer The vertex order buffer + */ + public ShortBuffer getVertexOrderBuffer() { + return mVertexOrderBuffer; + } + + /** + * Method that returns the texture buffer + * + * @return FloatBuffer The texture buffer + */ + public FloatBuffer getTextureBuffer() { + synchronized (mSync) { + return mTextureBuffer; + } + } + + /** + * Method that returns the texture handle + * + * @return int The texture handle + */ + public int getTextureHandle() { + if (mTextureInfo != null) { + return mTextureInfo.handle; + } + return -1; + } + + /** + * Method that returns the bitmap handle + * + * @return int The bitmap handle + */ + public Bitmap getTextureBitmap() { + if (mTextureInfo != null) { + return mTextureInfo.bitmap; + } + return null; + } + + /** + * Method that returns the frame width + * + * @return float The frame width + */ + public float getFrameWidth() { + return mFrameWidth; + } + + /** + * Method that returns the frame height + * + * @return float The frame height + */ + public float getFrameHeight() { + return mFrameHeight; + } + + /** + * Method that returns the picture width + * + * @return float The picture width + */ + public float getPictureWidth() { + return mPictureWidth; + } + + /** + * Method that returns the picture height + * + * @return float The picture height + */ + public float getPictureHeight() { + return mPictureHeight; + } + + /** + * Method that returns the background color of the frame + * + * @return GLColor The background color + */ + public GLColor getBackgroundColor() { + return mBackgroundColor; + } + + /** + * Method that returns if the frame is loaded (has its picture loaded) + * + * @return boolean If the frame is loaded (has its picture loaded) + */ + public boolean isLoaded() { + return mLoaded; + } + + /** + * Request a recycle of the references of the object + */ + public void recycle() { + if (mTextureInfo != null && mTextureInfo.handle != 0) { + int[] textures = new int[]{mTextureInfo.handle}; + GLES20.glDeleteTextures(1, textures, 0); + GLESUtil.glesCheckError("glDeleteTextures"); + } + mTextureInfo = null; + + if (mPictureVertexBuffer != null) { + mPictureVertexBuffer.clear(); + } + if (mVertexOrderBuffer != null) { + mVertexOrderBuffer.clear(); + } + if (mTextureBuffer != null) { + mTextureBuffer.clear(); + } + mPictureVertexBuffer = null; + mVertexOrderBuffer = null; + mTextureBuffer = null; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java new file mode 100644 index 0000000..6ea835c --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.app.Activity; +import android.content.Intent; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.Window; +import android.view.WindowManager; + +import org.cyanogenmod.wallpapers.photophase.preferences.PhotoPhasePreferences; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider; + +/** + * A testing activity to simulate the PhotoPhase Live Wallpaper inside an GLES activity. + */ +public class PhotoPhaseActivity extends Activity { + + private static final String TAG = "PhotoPhaseActivity"; + + private static final boolean DEBUG = true; + + private GLSurfaceView mGLSurfaceView; + private PhotoPhaseRenderer mRenderer; + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + + boolean preserveEglCtx = getResources().getBoolean(R.bool.config_preserve_egl_context); + + // Instance the application + PreferencesProvider.reload(this); + Colors.register(this); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + // Configure the EGL context + mGLSurfaceView = new GLSurfaceView(getApplicationContext()); + mGLSurfaceView.setEGLContextClientVersion(2); + mGLSurfaceView.setEGLConfigChooser(false); + mRenderer = new PhotoPhaseRenderer(this, new GLESSurfaceDispatcher(mGLSurfaceView)); + mGLSurfaceView.setRenderer(mRenderer); + mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + mGLSurfaceView.setPreserveEGLContextOnPause(preserveEglCtx); + setContentView(mGLSurfaceView); + + mRenderer.onCreate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + super.onDestroy(); + mRenderer.onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + if (DEBUG) Log.d(TAG, "onResume"); + mGLSurfaceView.onResume(); + mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + if (mRenderer != null) { + mRenderer.onResume(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + if (DEBUG) Log.d(TAG, "onPause"); + mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + mRenderer.onPause(); + mGLSurfaceView.onPause(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return super.onCreateOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mnu_settings: + Intent settings = new Intent(this, PhotoPhasePreferences.class); + startActivity(settings); + return true; + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java new file mode 100644 index 0000000..dff9887 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.opengl.GLES20; +import android.opengl.GLException; +import android.opengl.GLSurfaceView; +import android.opengl.Matrix; +import android.os.Handler; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider; +import org.cyanogenmod.wallpapers.photophase.shapes.ColorShape; +import org.cyanogenmod.wallpapers.photophase.transitions.Transition; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * The EGL renderer of PhotoPhase Live Wallpaper. + */ +public class PhotoPhaseRenderer implements GLSurfaceView.Renderer { + + private static final String TAG = "PhotoPhaseRenderer"; + + private static final boolean DEBUG = true; + + private final long mInstance; + private static long sInstances; + + final Context mContext; + private final Handler mHandler; + final GLESSurfaceDispatcher mDispatcher; + TextureManager mTextureManager; + + PhotoPhaseWallpaperWorld mWorld; + ColorShape mOverlay; + + long mLastRunningTransition; + + int mWidth = -1; + int mHeight = -1; + int mMeasuredHeight = -1; + + private final float[] mMVPMatrix = new float[16]; + private final float[] mProjMatrix = new float[16]; + private final float[] mVMatrix = new float[16]; + + private final Object mDrawing = new Object(); + + final Object mMediaSync = new Object(); + private PendingIntent mMediaScanIntent; + + private final BroadcastReceiver mSettingsChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Check what flags are been requested + boolean recreateWorld = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, false); + boolean redraw = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, false); + boolean emptyTextureQueue = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, false); + boolean mediaReload = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, false); + boolean mediaIntervalChanged = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_INTERVAL_CHANGED, false); + if (recreateWorld) { + // Recreate the wallpaper world + try { + mWorld.recreateWorld(mWidth, mMeasuredHeight); + } catch (GLException e) { + Log.e(TAG, "Cannot recreate the wallpaper world.", e); + } + } + if (redraw) { + mDispatcher.requestRender(); + } + if (emptyTextureQueue) { + mTextureManager.emptyTextureQueue(true); + } + if (mediaReload) { + synchronized (mMediaSync) { + mTextureManager.reloadMedia(); + scheduleOrCancelMediaScan(); + } + } + if (mediaIntervalChanged) { + scheduleOrCancelMediaScan(); + } + } + }; + + private final Runnable mTransitionThread = new Runnable() { + /** + * {@inheritDoc} + */ + @Override + public void run() { + // Run in GLES's thread + mDispatcher.dispatch(new Runnable() { + @Override + public void run() { + // Select a new transition + mWorld.selectTransition(); + mLastRunningTransition = System.currentTimeMillis(); + + // Now force continuously render while transition is applied + mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } + }); + } + }; + + /** + * Constructor of <code>PhotoPhaseRenderer<code> + * + * @param ctx The current context + * @param dispatcher The GLES dispatcher + */ + public PhotoPhaseRenderer(Context ctx, GLESSurfaceDispatcher dispatcher) { + super(); + mContext = ctx; + mHandler = new Handler(); + mDispatcher = dispatcher; + mInstance = sInstances; + sInstances++; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (mInstance ^ (mInstance >>> 32)); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PhotoPhaseRenderer other = (PhotoPhaseRenderer) obj; + if (mInstance != other.mInstance) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "PhotoPhaseRenderer [instance: " + mInstance + "]"; + } + + /** + * Method called when when renderer is created + */ + public void onCreate() { + if (DEBUG) Log.d(TAG, "onCreate [" + mInstance + "]"); + // Register a receiver to listen for media reload request + IntentFilter filter = new IntentFilter(); + filter.addAction(PreferencesProvider.ACTION_SETTINGS_CHANGED); + mContext.registerReceiver(mSettingsChangedReceiver, filter); + + // Check whether the media scan is active + int interval = PreferencesProvider.Preferences.Media.getRefreshFrecuency(); + if (interval != PreferencesProvider.Preferences.Media.MEDIA_RELOAD_DISABLED) { + // Schedule a media scan + scheduleMediaScan(interval); + } + } + + /** + * Method called when when renderer is destroyed + */ + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy [" + mInstance + "]"); + // Register a receiver to listen for media reload request + mContext.unregisterReceiver(mSettingsChangedReceiver); + recycle(); + mWidth = -1; + mHeight = -1; + mMeasuredHeight = -1; + } + + /** + * Method called when the renderer should be paused + */ + public void onPause() { + if (DEBUG) Log.d(TAG, "onPause [" + mInstance + "]"); + mHandler.removeCallbacks(mTransitionThread); + if (mTextureManager != null) { + mTextureManager.setPause(true); + } + } + + /** + * Method called when the renderer should be resumed + */ + public void onResume() { + if (DEBUG) Log.d(TAG, "onResume [" + mInstance + "]"); + if (mTextureManager != null) { + mTextureManager.setPause(false); + } + } + + void scheduleOrCancelMediaScan() { + int interval = PreferencesProvider.Preferences.Media.getRefreshFrecuency(); + if (interval != PreferencesProvider.Preferences.Media.MEDIA_RELOAD_DISABLED) { + scheduleMediaScan(interval); + } else { + cancelMediaScan(); + } + } + + /** + * Method that schedules a new media scan + * + * @param interval The new interval + */ + private void scheduleMediaScan(int interval) { + AlarmManager am = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + + Intent i = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + i.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE); + mMediaScanIntent = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); + + long milliseconds = PreferencesProvider.Preferences.Media.getRefreshFrecuency() * 1000L; + am.set(AlarmManager.RTC, System.currentTimeMillis() + milliseconds, mMediaScanIntent); + } + + /** + * Method that cancels a pending media scan + */ + private void cancelMediaScan() { + if (mMediaScanIntent != null) { + AlarmManager am = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + am.cancel(mMediaScanIntent); + mMediaScanIntent = null; + } + } + + /** + * Method that destroy all the internal references + */ + private void recycle() { + if (DEBUG) Log.d(TAG, "recycle [" + mInstance + "]"); + synchronized (mDrawing) { + // Remove any pending handle + if (mHandler != null && mTransitionThread != null) { + mHandler.removeCallbacks(mTransitionThread); + } + + // Delete the world + if (mWorld != null) mWorld.recycle(); + if (mTextureManager != null) mTextureManager.recycle(); + if (mOverlay != null) mOverlay.recycle(); + mWorld = null; + mTextureManager = null; + mOverlay = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { + if (DEBUG) Log.d(TAG, "onSurfaceCreated [" + mInstance + "]"); + + mWidth = -1; + mHeight = -1; + mMeasuredHeight = -1; + + // We have a 2d (fake) scenario, disable all unnecessary tests. Deep are + // necessary for some 3d effects + GLES20.glDisable(GL10.GL_DITHER); + GLESUtil.glesCheckError("glDisable"); + GLES20.glDisable(GL10.GL_CULL_FACE); + GLESUtil.glesCheckError("glDisable"); + GLES20.glEnable(GL10.GL_DEPTH_TEST); + GLESUtil.glesCheckError("glEnable"); + GLES20.glDepthMask(false); + GLESUtil.glesCheckError("glDepthMask"); + GLES20.glDepthFunc(GLES20.GL_LEQUAL); + GLESUtil.glesCheckError("glDepthFunc"); + + // Create the texture manager and recycle the old one + if (mTextureManager == null) { + // Precalculate the window size for the TextureManager. In onSurfaceChanged + // the best fixed size will be set. The disposition size is simple for a better + // performance of the internal arrays + final Configuration conf = mContext.getResources().getConfiguration(); + int w = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp); + int h = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp); + Rect dimensions = new Rect(0, 0, w / 2, h / 2); + int cc = PreferencesProvider.Preferences.Layout.getDisposition().size(); + + recycle(); + mTextureManager = new TextureManager(mContext, mDispatcher, cc, dimensions); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onSurfaceChanged(GL10 glUnused, int width, int height) { + if (DEBUG) Log.d(TAG, "onSurfaceChanged [" + mInstance + "," + width + "x" + height + "]"); + + // Check if the size was changed + if (mWidth == width && mHeight == height) { + return; + } + + // Save the width and height to avoid recreate the world + mWidth = width; + mHeight = height; + mMeasuredHeight = mHeight - AndroidHelper.calculateStatusBarHeight(mContext); + + // Calculate a better fixed size for the pictures + Rect dimensions = new Rect(0, 0, width / 2, mMeasuredHeight / 2); + mTextureManager.setDimensions(dimensions); + mTextureManager.setPause(false); + + // Create the wallpaper + mWorld = new PhotoPhaseWallpaperWorld(mContext, mTextureManager); + + // Create all other shapes + final float[] vertex = { + -1.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f + }; + mOverlay = new ColorShape(mContext, vertex, Colors.getOverlay()); + + // Set the viewport and the fustrum to use + GLES20.glViewport(0, 0, width, mMeasuredHeight); + GLESUtil.glesCheckError("glViewport"); + Matrix.frustumM(mProjMatrix, 0, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 2.0f); + + // Recreate the wallpaper world + try { + mWorld.recreateWorld(width, mMeasuredHeight); + } catch (GLException e) { + Log.e(TAG, "Cannot recreate the wallpaper world.", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDrawFrame(GL10 glUnused) { + synchronized (mDrawing) { + // Draw the background + drawBackground(); + + if (mWorld != null) { + // Set the projection, view and model + Matrix.setLookAtM(mVMatrix, 0, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); + Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0); + + // Now draw the world (all the photo frames with effects) + mWorld.draw(mMVPMatrix); + + // Check if we have some pending transition or transition has exceed its timeout + if (!mWorld.hasRunningTransition() || isTransitionTimeout()) { + mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + + // Now start a delayed thread to generate the next effect + mHandler.removeCallbacks(mTransitionThread); + mWorld.deselectTransition(mMVPMatrix); + mLastRunningTransition = 0; + mHandler.postDelayed(mTransitionThread, + PreferencesProvider.Preferences.General.Transitions.getTransitionInterval()); + } + } + + // Draw the overlay + drawOverlay(); + } + } + + /** + * Check whether the transition has exceed the timeout + * + * @return boolean if the transition has exceed the timeout + */ + private boolean isTransitionTimeout() { + long now = System.currentTimeMillis(); + long diff = now - mLastRunningTransition; + return mLastRunningTransition != 0 && diff > Transition.MAX_TRANSTION_TIME; + } + + /** + * Method that draws the background of the wallpaper + */ + private static void drawBackground() { + GLColor bg = Colors.getBackground(); + GLES20.glClearColor(bg.r, bg.g, bg.b, bg.a); + GLESUtil.glesCheckError("glClearColor"); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + GLESUtil.glesCheckError("glClear"); + } + + /** + * Method that draws the overlay of the wallpaper + */ + private void drawOverlay() { + if (mOverlay != null) { + mOverlay.setAlpha(PreferencesProvider.Preferences.General.getWallpaperDim() / 100.0f); + mOverlay.draw(mMVPMatrix); + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java new file mode 100644 index 0000000..01f81c7 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.opengl.GLSurfaceView.Renderer; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.GLESWallpaperService.GLESEngineListener; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider; + +import java.util.ArrayList; +import java.util.List; + + +/** + * The PhotoPhase Live Wallpaper service. + */ +public class PhotoPhaseWallpaper + extends GLES20WallpaperService implements GLESEngineListener { + + private static final String TAG = "PhotoPhaseWallpaper"; + + private static final boolean DEBUG = true; + + private List<PhotoPhaseRenderer> mRenderers; + private PhotoPhaseWallpaperEngine mEngine; + + private boolean mPreserveEGLContext; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate() { + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(); + + // Load the configuration + mPreserveEGLContext = getResources().getBoolean(R.bool.config_preserve_egl_context); + mRenderers = new ArrayList<PhotoPhaseRenderer>(); + + // Instance the application + PreferencesProvider.reload(this); + Colors.register(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + super.onDestroy(); + for (PhotoPhaseRenderer renderer : mRenderers) { + renderer.onDestroy(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Engine onCreateEngine() { + mEngine = new PhotoPhaseWallpaperEngine(this); + return mEngine; + } + + /** + * A wallpaper engine implementation using GLES. + */ + class PhotoPhaseWallpaperEngine extends GLES20WallpaperService.GLES20Engine { + /** + * Constructor of <code>PhotoPhaseWallpaperEngine<code> + * + * @param wallpaper The wallpaper service reference + */ + PhotoPhaseWallpaperEngine(PhotoPhaseWallpaper wallpaper) { + super(); + setOffsetNotificationsEnabled(true); + setTouchEventsEnabled(false); + setGLESEngineListener(wallpaper); + setWallpaperGLSurfaceView(new PhotoPhaseWallpaperGLSurfaceView(wallpaper)); + setPauseOnPreview(true); + } + + /** + * Out custom GLSurfaceView class to let us access all events stuff. + */ + class PhotoPhaseWallpaperGLSurfaceView extends WallpaperGLSurfaceView { + /** + * The constructor of <code>PhotoPhaseWallpaperGLSurfaceView</code>. + * + * @param context The current context + */ + public PhotoPhaseWallpaperGLSurfaceView(Context context) { + super(context); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onInitializeEGLView(GLSurfaceView view) { + if (DEBUG) Log.d(TAG, "onInitializeEGLView"); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroyEGLView(GLSurfaceView view, Renderer renderer) { + if (DEBUG) Log.d(TAG, "onDestroyEGLView" + renderer); + mRenderers.remove(renderer); + ((PhotoPhaseRenderer)renderer).onPause(); + ((PhotoPhaseRenderer)renderer).onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onEGLViewInitialized(GLSurfaceView view) { + view.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + view.setPreserveEGLContextOnPause(mPreserveEGLContext); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPause(Renderer renderer) { + if (DEBUG) Log.d(TAG, "onPause: " + renderer); + ((PhotoPhaseRenderer)renderer).onPause(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onResume(Renderer renderer) { + if (DEBUG) Log.d(TAG, "onResume: " + renderer); + ((PhotoPhaseRenderer)renderer).onResume(); + } + + /** + * {@inheritDoc} + */ + @Override + public Renderer getNewRenderer(GLSurfaceView view) { + if (DEBUG) Log.d(TAG, "getNewRenderer()"); + PhotoPhaseRenderer renderer = new PhotoPhaseRenderer(this, new GLESSurfaceDispatcher(view)); + renderer.onCreate(); + mRenderers.add(renderer); + if (DEBUG) Log.d(TAG, "renderer" + renderer); + return renderer; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java new file mode 100644 index 0000000..395cdbb --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.content.res.Configuration; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.preferences.Disposition; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; +import org.cyanogenmod.wallpapers.photophase.transitions.Transition; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that represents the wallpapers with all its photo frames. + */ +public class PhotoPhaseWallpaperWorld { + + private static final String TAG = "PhotoPhaseWallpaperWorld"; + + private static final boolean DEBUG = false; + + // The frame padding + private static final int PHOTO_FRAME_PADDING = 2; + + private final Context mContext; + private final TextureManager mTextureManager; + + private List<PhotoFrame> mPhotoFrames; + private List<Transition> mTransitions; + private final List<Transition> mUnusedTransitions; + + private List<Integer> mTransitionsQueue; + private List<Integer> mUsedTransitionsQueue; + private int mCurrent; + + private boolean mRecycled; + + /** + * Constructor <code>PhotoPhaseWallpaperWorld</code> + * + * @param ctx The current context + * @param textureManager The texture manager + */ + public PhotoPhaseWallpaperWorld( + Context ctx, TextureManager textureManager) { + super(); + mContext = ctx; + mTextureManager = textureManager; + mCurrent = -1; + mUnusedTransitions = new ArrayList<Transition>(); + mRecycled = false; + } + + /** + * Method that returns an unused transition for the type of transition + * + * @param type The type of transition + * @return Transition The unused transition + */ + private Transition getUnusedTransition(TRANSITIONS type) { + for (Transition transition : mUnusedTransitions) { + if (transition.getType().compareTo(type) == 0) { + mUnusedTransitions.remove(transition); + return transition; + } + } + return null; + } + + /** + * Method that returns or creates a transition for the type of transition + * + * @param type The type of transition + * @param frame The frame which the effect will be applied to + * @return Transition The unused transition + */ + private Transition getOrCreateTransition(TRANSITIONS type, PhotoFrame frame) { + Transition transition = getUnusedTransition(type); + if (transition == null) { + transition = Transitions.createTransition(mContext, mTextureManager, type, frame); + } + transition.reset(); + return transition; + } + + /** + * Method that selects a transition and assign it to a photo frame. + */ + public void selectTransition() { + // Ensure queue + if (mTransitionsQueue.isEmpty()) { + mTransitionsQueue.addAll(mUsedTransitionsQueue); + mUsedTransitionsQueue.clear(); + } + + // Get a random frame to which apply the transition + int r = 0 + (int)(Math.random() * (((mTransitionsQueue.size()-1) - 0) + 1)); + int pos = mTransitionsQueue.remove(r).intValue(); + mUsedTransitionsQueue.add(Integer.valueOf(pos)); + + // Create or use a transition + PhotoFrame frame = mPhotoFrames.get(pos); + Transition transition = null; + boolean isSelectable = false; + while (transition == null || !isSelectable) { + boolean isRandom = Preferences.General.Transitions.getTransitionTypes() == TRANSITIONS.RANDOM.ordinal(); + TRANSITIONS type = Transitions.getNextTypeOfTransition(frame); + transition = getOrCreateTransition(type, frame); + isSelectable = transition.isSelectable(frame); + if (!isSelectable) { + mUnusedTransitions.add(transition); + if (!isRandom) { + // If is not possible to select a valid transition then select a swap + // transition (this one doesn't relies on any selection) + transition = getOrCreateTransition(TRANSITIONS.SWAP, frame); + isSelectable = true; + } + } + } + mTransitions.set(pos, transition); + transition.select(frame); + mCurrent = pos; + } + + /** + * Method that deselect the current transition. + * + * @param matrix The model-view-projection matrix + */ + public void deselectTransition(float[] matrix) { + if (mCurrent != -1) { + // Retrieve the finally target + Transition currentTransition = mTransitions.get(mCurrent); + PhotoFrame currentTarget = currentTransition.getTarget(); + PhotoFrame finalTarget = currentTransition.getTransitionTarget(); + mUnusedTransitions.add(currentTransition); + + if (finalTarget != null) { + Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION, finalTarget); + mTransitions.set(mCurrent, transition); + + currentTarget.recycle(); + mPhotoFrames.set(mCurrent, finalTarget); + transition.select(finalTarget); + + // Draw the transition once + transition.apply(matrix); + } + mCurrent = -1; + } + } + + /** + * Method that removes all internal references. + */ + public void recycle() { + // Destroy the previous world + if (mTransitions != null) { + int cc = mTransitions.size()-1; + for (int i = cc; i >= 0; i--) { + Transition transition = mTransitions.get(i); + transition.recycle(); + mTransitions.remove(i); + } + } + if (mUnusedTransitions != null) { + int cc = mUnusedTransitions.size()-1; + for (int i = cc; i >= 0; i--) { + Transition transition = mUnusedTransitions.get(i); + transition.recycle(); + mUnusedTransitions.remove(i); + } + } + if (mPhotoFrames != null) { + int cc = mPhotoFrames.size()-1; + for (int i = cc; i >= 0; i--) { + PhotoFrame frame = mPhotoFrames.get(i); + mTextureManager.releaseBitmap(frame.getTextureBitmap()); + frame.recycle(); + mPhotoFrames.remove(i); + } + } + mTransitionsQueue.clear(); + mUsedTransitionsQueue.clear(); + mRecycled = true; + } + + /** + * Method that returns if there are any transition running in the world. + * + * @return boolean If there are any transition running in the world + */ + public boolean hasRunningTransition() { + if (mTransitions != null) { + for (Transition transition : mTransitions) { + if (transition.isRunning()) { + return true; + } + } + } + return false; + } + + /** + * Method that creates and fills the world with {@link PhotoFrame} objects. + * + * @param w The new width dimension + * @param h The new height dimension + */ + public synchronized void recreateWorld(int w, int h) { + if (DEBUG) Log.d(TAG, "Recreating the world. New surface: " + w + "x" + h); + + // Destroy the previous world + if (mRecycled) { + recycle(); + mRecycled = false; + } + + // Calculate the new world + int orientation = mContext.getResources().getConfiguration().orientation; + boolean portrait = orientation == Configuration.ORIENTATION_PORTRAIT; + int cols = portrait ? Preferences.Layout.getCols() : Preferences.Layout.getRows(); + int rows = portrait ? Preferences.Layout.getRows() : Preferences.Layout.getCols(); + float cellw = 2.0f / cols; + float cellh = 2.0f / rows; + List<Disposition> dispositions = Preferences.Layout.getDisposition(); + if (DEBUG) Log.d(TAG, + "Dispositions: " + dispositions.size() + " | " + String.valueOf(dispositions)); + mPhotoFrames = new ArrayList<PhotoFrame>(dispositions.size()); + mTransitions = new ArrayList<Transition>(dispositions.size()); + mTransitionsQueue = new ArrayList<Integer>(dispositions.size()); + mUsedTransitionsQueue = new ArrayList<Integer>(dispositions.size()); + int i = 0; + for (Disposition disposition : dispositions) { + // Create the photo frame + float[] frameVertices = getVerticesFromDisposition(disposition, portrait, cellw, cellh); + float[] pictureVertices = getFramePadding(frameVertices, portrait ? w : h, portrait ? h : w); + PhotoFrame frame = + new PhotoFrame( + mContext, + mTextureManager, + frameVertices, + pictureVertices, + Colors.getBackground()); + mPhotoFrames.add(frame); + + // Assign a null transition to the photo frame + Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION, frame); + transition.select(frame); + mTransitions.add(transition); + + mTransitionsQueue.add(Integer.valueOf(i)); + i++; + } + } + + /** + * Method that draws all the photo frames. + * + * @param matrix The model-view-projection matrix + */ + public void draw(float[] matrix) { + // Apply every transition + if (mTransitions != null) { + // First draw active transitions; then the not running transitions + for (Transition transition : mTransitions) { + transition.apply(matrix); + } + } + } + + /** + * Method that returns a coordinates per vertex array from a disposition + * + * @param disposition The source disposition + * @param portrait If the device is in portrait mode + * @param cellw The cell width based on the surface + * @param cellh The cell height based on the surface + * @return float[] The coordinates per vertex array + */ + private static float[] getVerticesFromDisposition( + Disposition disposition, boolean portrait, float cellw, float cellh) { + int x = portrait ? disposition.x : disposition.y; + int y = portrait ? disposition.y : disposition.x; + int w = portrait ? disposition.w : disposition.h; + int h = portrait ? disposition.h : disposition.w; + return new float[] + { + // top left + -1.0f + (x * cellw), + 1.0f - (y * cellh), + 0.0f, + + // bottom left + -1.0f + (x * cellw), + 1.0f - ((y * cellh) + (h * cellh)), + 0.0f, + + // bottom right + -1.0f + ((x * cellw) + (w * cellw)), + 1.0f - ((y * cellh) + (h * cellh)), + 0.0f, + + // top right + -1.0f + ((x * cellw) + (w * cellw)), + 1.0f - (y * cellh), + 0.0f + }; + } + + /** + * Method that applies a padding to the frame + * + * @param texCoords The source coordinates + * @param screenWidth The screen width + * @param screenHeight The screen height + * @return float[] The new coordinates + */ + private static float[] getFramePadding(float[] coords, int screenWidth, int screenHeight) { + float[] paddingCoords = new float[coords.length]; + System.arraycopy(coords, 0, paddingCoords, 0, coords.length); + final float pxw = (1 / (float)screenWidth) * PHOTO_FRAME_PADDING; + final float pxh = (1 / (float)screenHeight) * PHOTO_FRAME_PADDING; + paddingCoords[0] += pxw; + paddingCoords[1] -= pxh; + paddingCoords[3] += pxw; + paddingCoords[4] += pxh; + paddingCoords[6] -= pxw; + paddingCoords[7] += pxh; + paddingCoords[9] -= pxw; + paddingCoords[10] -= pxh; + return paddingCoords; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java b/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java new file mode 100644 index 0000000..9d3663f --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java @@ -0,0 +1,70 @@ +/** + * + */ +package org.cyanogenmod.wallpapers.photophase; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * @author jruesga + * + */ +public final class StorageHelper { + + private static final String TAG = "StorageHelper"; + + private static final String EXTERNAL_REGEXP = "(?i).*vold.*(fuse|vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*"; + + /** + * Method that returns all the external mounts + * + * @return List<String> All the external mounts + */ + public static List<String> getExternalMounts() { + final List<String> out = new ArrayList<String>(); + + // Execute the mount command to list mounts + final StringBuilder sb = new StringBuilder(); + try { + final Process process = + new ProcessBuilder().command("mount") + .redirectErrorStream(true).start(); + process.waitFor(); + final InputStream is = process.getInputStream(); + byte[] buffer = new byte[1024]; + int read = 0; + while ((read = is.read(buffer, 0, 1024)) != -1) { + sb.append(new String(buffer, 0, read)); + } + is.close(); + } catch (IOException ioex) { + Log.e(TAG, "Failed to list external mounts", ioex); + } catch (InterruptedException iex) { + Log.e(TAG, "Failed to list external mounts", iex); + } + + // Parse the output + final String[] lines = sb.toString().split("\n"); + for (String line : lines) { + if (!line.toLowerCase(Locale.US).contains("asec")) { + if (line.matches(EXTERNAL_REGEXP)) { + String[] parts = line.split(" "); + for (String part : parts) { + if (part.startsWith("/")) { + if (!part.toLowerCase(Locale.US).contains("vold")) { + out.add(part); + } + } + } + } + } + } + return out; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java new file mode 100644 index 0000000..bd42af9 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.opengl.GLES20; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.FixedQueue.EmptyQueueException; +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo; +import org.cyanogenmod.wallpapers.photophase.MediaPictureDiscoverer.OnMediaPictureDiscoveredListener; +import org.cyanogenmod.wallpapers.photophase.effects.Effects; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A class that manages the acquisition of new textures. + */ +public class TextureManager implements OnMediaPictureDiscoveredListener { + + private static final String TAG = "TextureManager"; + + private static final int QUEUE_SIZE = 1; + + static final List<Bitmap> sRecycledBitmaps = new ArrayList<Bitmap>(); + + final Context mContext; + final Object mSync; + final List<TextureRequestor> mPendingRequests; + final FixedQueue<GLESTextureInfo> mQueue = new FixedQueue<GLESTextureInfo>(QUEUE_SIZE); + BackgroundPictureLoaderThread mBackgroundTask; + private final MediaPictureDiscoverer mPictureDiscoverer; + + + + /*package*/ Rect mDimensions; + + final GLESSurfaceDispatcher mDispatcher; + + /** + * A private runnable that will run in the GLThread + */ + /*package*/ class PictureDispatcher implements Runnable { + File mImage; + GLESTextureInfo ti; + final Object mWait = new Object(); + + /** + * {@inheritDoc} + */ + @Override + public void run() { + try { + // If we have bitmap to reused then pick up from the recycled list + if (sRecycledBitmaps.size() > 0) { + // Bind to the GLES context + ti = GLESUtil.loadTexture(sRecycledBitmaps.remove(0)); + } else { + // Load and bind to the GLES context + ti = GLESUtil.loadTexture(mImage, mDimensions, Effects.getNextEffect(), false); + } + + synchronized (mSync) { + // Notify the new images to all pending frames + if (mPendingRequests.size() > 0) { + // Invalid textures are also reported, so requestor can handle it + mPendingRequests.remove(0).setTextureHandle(ti); + } else { + // Add to the queue (only valid textures) + if (ti.handle > 0) { + mQueue.insert(ti); + } + } + } + + } catch (Exception e) { + Log.e(TAG, "Something was wrong loading the texture: " + mImage.getAbsolutePath(), e); + + } finally { + // Notify that we have a new image + synchronized (mWait) { + mWait.notify(); + } + } + } + } + + /** + * Constructor of <code>TextureManager</code> + * + * @param ctx The current context + * @param dispatcher The GLES dispatcher + * @param requestors The number of requestors + * @param dimensions The desired dimensions for the decoded bitmaps + */ + public TextureManager(final Context ctx, GLESSurfaceDispatcher dispatcher, int requestors, Rect dimensions) { + super(); + mContext = ctx; + mDispatcher = dispatcher; + mDimensions = dimensions; + mSync = new Object(); + mPendingRequests = new ArrayList<TextureRequestor>(requestors); + mPictureDiscoverer = new MediaPictureDiscoverer(mContext, this); + + // Run the media discovery thread + mBackgroundTask = new BackgroundPictureLoaderThread(); + mBackgroundTask.mTaskPaused = false; + reloadMedia(); + } + + /** + * Method that allow to change the preferred dimensions of the bitmaps loaded + * + * @param dimensions The new dimensions + */ + public void setDimensions(Rect dimensions) { + mDimensions = dimensions; + } + + /** + * Method that returns if the texture manager is paused + * + * @return boolean whether the texture manager is paused + */ + public boolean isPaused() { + return mBackgroundTask != null && mBackgroundTask.mTaskPaused; + } + + /** + * Method that pauses the internal threads + * + * @param pause If the thread is paused (true) or resumed (false) + */ + public synchronized void setPause(boolean pause) { + synchronized (mBackgroundTask.mLoadSync) { + mBackgroundTask.mTaskPaused = pause; + if (!mBackgroundTask.mTaskPaused) { + mBackgroundTask.mLoadSync.notify(); + } + } + } + + /** + * Method that reload the references of media pictures + */ + void reloadMedia() { + Log.d(TAG, "Reload media picture data"); + // Discover new media + mPictureDiscoverer.discover(Preferences.Media.getSelectedAlbums()); + } + + /** + * Method that returns a bitmap to be reused + * + * @param bitmap The bitmap to release + */ + @SuppressWarnings("static-method") + public void releaseBitmap(Bitmap bitmap) { + if (bitmap != null) { + sRecycledBitmaps.add(0, bitmap); + } + } + + /** + * Method that request a new picture for the {@link TextureRequestor} + * + * @param requestor The requestor of the texture + */ + public void request(TextureRequestor requestor) { + synchronized (mSync) { + try { + requestor.setTextureHandle(mQueue.remove()); + } catch (EmptyQueueException eqex) { + // Add to queue of pending request to be notified when + // we have a new bitmap in the queue + mPendingRequests.add(requestor); + } + } + + synchronized (mBackgroundTask.mLoadSync) { + mBackgroundTask.mLoadSync.notify(); + } + } + + /** + * Method that removes all the textures from the queue + * + * @param reload Forces a reload of the queue + */ + public void emptyTextureQueue(boolean reload) { + synchronized (mSync) { + try { + List<GLESTextureInfo> all = mQueue.removeAll(); + for (GLESTextureInfo info : all) { + if (GLES20.glIsTexture(info.handle)) { + int[] textures = new int[] {info.handle}; + GLES20.glDeleteTextures(1, textures, 0); + GLESUtil.glesCheckError("glDeleteTextures"); + } + // Return the bitmap + sRecycledBitmaps.add(info.bitmap); + } + } catch (EmptyQueueException eqex) { + // Ignore + } + + // Reload the queue + if (reload) { + synchronized (mBackgroundTask.mLoadSync) { + mBackgroundTask.mLoadSync.notify(); + } + } + } + } + + /** + * Method that cancels a request did it previously. + * + * @param requestor The requestor of the texture + */ + public void cancelRequest(TextureRequestor requestor) { + synchronized (mSync) { + if (mPendingRequests.contains(requestor)) { + mPendingRequests.remove(requestor); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onMediaDiscovered(MediaPictureDiscoverer mpc, File[] images) { + // Now we have the paths of the images to use. Start a image loader + // thread to load pictures in background + mBackgroundTask.setAvailableImages(images); + if (!mBackgroundTask.mRun) { + mBackgroundTask.start(); + } else { + synchronized (mBackgroundTask.mLoadSync) { + mBackgroundTask.mLoadSync.notify(); + } + } + int found = images == null ? 0 : images.length; + Log.d(TAG, "Media picture data reloaded: " + found + " images found."); + } + + /** + * Method that destroy the references of this class + */ + public void recycle() { + // Destroy the media discovery task + mPictureDiscoverer.recycle(); + + // Destroy the background task + if (mBackgroundTask != null) { + mBackgroundTask.mRun = false; + try { + synchronized (mBackgroundTask.mLoadSync) { + mBackgroundTask.interrupt(); + } + } catch (Exception e) { + // Ignore + } + } + mBackgroundTask = null; + + // Recycle the textures of the queue + emptyTextureQueue(false); + } + + /** + * An internal thread to load pictures in background + */ + private class BackgroundPictureLoaderThread extends Thread { + + final Object mLoadSync = new Object(); + boolean mRun; + boolean mTaskPaused; + + private final List<File> mNewImages; + private final List<File> mUsedImages; + + /** + * Constructor of <code>BackgroundPictureLoaderThread</code>. + */ + public BackgroundPictureLoaderThread() { + super(); + mNewImages = new ArrayList<File>(); + mUsedImages = new ArrayList<File>(); + } + + /** + * Method that sets the current available images. + * + * @param images The current images + */ + public void setAvailableImages(File[] images) { + synchronized (mLoadSync) { + mNewImages.addAll(Arrays.asList(images)); + mUsedImages.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + mRun = true; + while (mRun) { + // Check if we need to load more images + while (!mTaskPaused && TextureManager.this.mQueue.items() < TextureManager.this.mQueue.size()) { + File image = null; + synchronized (mLoadSync) { + // Swap arrays if needed + if (mNewImages.size() == 0) { + mNewImages.addAll(mUsedImages); + mUsedImages.clear(); + } + if (mNewImages.size() == 0) { + reloadMedia(); + break; + } + + // Extract a random image + int low = 0; + int hight = mNewImages.size()-1; + int index = low + (int)(Math.random() * ((hight - low) + 1)); + image = mNewImages.remove(index); + } + + // Run commands in the GLThread + if (!mRun) break; + PictureDispatcher pd = new PictureDispatcher(); + pd.mImage = image; + mDispatcher.dispatch(pd); + + // Wait until the texture is loaded + try { + synchronized (pd.mWait) { + pd.mWait.wait(); + } + } catch (Exception e) { + // Ignore + } + + // Add to used images + mUsedImages.add(image); + } + + // Wait for new request + synchronized (mLoadSync) { + try { + mLoadSync.wait(); + } catch (Exception e) { + // Ignore + } + } + } + } + + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java b/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java new file mode 100644 index 0000000..59e8593 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo; + +/** + * An interface that defines an object as able to request textures. + */ +public interface TextureRequestor { + + /** + * Method that set the texture handle requested. + * + * @param ti The texture information + */ + void setTextureHandle(GLESTextureInfo ti); +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/Utils.java b/src/org/cyanogenmod/wallpapers/photophase/Utils.java new file mode 100644 index 0000000..d8331a0 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/Utils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase; + +import android.content.Context; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +/** + * A helper class with utilities + */ +public class Utils { + + /** + * This method converts dp unit to equivalent device specific value in pixels. + * + * @param ctx The current context + * @param dp A value in dp (Device independent pixels) unit + * @return float A float value to represent Pixels equivalent to dp according to device + */ + public static float convertDpToPixel(Context ctx, float dp) { + Resources resources = ctx.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * (metrics.densityDpi / 160f); + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java b/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java new file mode 100644 index 0000000..44101f4 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.animations; + +import android.view.View; +import android.view.View.OnClickListener; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; + +import org.cyanogenmod.wallpapers.photophase.model.Album; +import org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo; +import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures; +import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures.CallbacksListener; + +/** + * A class that manages a flip 3d effect of an album + */ +public class AlbumsFlip3dAnimationController { + + private static final int DURATION = 200; + + View mFront; + View mBack; + boolean mFrontFace; + + /** + * Constructor of <code>AlbumsFlip3dAnimationController</code> + * + * @param front The front view + * @param back The back view + */ + public AlbumsFlip3dAnimationController(AlbumInfo front, AlbumPictures back) { + super(); + mFront = front; + mBack = back; + mBack.setVisibility(View.GONE); + mFrontFace = true; + } + + /** + * Method that register the controller + */ + public void register() { + getFrontView().setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getBackView().setVisibility(View.INVISIBLE); + applyAnimation(false); + } + }); + ((AlbumPictures)getBackView()).addCallBackListener(new CallbacksListener() { + @Override + public void onBackButtonClick(View v) { + getBackView().setVisibility(View.INVISIBLE); + applyAnimation(true); + } + + @Override + public void onSelectionChanged(Album album) { + // Ignore + } + }); + } + + /** + * Method that applies the animation over the views + * + * @param inverse Applies the inverse animation + */ + /*package*/ void applyAnimation(boolean inverse) { + applyTransformation(getFrontView(), 0, 90 * (inverse ? -1 : 1), true); + } + + /*package*/ void applyTransformation(final View v, float start, float end, final boolean step1) { + final float centerX = v.getWidth() / 2.0f; + final float centerY = v.getHeight() / 2.0f; + + final Flip3dAnimation anim = new Flip3dAnimation(start, end, centerX, centerY); + anim.setDuration(DURATION); + anim.setFillAfter(true); + anim.setInterpolator(new AccelerateInterpolator()); + + anim.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (!step1) { + getBackView().setVisibility(View.VISIBLE); + } + getFrontView().setOnClickListener(null); + getBackView().setOnClickListener(null); + } + + @Override + public void onAnimationRepeat(Animation animation) { + // Ignore + } + + @Override + public void onAnimationEnd(Animation animation) { + getFrontView().setAnimation(null); + getBackView().setAnimation(null); + if (step1) { + getFrontView().setVisibility(View.INVISIBLE); + applyTransformation(getBackView(), -90 * (!mFrontFace ? -1 : 1), 0, false); + } else { + mFrontFace = !mFrontFace; + getBackView().setVisibility(View.GONE); + if (mFrontFace) { + getFrontView().setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + getBackView().setVisibility(View.INVISIBLE); + applyAnimation(false); + } + }); + } else { + ((AlbumPictures)getFrontView()).onShow(); + } + } + } + }); + v.startAnimation(anim); + } + + /*package*/ View getFrontView() { + return mFrontFace ? mFront : mBack; + } + + /*package*/ View getBackView() { + return !mFrontFace ? mFront : mBack; + } +} + diff --git a/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java b/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java new file mode 100644 index 0000000..0196dc5 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.animations; + +import android.graphics.Camera; +import android.graphics.Matrix; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * A 3d flit animation + */ +public class Flip3dAnimation extends Animation { + + private final float mFromDegrees; + private final float mToDegrees; + private final float mCenterX; + private final float mCenterY; + private Camera mCamera; + + /** + * Constructor of <code>Flip3dAnimation</code> + * + * @param fromDegrees From origin degrees + * @param toDegrees To destination degrees + * @param centerX The center horizontal position + * @param centerY The center vertical position + */ + public Flip3dAnimation(float fromDegrees, float toDegrees, float centerX, float centerY) { + mFromDegrees = fromDegrees; + mToDegrees = toDegrees; + mCenterX = centerX; + mCenterY = centerY; + } + + /** + * {@inheritDoc} + */ + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + mCamera = new Camera(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + final float fromDegrees = mFromDegrees; + float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); + + final float centerX = mCenterX; + final float centerY = mCenterY; + final Camera camera = mCamera; + + final Matrix matrix = t.getMatrix(); + + camera.save(); + camera.rotateY(degrees); + camera.getMatrix(matrix); + camera.restore(); + + matrix.preTranslate(-centerX, -centerY); + matrix.postTranslate(centerX, centerY); + } + +} + diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java new file mode 100644 index 0000000..dbda330 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.effects; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; + +/** + * This effect converts the source image to black and white color scheme. + */ +public class BlackAndWhiteEffect extends Effect { + + /** + * {@inheritDoc} + */ + @Override + public Bitmap apply(Bitmap src) { + try { + final int height = src.getHeight(); + final int width = src.getWidth(); + + Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas c = new Canvas(dst); + final Paint paint = new Paint(); + final ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0); + final ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); + paint.setColorFilter(f); + c.drawBitmap(src, 0, 0, paint); + return dst; + } finally { + src.recycle(); + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java new file mode 100644 index 0000000..4ab4695 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.effects; + +import android.graphics.Bitmap; + +/** + * The base class for all image effects. + */ +public abstract class Effect { + + /** + * Method that applies the effect + * + * @param src The source bitmap + * @return Bitmap The bitmap with the effect applied + */ + public abstract Bitmap apply(Bitmap src); +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java b/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java new file mode 100644 index 0000000..f8fd90d --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.effects; + +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; + +/** + * A class that manages all the supported effects + */ +public class Effects { + + /** + * Enumeration of the supported effects + */ + public enum EFFECTS { + /** + * A random combination of all supported effects + */ + RANDOM, + /** + * @see NullEffect + */ + NO_EFFECT, + /** + * @see BlackAndWhiteEffect + */ + BLACK_AND_WHITE, + /** + * @see SepiaEffect + */ + SEPIA; + } + + /** + * Method that return the next effect to use with the picture. + * + * @return Effect The next effect to use + */ + public static Effect getNextEffect() { + int effect = Preferences.General.Effects.getEffectTypes(); + if (effect == EFFECTS.RANDOM.ordinal()) { + int low = EFFECTS.NO_EFFECT.ordinal(); + int hight = EFFECTS.values().length - 1; + effect = low + (int)(Math.random() * ((hight - low) + 1)); + } + if (effect == EFFECTS.BLACK_AND_WHITE.ordinal()) { + return new BlackAndWhiteEffect(); + } + if (effect == EFFECTS.SEPIA.ordinal()) { + return new SepiaEffect(); + } + return new NullEffect(); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java new file mode 100644 index 0000000..0cd5e10 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.effects; + +import android.graphics.Bitmap; + +/** + * A <code>null</code> effect. This class doesn't apply any filter to the bitmap + */ +public class NullEffect extends Effect { + + /** + * {@inheritDoc} + */ + @Override + public Bitmap apply(Bitmap bitmap) { + return bitmap; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java new file mode 100644 index 0000000..15aa894 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.effects; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; + +/** + * This effect converts the source image to sepia color scheme. + */ +public class SepiaEffect extends Effect { + + /** + * {@inheritDoc} + */ + @Override + public Bitmap apply(Bitmap src) { + try { + final int height = src.getHeight(); + final int width = src.getWidth(); + + Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas c = new Canvas(dst); + final Paint paint = new Paint(); + final ColorMatrix cmA = new ColorMatrix(); + cmA.setSaturation(0); + final ColorMatrix cmB = new ColorMatrix(); + cmB.setScale(1f, .95f, .82f, 1.0f); + cmA.setConcat(cmB, cmA); + final ColorMatrixColorFilter f = new ColorMatrixColorFilter(cmA); + paint.setColorFilter(f); + c.drawBitmap(src, 0, 0, paint); + return dst; + } finally { + src.recycle(); + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/model/Album.java b/src/org/cyanogenmod/wallpapers/photophase/model/Album.java new file mode 100644 index 0000000..6329a69 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/model/Album.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.model; + +import android.graphics.drawable.Drawable; + +import java.util.List; + +/** + * A class that represents an album + */ +public class Album implements Comparable<Album> { + + private Drawable mIcon; + private String mPath; + private String mName; + private String mDate; + private boolean selected; + private List<String> mItems; + private List<String> mSelectedItems; + + public Drawable getIcon() { + return mIcon; + } + + public void setIcon(Drawable icon) { + this.mIcon = icon; + } + + public String getPath() { + return mPath; + } + + public void setPath(String path) { + this.mPath = path; + } + + public String getName() { + return mName; + } + + public void setName(String name) { + this.mName = name; + } + + public String getDate() { + return mDate; + } + + public void setDate(String date) { + this.mDate = date; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public List<String> getItems() { + return mItems; + } + + public void setItems(List<String> items) { + this.mItems = items; + } + + public List<String> getSelectedItems() { + return mSelectedItems; + } + + public void setSelectedItems(List<String> selectedItems) { + this.mSelectedItems = selectedItems; + } + + @Override + public int compareTo(Album another) { + return mPath.compareTo(another.mPath); + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java new file mode 100644 index 0000000..f0f3522 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.AsyncTask.Status; +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.provider.MediaStore; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.animations.AlbumsFlip3dAnimationController; +import org.cyanogenmod.wallpapers.photophase.model.Album; +import org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo; +import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures; +import org.cyanogenmod.wallpapers.photophase.widgets.CardLayout; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A fragment class for select the picture that will be displayed on the wallpaper + */ +public class ChoosePicturesFragment extends PreferenceFragment { + + private final AsyncTask<Void, Album, Void> mAlbumsLoaderTask = new AsyncTask<Void, Album, Void>() { + /** + * {@inheritDoc} + */ + @Override + protected Void doInBackground(Void... params) { + // Query all the external content and classify the pictures in albums and load the cards + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + Album album = null; + mAlbums.clear(); + Cursor c = mContentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new String[]{ MediaStore.MediaColumns.DATA }, + null, + null, + MediaStore.MediaColumns.DATA); + if (c != null) { + try { + while (c.moveToNext()) { + // Only valid files (those i can read) + String p = c.getString(0); + if (p != null) { + File f = new File(p); + if (f.exists() && f.isFile() && f.canRead()) { + File path = f.getParentFile(); + String name = path.getName(); + if (album == null || album.getPath().compareTo(path.getAbsolutePath()) != 0) { + if (album != null) { + mAlbums.add(album); + this.publishProgress(album); + try { + Thread.sleep(50L); + } catch (InterruptedException e) { + // Ignore + } + } + album = new Album(); + album.setPath(path.getAbsolutePath()); + album.setName(name); + album.setDate(df.format(new Date(path.lastModified()))); + album.setSelected(isSelectedItem(album.getPath())); + album.setItems(new ArrayList<String>()); + album.setSelectedItems(new ArrayList<String>()); + } + album.getItems().add(f.getAbsolutePath()); + if (isSelectedItem(f.getAbsolutePath())) { + album.getSelectedItems().add(f.getAbsolutePath()); + } + } + } + } + } finally { + c.close(); + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onProgressUpdate(Album... values) { + for (Album album : values) { + addAlbum(album); + } + } + }; + + + + List<Album> mAlbums; + ContentResolver mContentResolver; + + private CardLayout mAlbumsPanel; + + /*package*/ Set<String> mSelectedAlbums; + + /*package*/ boolean mSelectionChanged; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContentResolver = getActivity().getContentResolver(); + + // Create an empty album + mAlbums = new ArrayList<Album>(); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Load the albums user selection + mSelectedAlbums = PreferencesProvider.Preferences.Media.getSelectedAlbums(); + mSelectionChanged = false; + + setHasOptionsMenu(true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + if (mAlbumsLoaderTask.getStatus().compareTo(Status.PENDING) == 0) { + mAlbumsLoaderTask.cancel(true); + } + unbindDrawables(mAlbumsPanel); + + // Notify that the settings was changed + Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + if (mSelectionChanged) { + intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE); + } + getActivity().sendBroadcast(intent); + } + + /** + * Method that unbind all the drawables for a view + * + * @param view The root view + */ + private void unbindDrawables(View view) { + if (view.getBackground() != null) { + view.getBackground().setCallback(null); + } + if (view instanceof ViewGroup) { + for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { + unbindDrawables(((ViewGroup) view).getChildAt(i)); + } + ((ViewGroup) view).removeAllViews(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + View v = inflater.inflate(R.layout.choose_picture_fragment, container, false); + mAlbumsPanel = (CardLayout) v.findViewById(R.id.albums_panel); + + // Load the albums + mAlbumsLoaderTask.execute(); + + return v; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.albums, menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mnu_ok: + getActivity().finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Method that adds a new album to the card layout + * + * @param album The album to add + */ + void addAlbum(Album album) { + LayoutInflater li = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View albumView = li.inflate(R.layout.album, mAlbumsPanel, false); + final AlbumInfo albumInfo = (AlbumInfo)albumView.findViewById(R.id.album_info); + final AlbumPictures albumPictures = (AlbumPictures)albumView.findViewById(R.id.album_pictures); + + // Load the album info + albumInfo.updateView(album); + if (album.isSelected()) { + albumInfo.setSelected(true); + } + albumInfo.addCallBackListener(new AlbumInfo.CallbacksListener() { + @Override + public void onAlbumSelected(Album ref) { + // Remove all pictures of the album and add the album reference + removeAlbumItems(ref); + mSelectedAlbums.add(ref.getPath()); + ref.setSelected(true); + albumPictures.updateView(ref); + + PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums); + mSelectionChanged = true; + } + + @Override + public void onAlbumDeselected(Album ref) { + // Remove all pictures of the album + removeAlbumItems(ref); + ref.setSelected(false); + albumPictures.updateView(ref); + + PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums); + mSelectionChanged = true; + } + + + }); + + // Load the album picture data + albumPictures.updateView(album); + albumPictures.addCallBackListener(new AlbumPictures.CallbacksListener() { + @Override + public void onBackButtonClick(View v) { + // Ignored + } + + @Override + public void onSelectionChanged(Album ref) { + // Remove, add, and persist the selection + removeAlbumItems(ref); + mSelectedAlbums.addAll(ref.getSelectedItems()); + ref.setSelected(false); + albumInfo.updateView(ref); + + PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums); + mSelectionChanged = true; + } + }); + + // Register the animation controller + AlbumsFlip3dAnimationController controller = new AlbumsFlip3dAnimationController(albumInfo, albumPictures); + controller.register(); + + // Add to the panel of cards + mAlbumsPanel.addCard(albumView); + } + + /** + * Method that checks if an item is selected + * + * @param item The item + * @return boolean if an item is selected + */ + /*package*/ boolean isSelectedItem(String item) { + Iterator<String> it = mSelectedAlbums.iterator(); + while (it.hasNext()) { + String albumPath = it.next(); + if (item.compareTo(albumPath) == 0) { + return true; + } + } + return false; + } + + /** + * Method that removes the reference to all the items and itself + * + * @param ref The album + */ + /*package*/ void removeAlbumItems(Album ref) { + Iterator<String> it = mSelectedAlbums.iterator(); + while (it.hasNext()) { + String item = it.next(); + String parent = new File(item).getParent(); + if (parent.compareTo(ref.getPath()) == 0) { + it.remove(); + } else if (item.compareTo(ref.getPath()) == 0) { + it.remove(); + } + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java new file mode 100644 index 0000000..008522a --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; + +/** + * A class that holds a {@link PhotoFrame} disposition. + */ +public class Disposition { + /** + * Column + */ + public int x; + /** + * Row + */ + public int y; + /** + * Columns width + */ + public int w; + /** + * Rows height + */ + public int h; + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + h; + result = prime * result + w; + result = prime * result + x; + result = prime * result + y; + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Disposition other = (Disposition) obj; + if (h != other.h) + return false; + if (w != other.w) + return false; + if (x != other.x) + return false; + if (y != other.y) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "Disposition [x=" + x + ", y=" + y + ", w=" + w + ", h=" + h + "]"; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java new file mode 100644 index 0000000..069d648 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceFragment; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.Colors; +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.preferences.SeekBarProgressPreference.OnDisplayProgress; +import org.cyanogenmod.wallpapers.photophase.widgets.ColorPickerPreference; + +import java.text.DecimalFormat; + +/** + * A fragment class with all the general settings + */ +public class GeneralPreferenceFragment extends PreferenceFragment { + + private static final String TAG = "GeneralPreferenceFragment"; + + private static final boolean DEBUG = true; + + private SeekBarProgressPreference mWallpaperDim; + private ColorPickerPreference mBackgroundColor; + private ListPreference mTransitionsTypes; + private SeekBarProgressPreference mTransitionsInterval; + private ListPreference mEffectsTypes; + + boolean mRedrawFlag; + boolean mEmptyTextureQueueFlag; + + private final OnPreferenceChangeListener mOnChangeListener = new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String key = preference.getKey(); + if (DEBUG) Log.d(TAG, "Preference changed: " + key + "=" + newValue); + if (key.compareTo("ui_wallpaper_dim") == 0) { + mRedrawFlag = true; + } else if (key.compareTo("ui_background_color") == 0) { + mRedrawFlag = true; + int color = ((Integer)newValue).intValue(); + Colors.setBackground(new GLColor(color)); + } else if (key.compareTo("ui_transition_types") == 0) { + mRedrawFlag = true; + } else if (key.compareTo("ui_transition_interval") == 0) { + mRedrawFlag = true; + } else if (key.compareTo("ui_effect_types") == 0) { + mRedrawFlag = true; + mEmptyTextureQueueFlag = true; + } + return true; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + + // Reload the settings + PreferencesProvider.reload(getActivity()); + + // Notify that the settings was changed + Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + if (mRedrawFlag) { + intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE); + } + if (mEmptyTextureQueueFlag) { + intent.putExtra(PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, Boolean.TRUE); + } + getActivity().sendBroadcast(intent); + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + final DecimalFormat df = new DecimalFormat(); + df.setMinimumFractionDigits(0); + df.setMaximumIntegerDigits(1); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_general); + + mWallpaperDim = (SeekBarProgressPreference)findPreference("ui_wallpaper_dim"); + mWallpaperDim.setFormat(getString(R.string.pref_general_settings_wallpaper_dim_format)); + mWallpaperDim.setOnPreferenceChangeListener(mOnChangeListener); + + mBackgroundColor = (ColorPickerPreference)findPreference("ui_background_color"); + mBackgroundColor.setOnPreferenceChangeListener(mOnChangeListener); + + mTransitionsTypes = (ListPreference)findPreference("ui_transition_types"); + mTransitionsTypes.setOnPreferenceChangeListener(mOnChangeListener); + + mTransitionsInterval = (SeekBarProgressPreference)findPreference("ui_transition_interval"); + mTransitionsInterval.setFormat(getString(R.string.pref_general_transitions_interval_format)); + int max = PreferencesProvider.Preferences.General.Transitions.MAX_TRANSITION_INTERVAL; + int min = PreferencesProvider.Preferences.General.Transitions.MIN_TRANSITION_INTERVAL; + final int MAX = ((max - min) / 1000) * 2; + mTransitionsInterval.setMax(MAX); + mTransitionsInterval.setOnDisplayProgress(new OnDisplayProgress() { + @Override + public String onDisplayProgress(int progress) { + return df.format((progress * 0.5) + 1); + } + }); + mTransitionsInterval.setOnPreferenceChangeListener(mOnChangeListener); + + mEffectsTypes = (ListPreference)findPreference("ui_effect_types"); + mEffectsTypes.setOnPreferenceChangeListener(mOnChangeListener); + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java new file mode 100644 index 0000000..564c425 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.os.Bundle; +import android.preference.PreferenceFragment; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A fragment class with the layout disposition + */ +public class LayoutPreferenceFragment extends PreferenceFragment { + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_layout); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java new file mode 100644 index 0000000..8dd942d --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceFragment; +import android.util.Log; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A fragment class with all the media settings + */ +public class MediaPreferenceFragment extends PreferenceFragment { + + private static final String TAG = "MediaPreferenceFragment"; + + private static final boolean DEBUG = true; + + ListPreference mRefreshInterval; + Preference mRefreshNow; + + boolean mMediaIntevalChangedFlag; + + private final OnPreferenceChangeListener mOnChangeListener = new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + String key = preference.getKey(); + if (DEBUG) Log.d(TAG, "Preference changed: " + key + "=" + newValue); + if (key.compareTo("ui_media_refresh_interval") == 0) { + setRefreshIntervalSummary(Integer.valueOf(String.valueOf(newValue)).intValue()); + mMediaIntevalChangedFlag = true; + } + return true; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + + // Reload the settings + PreferencesProvider.reload(getActivity()); + + // Notify that the settings was changed + Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + if (mMediaIntevalChangedFlag) { + intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_INTERVAL_CHANGED, Boolean.TRUE); + } + getActivity().sendBroadcast(intent); + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_media); + + mRefreshInterval = (ListPreference)findPreference("ui_media_refresh_interval"); + setRefreshIntervalSummary(PreferencesProvider.Preferences.Media.getRefreshFrecuency()); + mRefreshInterval.setOnPreferenceChangeListener(mOnChangeListener); + + mRefreshNow = findPreference("ui_media_refresh_now"); + mRefreshNow.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + // Request a refresh of the media data + Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE); + getActivity().sendBroadcast(intent); + return true; + } + }); + } + + /** + * Method that set the refresh interval summary + * + * @param interval The interval value + */ + void setRefreshIntervalSummary(int interval) { + String v = String.valueOf(interval); + String[] labels = getResources().getStringArray(R.array.refresh_intervals_labels); + String[] values = getResources().getStringArray(R.array.refresh_intervals_values); + int cc = values.length; + for (int i = 0; i < cc; i++) { + if (values[i].compareTo(String.valueOf(v)) == 0) { + v = labels[i]; + break; + } + } + String summary = + (interval == 0) + ? getString(R.string.pref_media_settings_refresh_interval_disable) + : getString(R.string.pref_media_settings_refresh_interval_summary, v); + mRefreshInterval.setSummary(summary); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java new file mode 100644 index 0000000..3733418 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.app.ActionBar; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.MenuItem; + +import org.cyanogenmod.wallpapers.photophase.R; + +import java.util.List; + +/** + * The PhotoPhase Live Wallpaper preferences. + */ +public class PhotoPhasePreferences extends PreferenceActivity { + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //Initialize action bars + initTitleActionBar(); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Configure the action bar options + getActionBar().setDisplayOptions( + ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onBuildHeaders(List<Header> target) { + loadHeadersFromResource(R.xml.preferences_headers, target); + + // Retrieve the about header + Header aboutHeader = target.get(target.size()-1); + try { + String appver = + this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName; + aboutHeader.summary = getString(R.string.pref_about_summary, appver); + } catch (Exception e) { + aboutHeader.summary = getString(R.string.pref_about_summary, ""); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java new file mode 100644 index 0000000..8d04258 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.effects.Effects.EFFECTS; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A class that holds all the preferences of the wallpaper + */ +@SuppressWarnings("boxing") +public final class PreferencesProvider { + + /** + * Internal broadcast action to communicate that some setting was changed + * @see #EXTRA_FLAG_REDRAW + * {@hide} + */ + public static final String ACTION_SETTINGS_CHANGED = "org.cyanogenmod.wallpapers.photophase.actions.SETTINGS_CHANGED"; + + /** + * An extra setting that indicates that the changed setting request a whole recreation of the wallpaper world + * {@hide} + */ + public static final String EXTRA_FLAG_RECREATE_WORLD = "flag_recreate_world"; + + /** + * An extra setting that indicates that the changed setting request a redraw of the wallpaper + * {@hide} + */ + public static final String EXTRA_FLAG_REDRAW = "flag_redraw"; + + /** + * An extra setting that indicates that the changed setting request to empty the texture queue + * {@hide} + */ + public static final String EXTRA_FLAG_EMPTY_TEXTURE_QUEUE = "flag_empty_texture_queue"; + + /** + * An extra setting that indicates that the changed setting request a reload of the media data + * {@hide} + */ + public static final String EXTRA_FLAG_MEDIA_RELOAD = "flag_media_reload"; + + /** + * An extra setting that indicates that the changed setting notifies that the media + * interval was changed + * {@hide} + */ + public static final String EXTRA_FLAG_MEDIA_INTERVAL_CHANGED = "flag_media_interval_changed"; + + /** + * The shared preferences file + */ + public static final String PREFERENCES_FILE = "org.cyanogenmod.wallpapers.photophase"; + + private static Map<String, ?> mPreferences = new HashMap<String, Object>(); + + /** + * Method that loads the all the preferences of the application + * + * @param context The current context + */ + public static void reload(Context context) { + SharedPreferences preferences = + context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); + mPreferences = preferences.getAll(); + } + + /** + * Method that returns a integer property value. + * + * @param key The preference key + * @param def The default value + * @return int The integer property value + */ + static int getInt(String key, int def) { + return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Integer ? + (Integer) mPreferences.get(key) : def; + } + + /** + * Method that returns a long property value. + * + * @param key The preference key + * @param def The default value + * @return long The long property value + */ + static long getLong(String key, long def) { + return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Long ? + (Long) mPreferences.get(key) : def; + } + + /** + * Method that returns a boolean property value. + * + * @param key The preference key + * @param def The default value + * @return boolean The boolean property value + */ + static boolean getBoolean(String key, boolean def) { + return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Boolean ? + (Boolean) mPreferences.get(key) : def; + } + + /** + * Method that returns a string property value. + * + * @param key The preference key + * @param def The default value + * @return String The string property value + */ + static String getString(String key, String def) { + return mPreferences.containsKey(key) && mPreferences.get(key) instanceof String ? + (String) mPreferences.get(key) : def; + } + + /** + * Method that returns a string set property value. + * + * @param key The preference key + * @param def The default value + * @return Set<String> The string property value + */ + @SuppressWarnings("unchecked") + static Set<String> getStringSet(String key, Set<String> def) { + return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Set<?> ? + (Set<String>) mPreferences.get(key) : def; + } + + /** + * A class for access to the preferences of the application + */ + public static class Preferences { + /** + * General preferences + */ + public static class General { + private static final GLColor DEFAULT_BACKGROUND_COLOR = new GLColor("#ff202020"); + + /** + * Method that returns the wallpaper dimmed value. + * + * @return float If the wallpaper dimmed value (0-black, 100-black) + */ + public static float getWallpaperDim() { + return getInt("ui_wallpaper_dim", 0); + } + + /** + * Method that returns the background color + * + * @return GLColor The background color + */ + public static GLColor getBackgroundColor() { + int color = getInt("ui_background_color", 0); + if (color == 0) { + return DEFAULT_BACKGROUND_COLOR; + } + return new GLColor(color); + } + + /** + * Transitions preferences + */ + public static class Transitions { + /** + * The default transition interval + */ + public static final int DEFAULT_TRANSITION_INTERVAL = 2000; + /** + * The minimum transition interval + */ + public static final int MIN_TRANSITION_INTERVAL = 1000; + /** + * The maximum transition interval + */ + public static final int MAX_TRANSITION_INTERVAL = 8000; + + /** + * Return the current user preference about the transition to apply to + * the pictures of the wallpaper. + * + * @return int The transition to apply to the wallpaper's pictures + */ + public static int getTransitionTypes() { + return Integer.valueOf(getString("ui_transition_types", String.valueOf(TRANSITIONS.RANDOM.ordinal()))); + } + + /** + * Method that returns how often the transitions are triggered. + * + * @return int The milliseconds in which the next transition will be triggered + */ + public static int getTransitionInterval() { + int def = (DEFAULT_TRANSITION_INTERVAL / 500) - 2; + int interval = getInt("ui_transition_interval", def); + return (interval * 500) + 1000; + } + } + + /** + * Effects preferences + */ + public static class Effects { + /** + * Return the current user preference about the effect to apply to + * the pictures of the wallpaper. + * + * @return int The effect to apply to the wallpaper's pictures + */ + public static int getEffectTypes() { + return Integer.valueOf(getString("ui_effect_types", String.valueOf(EFFECTS.NO_EFFECT.ordinal()))); + } + } + } + + /** + * Media preferences + */ + public static class Media { + /** + * Constant that indicates that the media reload is disabled + */ + public static final int MEDIA_RELOAD_DISABLED = 0; + + /** + * Method that returns the frequency with which the media is updated. + * + * @return int The interval in seconds between updates. 0 means that updates are disabled + */ + public static int getRefreshFrecuency() { + return Integer.valueOf(getString("ui_media_refresh_interval", String.valueOf(MEDIA_RELOAD_DISABLED))); + } + + /** + * Method that returns the list of albums and pictures to be displayed + * + * @return Set<String> The list of albums and pictures to be displayed + */ + public static Set<String> getSelectedAlbums() { + return getStringSet("ui_media_selected_albums", new HashSet<String>()); + } + + /** + * Method that returns the list of albums and pictures to be displayed + * + * @param context The current context + * @param selection The new list of albums and pictures to be displayed + */ + public static synchronized void setSelectedAlbums(Context context, Set<String> selection) { + SharedPreferences preferences = + context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); + Editor editor = preferences.edit(); + editor.remove("ui_media_selected_albums"); + editor.putStringSet("ui_media_selected_albums", selection); + editor.commit(); + reload(context); + } + } + + /** + * Layout preferences + */ + public static class Layout { + + private static final int DEFAULT_COLS = 8; + private static final int DEFAULT_ROWS = 14; + private static final String DEFAULT_DISPOSITION = "0x0:5x4|5x0:3x2|5x2:3x2|0x4:4x4|4x4:4x4|0x8:8x6"; + + /** + * Method that returns the rows of the wallpaper. + * + * @return int The rows of the wallpaper + */ + public static int getRows() { + return getInt("ui_layout_rows", DEFAULT_ROWS); + } + + /** + * Method that returns the columns of the wallpaper. + * + * @return int The columns of the wallpaper + */ + public static int getCols() { + return getInt("ui_layout_cols", DEFAULT_COLS); + } + + /** + * Returns the disposition of the photo frames in the wallpaper. The setting is + * stored as <code>0x0:1x2|2x2:3x4|...</code>, which it means (position x=0, y=0, + * 1 cells width, 2 cells height, ...). + * + * @return List<Disposition> The photo frames dispositions + */ + public static List<Disposition> getDisposition() { + String setting = getString("ui_layout_disposition", DEFAULT_DISPOSITION); + String[] v = setting.split("\\|"); + List<Disposition> dispositions = new ArrayList<Disposition>(v.length); + for (String s : v) { + String[] s1 = s.split(":"); + String[] s2 = s1[0].split("x"); + String[] s3 = s1[1].split("x"); + Disposition disposition = new Disposition(); + disposition.x = Integer.parseInt(s2[0]); + disposition.y = Integer.parseInt(s2[1]); + disposition.w = Integer.parseInt(s3[0]); + disposition.h = Integer.parseInt(s3[1]); + dispositions.add(disposition); + } + return dispositions; + } + } + + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java new file mode 100644 index 0000000..e8b6d2e --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A preference with a seekbar widget + */ +public class SeekBarPreference extends Preference implements OnSeekBarChangeListener { + + private int mProgress; + private int mMax; + private boolean mTrackingTouch; + + /** + * Constructor of <code>SeekBarPreference</code> + * + * @param context The current context + * @param attrs The attributes of the view + * @param defStyle The resource with the style + */ + public SeekBarPreference( + Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setMax(100); + setLayoutResource(R.layout.preference_widget_seekbar); + } + + /** + * Constructor of <code>SeekBarPreference</code> + * + * @param context The current context + * @param attrs The attributes of the view + */ + public SeekBarPreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor of <code>SeekBarPreference</code> + * + * @param context The current context + */ + public SeekBarPreference(Context context) { + this(context, null); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); + seekBar.setOnSeekBarChangeListener(this); + seekBar.setMax(mMax); + seekBar.setProgress(mProgress); + seekBar.setEnabled(isEnabled()); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("boxing") + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setProgress(restoreValue ? getPersistedInt(mProgress) : (Integer) defaultValue); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("boxing") + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInt(index, 0); + } + + /** + * Allows a Preference to intercept key events without having focus. + * For example, SeekBarPreference uses this to intercept +/- to adjust + * the progress. + * + * @param v The view + * @param keyCode The key code + * @param event The key event + * @return True if the Preference handled the key. Returns false by default. + */ + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_UP) { + if (keyCode == KeyEvent.KEYCODE_PLUS + || keyCode == KeyEvent.KEYCODE_EQUALS) { + setProgress(getProgress() + 1); + return true; + } + if (keyCode == KeyEvent.KEYCODE_MINUS) { + setProgress(getProgress() - 1); + return true; + } + } + return false; + } + + /** + * Method that set the maximum progress + * + * @param max The maximum progress + */ + public void setMax(int max) { + if (max != mMax) { + mMax = max; + notifyChanged(); + } + } + + /** + * Method that set the actual progress + * + * @param progress The actual progress + */ + public void setProgress(int progress) { + setProgress(progress, true); + } + + /** + * Method that set the actual progress + * + * @param progress The actual progress + * @param notifyChanged Whether notify if the progress was changed + */ + protected void setProgress(int progress, boolean notifyChanged) { + int p = progress; + if (p > mMax) { + p = mMax; + } + if (p < 0) { + p = 0; + } + if (p != mProgress) { + mProgress = p; + persistInt(p); + if (notifyChanged) { + notifyChanged(); + } + } + } + + /** + * Method that returns the current progress + * + * @return int The current progress + */ + public int getProgress() { + return mProgress; + } + + /** + * Persist the seekBar's progress value if callChangeListener + * + * returns boolean True, otherwise set the seekBar's progress to the stored value + */ + @SuppressWarnings("boxing") + void syncProgress(SeekBar seekBar) { + int progress = seekBar.getProgress(); + if (progress != mProgress) { + if (callChangeListener(progress)) { + setProgress(progress, false); + } else { + seekBar.setProgress(mProgress); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && !mTrackingTouch) { + syncProgress(seekBar); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mTrackingTouch = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mTrackingTouch = false; + if (seekBar.getProgress() != mProgress) { + syncProgress(seekBar); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Parcelable onSaveInstanceState() { + /* + * Suppose a client uses this preference type without persisting. We + * must save the instance state so it is able to, for example, survive + * orientation changes. + */ + + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + // Save the instance state + final SavedState myState = new SavedState(superState); + myState.progress = mProgress; + myState.max = mMax; + return myState; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + // Restore the instance state + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + mProgress = myState.progress; + mMax = myState.max; + notifyChanged(); + } + + /** + * SavedState, a subclass of {@link BaseSavedState}, will store the state + * of MyPreference, a subclass of Preference. + * <p> + * It is important to always call through to super methods. + */ + private static class SavedState extends BaseSavedState { + int progress; + int max; + + public SavedState(Parcel source) { + super(source); + + // Restore the click counter + progress = source.readInt(); + max = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + + // Save the click counter + dest.writeInt(progress); + dest.writeInt(max); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @SuppressWarnings({"unused", "hiding"}) + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java new file mode 100644 index 0000000..64b9b90 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.preferences; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A preference with a seekbar widget that display the progress + */ +public class SeekBarProgressPreference extends SeekBarPreference { + + /** + * Interface to intercept the progress value to display on screen + */ + public interface OnDisplayProgress { + /** + * Method invoked when a progress value is going to display on screen + * + * @param progress The real progress + * @return The progress to display + */ + String onDisplayProgress(int progress); + } + + private String mFormat; + private OnDisplayProgress mOnDisplayProgress; + + TextView mTextView; + + /** + * Constructor of <code>SeekBarProgressPreference</code> + * + * @param context The current context + * @param attrs The attributes of the view + * @param defStyle The resource with the style + */ + public SeekBarProgressPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Constructor of <code>SeekBarProgressPreference</code> + * + * @param context The current context + * @param attrs The attributes of the view + */ + public SeekBarProgressPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructor of <code>SeekBarProgressPreference</code> + * + * @param context The current context + */ + public SeekBarProgressPreference(Context context) { + super(context); + init(); + } + + /** + * Method that initializes the preference + */ + void init() { + mFormat = "%s"; + mOnDisplayProgress = null; + setWidgetLayoutResource(R.layout.preference_widget_seekbar_progress); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + mTextView = (TextView) view.findViewById(R.id.text); + setText(); + } + + /** + * Method that set the actual progress + * + * @param progress The actual progress + * @param notifyChanged Whether notify if the progress was changed + */ + @Override + protected void setProgress(int progress, boolean notifyChanged) { + super.setProgress(progress, notifyChanged); + setText(); + } + + /** + * Method that displays the progress value + */ + private void setText() { + if (mTextView != null) { + String value = String.valueOf(getProgress()); + if (mOnDisplayProgress != null) { + value = mOnDisplayProgress.onDisplayProgress(getProgress()); + } + mTextView.setText(String.format(mFormat, value)); + } + } + + /** + * Method that sets the callback to intercept the progress value before it will be + * displayed on screen. + * + * @param onDisplayProgress The callback + */ + public void setOnDisplayProgress(OnDisplayProgress onDisplayProgress) { + this.mOnDisplayProgress = onDisplayProgress; + } + + /** + * Method that set the format of the progress + * + * @param format The format of the string progress + */ + public void setFormat(String format) { + mFormat = format; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java new file mode 100644 index 0000000..027d089 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.shapes; + +import android.content.Context; +import android.opengl.GLES20; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil; +import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor; +import org.cyanogenmod.wallpapers.photophase.R; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * A shape plus color. + */ +public class ColorShape implements DrawableShape { + + private int mProgramHandler; + private int mPositionHandler; + private int mColorHandler; + private int mMatrixHandler; + private FloatBuffer mVertexBuffer; + private ShortBuffer mVertexOrderBuffer; + + private final GLColor mColor; + + /** + * Constructor of <code>ColorShape</code>. + * + * @param ctx The current context + * @param vertex The vertext data + * @param color The color + */ + public ColorShape(Context ctx, float[] vertex, GLColor color) { + super(); + mColor = color; + + mProgramHandler = + GLESUtil.createProgram( + ctx.getResources(), + R.raw.color_vertex_shader, + R.raw.color_fragment_shader); + mPositionHandler = GLES20.glGetAttribLocation(mProgramHandler, "aPosition"); + GLESUtil.glesCheckError("glGetAttribLocation"); + mColorHandler = GLES20.glGetAttribLocation(mProgramHandler, "aColor"); + GLESUtil.glesCheckError("glGetAttribLocation"); + mMatrixHandler = GLES20.glGetUniformLocation(mProgramHandler, "uMVPMatrix"); + GLESUtil.glesCheckError("glGetUniformLocation"); + + // Initialize vertex byte buffer for shape coordinates + ByteBuffer bb = ByteBuffer.allocateDirect(vertex.length * 4); // (# of coordinate values * 4 bytes per float) + bb.order(ByteOrder.nativeOrder()); + mVertexBuffer = bb.asFloatBuffer(); + mVertexBuffer.put(vertex); + mVertexBuffer.position(0); + + // Initialize vertex byte buffer for shape coordinates order + final short[] order = { 0, 1, 2, 0, 2, 3 }; + ByteBuffer dlb = ByteBuffer.allocateDirect(order.length * 2); // (# of coordinate values * 2 bytes per short) + dlb.order(ByteOrder.nativeOrder()); + mVertexOrderBuffer = dlb.asShortBuffer(); + mVertexOrderBuffer.put(order); + mVertexOrderBuffer.position(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void draw(float[] matrix) { + // Enable properties + if (mColor.a != 1.0f) { + GLES20.glEnable(GLES20.GL_BLEND); + GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_ALPHA); + } + + // Set the program and its attributes + GLES20.glUseProgram(mProgramHandler); + GLESUtil.glesCheckError("glUseProgram"); + + // Position + mVertexBuffer.position(0); + GLES20.glVertexAttribPointer( + mPositionHandler, + 3, + GLES20.GL_FLOAT, + false, + 3 * 4, + mVertexBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mPositionHandler); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Color + GLES20.glVertexAttrib4f(mColorHandler, mColor.r, mColor.g, mColor.b, mColor.a); + GLESUtil.glesCheckError("glVertexAttrib4f"); + + // Apply the projection and view transformation + GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, matrix, 0); + GLESUtil.glesCheckError("glUniformMatrix4fv"); + + // Draw the photo frame + mVertexOrderBuffer.position(0); + GLES20.glDrawElements( + GLES20.GL_TRIANGLE_FAN, + 6, + GLES20.GL_UNSIGNED_SHORT, + mVertexOrderBuffer); + GLESUtil.glesCheckError("glDrawElements"); + + // Disable attributes + GLES20.glDisableVertexAttribArray(mPositionHandler); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + GLES20.glDisableVertexAttribArray(mColorHandler); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + + // Disable properties + if (mColor.a != 1.0f) { + GLES20.glDisable(GLES20.GL_BLEND); + GLESUtil.glesCheckError("glDisable"); + } + } + + /** + * Method that sets the alpha color of the shape + * + * @param value The new alpha color of the shape + */ + public void setAlpha(float value) { + mColor.a = value; + } + + /** + * Method that destroy all the internal references + */ + public void recycle() { + if (GLES20.glIsProgram(mProgramHandler)) { + GLES20.glDeleteProgram(mProgramHandler); + GLESUtil.glesCheckError("glDeleteProgram"); + } + mProgramHandler = 0; + mPositionHandler = 0; + mColorHandler = 0; + mMatrixHandler = 0; + mVertexBuffer.clear(); + mVertexOrderBuffer.clear(); + mVertexBuffer = null; + mVertexOrderBuffer = null; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java new file mode 100644 index 0000000..7866dcc --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.shapes; + +/** + * An interface that defines an object as a drawable shape. + */ +public interface DrawableShape { + + /** + * Method that request redraw of the shape. + * + * @param matrix The model-view-projection matrix + */ + void draw(float[] matrix); +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java b/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java new file mode 100644 index 0000000..9668d22 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.tasks; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.widget.ImageView; + +import org.cyanogenmod.wallpapers.photophase.BitmapUtils; + +import java.io.File; + +/** + * A class for load images associated to a ImageView in background. + */ +public class AsyncPictureLoaderTask extends AsyncTask<File, Void, Drawable> { + + private final Context mContext; + private final ImageView mView; + + /** + * Constructor of <code>AsyncPictureLoaderTask</code> + * + * @param context The current context + * @param v The associated view + */ + public AsyncPictureLoaderTask(Context context, ImageView v) { + super(); + mContext = context; + mView = v; + } + + /** + * {@inheritDoc} + */ + @Override + protected Drawable doInBackground(File... params) { + int width = mView.getMeasuredWidth(); + int height = mView.getMeasuredHeight(); + Bitmap bitmap = BitmapUtils.decodeBitmap(params[0], width, height); + if (bitmap != null) { + return new BitmapDrawable(mContext.getResources(), bitmap); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPostExecute(Drawable result) { + mView.setImageDrawable(result); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java new file mode 100644 index 0000000..d88aafc --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; +import android.opengl.GLException; +import android.os.SystemClock; + +import org.cyanogenmod.wallpapers.photophase.Colors; +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.shapes.ColorShape; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +/** + * A transition that applies a fade transition to the picture. + */ +public class FadeTransition extends NullTransition { + + private static final float TRANSITION_TIME = 800.0f; + + private boolean mRunning; + private long mTime; + + ColorShape mOverlay; + + /** + * Constructor of <code>FadeTransition</code> + * + * @param ctx The current context + * @param tm The texture manager + */ + public FadeTransition(Context ctx, TextureManager tm) { + super(ctx, tm); + } + + /** + * {@inheritDoc} + */ + @Override + public TRANSITIONS getType() { + return TRANSITIONS.FADE; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasTransitionTarget() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return mRunning; + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + super.reset(); + mTime = -1; + mRunning = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void select(PhotoFrame target) { + super.select(target); + mOverlay = new ColorShape(mContext, target.getPictureVertex(), Colors.getBackground()); + mOverlay.setAlpha(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(float[] matrix) throws GLException { + // Check internal vars + if (mTarget == null || + mTarget.getPictureVertexBuffer() == null || + mTarget.getTextureBuffer() == null || + mTarget.getVertexOrderBuffer() == null) { + return; + } + if (mTransitionTarget == null || + mTransitionTarget.getPictureVertexBuffer() == null || + mTransitionTarget.getTextureBuffer() == null || + mTransitionTarget.getVertexOrderBuffer() == null) { + return; + } + + // Set the time the first time + if (mTime == -1) { + mTime = SystemClock.uptimeMillis(); + } + + final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME; + if (delta <= 0.5) { + // Draw the src target + draw(mTarget, matrix); + mOverlay.setAlpha(delta * 2.0f); + } else { + // Draw the dst target + draw(mTransitionTarget, matrix); + mOverlay.setAlpha((1 - delta) * 2.0f); + } + mOverlay.draw(matrix); + + // Transition ended + if (delta == 1) { + mRunning = false; + } + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java new file mode 100644 index 0000000..66f89d1 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; +import android.opengl.GLES20; +import android.opengl.GLException; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil; +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * A special transition that does nothing other than draw the {@link PhotoFrame} + * on the screen continually. No transition is done. + */ +public class NullTransition extends Transition { + + private static final int[] VERTEX_SHADER = {R.raw.default_vertex_shader}; + private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader}; + + /** + * Constructor of <code>NullTransition</code> + * + * @param ctx The current context + * @param tm The texture manager + */ + public NullTransition(Context ctx, TextureManager tm) { + super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER); + } + + /** + * {@inheritDoc} + */ + @Override + public void select(PhotoFrame target) { + super.select(target); + } + + /** + * {@inheritDoc} + */ + @Override + public TRANSITIONS getType() { + return TRANSITIONS.NO_TRANSITION; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasTransitionTarget() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return mTarget == null || !mTarget.isLoaded(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSelectable(PhotoFrame frame) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + // Nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(float[] matrix) throws GLException { + // Check internal vars + if (mTarget == null || + mTarget.getPictureVertexBuffer() == null || + mTarget.getTextureBuffer() == null || + mTarget.getVertexOrderBuffer() == null) { + return; + } + + // Draw the current target + draw(mTarget, matrix); + } + + /** + * Method that draws the picture texture + * + * @param target + * @param matrix The model-view-projection matrix + */ + protected void draw(PhotoFrame target, float[] matrix) { + // Set the program + useProgram(0); + + // Bind the texture + int textureHandle = target.getTextureHandle(); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLESUtil.glesCheckError("glActiveTexture"); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); + GLESUtil.glesCheckError("glBindTexture"); + + // Position + FloatBuffer vertexBuffer = target.getPictureVertexBuffer(); + vertexBuffer.position(0); + GLES20.glVertexAttribPointer( + mPositionHandlers[0], + PhotoFrame.COORDS_PER_VERTER, + GLES20.GL_FLOAT, + false, + PhotoFrame.COORDS_PER_VERTER * 4, + vertexBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mPositionHandlers[0]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Texture + FloatBuffer textureBuffer = target.getTextureBuffer(); + textureBuffer.position(0); + GLES20.glVertexAttribPointer( + mTextureCoordHandlers[0], + 2, + GLES20.GL_FLOAT, + false, + 2 * 4, + textureBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Apply the projection and view transformation + GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, matrix, 0); + GLESUtil.glesCheckError("glUniformMatrix4fv"); + + // Draw the photo frame + ShortBuffer vertexOrderBuffer = target.getVertexOrderBuffer(); + vertexOrderBuffer.position(0); + GLES20.glDrawElements( + GLES20.GL_TRIANGLE_FAN, + 6, + GLES20.GL_UNSIGNED_SHORT, + vertexOrderBuffer); + GLESUtil.glesCheckError("glDrawElements"); + + // Disable attributes + GLES20.glDisableVertexAttribArray(mPositionHandlers[0]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java new file mode 100644 index 0000000..d100110 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; +import android.opengl.GLException; +import android.os.SystemClock; + +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +/** + * A simple transition that swap an image after the transition time is ended. + */ +public class SwapTransition extends NullTransition { + + private static final float TRANSITION_TIME = 250.0f; + + private boolean mRunning; + private long mTime; + + /** + * Constructor of <code>SwapTransition</code> + * + * @param ctx The current context + * @param tm The texture manager + */ + public SwapTransition(Context ctx, TextureManager tm) { + super(ctx, tm); + } + + /** + * {@inheritDoc} + */ + @Override + public TRANSITIONS getType() { + return TRANSITIONS.SWAP; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasTransitionTarget() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return mRunning; + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + super.reset(); + mTime = -1; + mRunning = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(float[] matrix) throws GLException { + // Check internal vars + if (mTarget == null || + mTarget.getPictureVertexBuffer() == null || + mTarget.getTextureBuffer() == null || + mTarget.getVertexOrderBuffer() == null) { + return; + } + if (mTransitionTarget == null || + mTransitionTarget.getPictureVertexBuffer() == null || + mTransitionTarget.getTextureBuffer() == null || + mTransitionTarget.getVertexOrderBuffer() == null) { + return; + } + + // Set the time the first time + if (mTime == -1) { + mTime = SystemClock.uptimeMillis(); + } + + // Calculate the delta time + final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME; + + // Apply the transition + boolean ended = delta == 1; + draw(ended ? mTransitionTarget : mTarget, matrix); + mRunning = !ended; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java new file mode 100644 index 0000000..315a9f8 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; +import android.opengl.GLES20; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil; +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +/** + * The base class of all transitions that can be applied to the {@link PhotoFrame} classes. + */ +public abstract class Transition { + + public static final long MAX_TRANSTION_TIME = 1500L; + + protected final Context mContext; + private final TextureManager mTextureManager; + + protected int[] mProgramHandlers; + protected int[] mPositionHandlers; + protected int[] mTextureCoordHandlers; + protected int[] mMVPMatrixHandlers; + + protected PhotoFrame mTarget; + protected PhotoFrame mTransitionTarget; + + private final int[] mVertexShader; + private final int[] mFragmentShader; + + /** + * Constructor of <code>Transition</code> + * + * @param ctx The current context + * @param tm The current texture manager + * @param vertexShader The vertex shaders of the programs + * @param fragmentShader The fragment shaders of the programs + */ + public Transition(Context ctx, TextureManager tm, int[] vertexShader, int[] fragmentShader) { + super(); + mContext = ctx; + mTextureManager = tm; + mVertexShader = vertexShader; + mFragmentShader = fragmentShader; + + // Compile every program + assert mVertexShader.length != mFragmentShader.length; + int cc = mVertexShader.length; + mProgramHandlers = new int[cc]; + mPositionHandlers = new int[cc]; + mTextureCoordHandlers = new int[cc]; + mMVPMatrixHandlers = new int[cc]; + for (int i = 0; i < cc; i++) { + createProgram(i); + } + } + + /** + * Method that requests to apply this transition. + * + * @param target The target photo frame + */ + public void select(PhotoFrame target) { + mTarget = target; + if (hasTransitionTarget()) { + // Load the transition frame and request a picture for it + mTransitionTarget = + new PhotoFrame( + mContext, + mTextureManager, + mTarget.getFrameVertex(), + mTarget.getPictureVertex(), + mTarget.getBackgroundColor()); + } + } + + /** + * Method that returns the target of the transition. + * + * @return PhotoFrame The target of the transition + */ + public PhotoFrame getTarget() { + return mTarget; + } + + /** + * Method that returns the transition target of the transition. + * + * @return PhotoFrame The transition target of the transition + */ + public PhotoFrame getTransitionTarget() { + return mTransitionTarget; + } + + /** + * Method that returns if the transition is selectable for the passed frame. + * + * @param frame The frame which the transition should be applied to + * @return boolean If the transition is selectable for the passed frame + */ + public abstract boolean isSelectable(PhotoFrame frame); + + /** + * Method that resets the current status of the transition. + */ + public abstract void reset(); + + /** + * Method that returns the type of transition. + * + * @return TRANSITIONS The type of transition + */ + public abstract TRANSITIONS getType(); + + /** + * Method that requests to apply this transition. + * + * @param matrix The model-view-projection matrix + */ + public abstract void apply(float[] matrix); + + /** + * Method that returns if the transition is being transition. + * + * @return boolean If the transition is being transition. + */ + public abstract boolean isRunning(); + + /** + * Method that return if the transition has a secondary target + * + * @return boolean If the transition has a secondary target + */ + public abstract boolean hasTransitionTarget(); + + /** + * Method that creates the program + */ + protected void createProgram(int index) { + mProgramHandlers[index] = + GLESUtil.createProgram( + mContext.getResources(), mVertexShader[index], mFragmentShader[index]); + mPositionHandlers[index] = + GLES20.glGetAttribLocation(mProgramHandlers[index], "aPosition"); + GLESUtil.glesCheckError("glGetAttribLocation"); + mTextureCoordHandlers[index] = + GLES20.glGetAttribLocation(mProgramHandlers[index], "aTextureCoord"); + GLESUtil.glesCheckError("glGetAttribLocation"); + mMVPMatrixHandlers[index] = + GLES20.glGetUniformLocation(mProgramHandlers[index], "uMVPMatrix"); + GLESUtil.glesCheckError("glGetUniformLocation"); + } + + /** + * Method that set the program to use + * + * @param index The index of the program to use + */ + protected void useProgram(int index) { + if (!GLES20.glIsProgram(mProgramHandlers[index])) { + createProgram(index); + } + GLES20.glUseProgram(mProgramHandlers[index]); + GLESUtil.glesCheckError("glUseProgram(" + index + ")"); + } + + /** + * Method that requests to the transition to remove its internal references and resources. + */ + public void recycle() { + int cc = mProgramHandlers.length; + for (int i = 0; i < cc; i++) { + if (GLES20.glIsProgram(mProgramHandlers[i])) { + GLES20.glDeleteProgram(mProgramHandlers[i]); + GLESUtil.glesCheckError("glDeleteProgram"); + } + mProgramHandlers[i] = 0; + mPositionHandlers[i] = 0; + mTextureCoordHandlers[i] = 0; + mMVPMatrixHandlers[i] = 0; + } + mTransitionTarget = null; + mTarget = null; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java new file mode 100644 index 0000000..e29bfce --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; + +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; + + +/** + * A class that manages all the supported transitions + */ +public class Transitions { + + /** + * Enumeration of the supported transitions + */ + public enum TRANSITIONS { + /** + * A random combination of all supported transitions + */ + RANDOM, + /** + * @see NullTransition + */ + NO_TRANSITION, + /** + * @see SwapTransition + */ + SWAP, + /** + * @see FadeTransition + */ + FADE, + /** + * @see TranslateTransition + */ + TRANSLATION; + } + + /** + * Method that return the next type of transition to apply the picture. + * + * @param frame The frame which the effect will be applied to + * @return TRANSITIONS The next type of transition to apply + */ + public static TRANSITIONS getNextTypeOfTransition(PhotoFrame frame) { + int transition = Preferences.General.Transitions.getTransitionTypes(); + if (transition == TRANSITIONS.RANDOM.ordinal()) { + int low = TRANSITIONS.SWAP.ordinal(); + int hight = TRANSITIONS.values().length - 1; + transition = low + (int)(Math.random() * ((hight - low) + 1)); + } + if (transition == TRANSITIONS.SWAP.ordinal()) { + return TRANSITIONS.SWAP; + } + if (transition == TRANSITIONS.FADE.ordinal()) { + return TRANSITIONS.FADE; + } + if (transition == TRANSITIONS.TRANSLATION.ordinal()) { + return TRANSITIONS.TRANSLATION; + } + return TRANSITIONS.NO_TRANSITION; + } + + /** + * Method that creates a new transition. + * + * @param ctx The current context + * @param tm The texture manager + * @param type The type of transition + * @param frame The frame which the effect will be applied to + * @return Transition The next transition to apply + */ + public static Transition createTransition( + Context ctx, TextureManager tm, TRANSITIONS type, PhotoFrame frame) { + if (type.compareTo(TRANSITIONS.SWAP) == 0) { + return new SwapTransition(ctx, tm); + } + if (type.compareTo(TRANSITIONS.FADE) == 0) { + return new FadeTransition(ctx, tm); + } + if (type.compareTo(TRANSITIONS.TRANSLATION) == 0) { + return new TranslateTransition(ctx, tm); + } + return new NullTransition(ctx, tm); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java new file mode 100644 index 0000000..4e32a4a --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.transitions; + +import android.content.Context; +import android.opengl.GLES20; +import android.opengl.GLException; +import android.opengl.Matrix; +import android.os.SystemClock; + +import org.cyanogenmod.wallpapers.photophase.GLESUtil; +import org.cyanogenmod.wallpapers.photophase.PhotoFrame; +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.TextureManager; +import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A transition that applies a translation transition to the picture. + */ +public class TranslateTransition extends Transition { + + /** + * The enumeration of all possibles translations movements + */ + public enum TRANSLATE_MODES { + /** + * Translate the picture from left to right + */ + LEFT_TO_RIGHT, + /** + * Translate the picture from right to left + */ + RIGHT_TO_LEFT, + /** + * Translate the picture from up to down + */ + UP_TO_DOWN, + /** + * Translate the picture from down to up + */ + DOWN_TO_UP + } + + private static final int[] VERTEX_SHADER = {R.raw.translate_vertex_shader, R.raw.default_vertex_shader}; + private static final int[] FRAGMENT_SHADER = {R.raw.translate_fragment_shader, R.raw.default_fragment_shader}; + + private static final float TRANSITION_TIME = 800.0f; + + private TRANSLATE_MODES mMode; + + private boolean mRunning; + private long mTime; + + /** + * Constructor of <code>TranslateTransition</code> + * + * @param ctx The current context + * @param tm The texture manager + */ + public TranslateTransition(Context ctx, TextureManager tm) { + super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER); + + // Initialized + reset(); + } + + /** + * {@inheritDoc} + */ + @Override + public TRANSITIONS getType() { + return TRANSITIONS.TRANSLATION; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasTransitionTarget() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return mRunning; + } + + /** + * {@inheritDoc} + */ + @Override + public void select(PhotoFrame target) { + super.select(target); + + // Discard all non-supported modes + List<TRANSLATE_MODES> modes = + new ArrayList<TranslateTransition.TRANSLATE_MODES>( + Arrays.asList(TRANSLATE_MODES.values())); + float[] vertex = target.getFrameVertex(); + if (vertex[0] != -1.0f) { + modes.remove(TRANSLATE_MODES.RIGHT_TO_LEFT); + } + if (vertex[9] != 1.0f) { + modes.remove(TRANSLATE_MODES.LEFT_TO_RIGHT); + } + if (vertex[1] != 1.0f) { + modes.remove(TRANSLATE_MODES.DOWN_TO_UP); + } + if (vertex[4] != -1.0f) { + modes.remove(TRANSLATE_MODES.UP_TO_DOWN); + } + + // Random mode + int low = 0; + int hight = modes.size() - 1; + mMode = modes.get(low + (int)(Math.random() * ((hight - low) + 1))); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSelectable(PhotoFrame frame) { + float[] vertex = frame.getFrameVertex(); + if (vertex[0] == -1.0f || vertex[9] == 1.0f || + vertex[1] == 1.0f || vertex[4] == -1.0f) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void reset() { + mTime = -1; + mRunning = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(float[] matrix) throws GLException { + // Check internal vars + if (mTarget == null || + mTarget.getPictureVertexBuffer() == null || + mTarget.getTextureBuffer() == null || + mTarget.getVertexOrderBuffer() == null) { + return; + } + if (mTransitionTarget == null || + mTransitionTarget.getPictureVertexBuffer() == null || + mTransitionTarget.getTextureBuffer() == null || + mTransitionTarget.getVertexOrderBuffer() == null) { + return; + } + + // Set the time the first time + if (mTime == -1) { + mTime = SystemClock.uptimeMillis(); + } + + // Calculate the delta time + final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME; + + // Apply the transition + applyTransitionToTransitionTarget(delta, matrix); + if (delta < 1) { + applyTransitionToTarget(delta, matrix); + } + + // Transition ending + if (delta == 1) { + mRunning = false; + } + } + + /** + * Apply the transition to the passed frame + * + * @param delta The delta time + * @param matrix The model-view-projection matrix + */ + private void applyTransitionToTarget(float delta, float[] matrix) { + // Set the program + useProgram(0); + + // Bind the texture + int textureHandle = mTarget.getTextureHandle(); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLESUtil.glesCheckError("glActiveTexture"); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); + GLESUtil.glesCheckError("glBindTexture"); + + // Position + FloatBuffer vertexBuffer = mTarget.getPictureVertexBuffer(); + vertexBuffer.position(0); + GLES20.glVertexAttribPointer( + mPositionHandlers[0], + PhotoFrame.COORDS_PER_VERTER, + GLES20.GL_FLOAT, + false, + PhotoFrame.COORDS_PER_VERTER * 4, + vertexBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mPositionHandlers[0]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Texture + FloatBuffer textureBuffer = mTarget.getTextureBuffer(); + textureBuffer.position(0); + GLES20.glVertexAttribPointer( + mTextureCoordHandlers[0], + 2, + GLES20.GL_FLOAT, + false, + 2 * 4, + textureBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Calculate the delta distance + float distance = + (mMode.compareTo(TRANSLATE_MODES.LEFT_TO_RIGHT) == 0 || mMode.compareTo(TRANSLATE_MODES.RIGHT_TO_LEFT) == 0) + ? mTarget.getPictureWidth() + : mTarget.getPictureHeight(); + if (mMode.compareTo(TRANSLATE_MODES.RIGHT_TO_LEFT) == 0 || mMode.compareTo(TRANSLATE_MODES.DOWN_TO_UP) == 0) { + distance *= -1; + } + distance *= delta; + boolean vertical = (mMode.compareTo(TRANSLATE_MODES.UP_TO_DOWN) == 0 || mMode.compareTo(TRANSLATE_MODES.DOWN_TO_UP) == 0); + + // Apply the projection and view transformation + float[] translationMatrix = new float[16]; + if (vertical) { + Matrix.translateM(translationMatrix, 0, matrix, 0, 0.0f, distance, 0.0f); + } else { + Matrix.translateM(translationMatrix, 0, matrix, 0, distance, 0.0f, 0.0f); + } + GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, translationMatrix, 0); + GLESUtil.glesCheckError("glUniformMatrix4fv"); + + // Draw the photo frame + ShortBuffer vertexOrderBuffer = mTarget.getVertexOrderBuffer(); + vertexOrderBuffer.position(0); + GLES20.glDrawElements( + GLES20.GL_TRIANGLE_FAN, + 6, + GLES20.GL_UNSIGNED_SHORT, + vertexOrderBuffer); + GLESUtil.glesCheckError("glDrawElements"); + + // Disable attributes + GLES20.glDisableVertexAttribArray(mPositionHandlers[0]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + } + + /** + * Apply the transition to the passed frame + * + * @param delta The delta time + * @param matrix The model-view-projection matrix + */ + private void applyTransitionToTransitionTarget(float delta, float[] matrix) { + // Set the program + useProgram(1); + + // Bind the texture + int textureHandle = mTransitionTarget.getTextureHandle(); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLESUtil.glesCheckError("glActiveTexture"); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); + GLESUtil.glesCheckError("glBindTexture"); + + // Position + FloatBuffer vertexBuffer = mTransitionTarget.getPictureVertexBuffer(); + vertexBuffer.position(0); + GLES20.glVertexAttribPointer( + mPositionHandlers[1], + PhotoFrame.COORDS_PER_VERTER, + GLES20.GL_FLOAT, + false, + PhotoFrame.COORDS_PER_VERTER * 4, + vertexBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mPositionHandlers[1]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Texture + FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer(); + textureBuffer.position(0); + GLES20.glVertexAttribPointer( + mTextureCoordHandlers[1], + 2, + GLES20.GL_FLOAT, + false, + 2 * 4, + textureBuffer); + GLESUtil.glesCheckError("glVertexAttribPointer"); + GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]); + GLESUtil.glesCheckError("glEnableVertexAttribArray"); + + // Apply the projection and view transformation + float[] translationMatrix = new float[16]; + Matrix.translateM(translationMatrix, 0, matrix, 0, 0.0f, 0.0f, 0.0f); + GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, translationMatrix, 0); + GLESUtil.glesCheckError("glUniformMatrix4fv"); + + // Draw the photo frame + ShortBuffer vertexOrderBuffer = mTransitionTarget.getVertexOrderBuffer(); + vertexOrderBuffer.position(0); + GLES20.glDrawElements( + GLES20.GL_TRIANGLE_FAN, + 6, + GLES20.GL_UNSIGNED_SHORT, + vertexOrderBuffer); + GLESUtil.glesCheckError("glDrawElements"); + + // Disable attributes + GLES20.glDisableVertexAttribArray(mPositionHandlers[1]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[1]); + GLESUtil.glesCheckError("glDisableVertexAttribArray"); + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java new file mode 100644 index 0000000..0711297 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.widgets; + +import android.content.Context; +import android.content.res.Resources; +import android.os.AsyncTask.Status; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.model.Album; +import org.cyanogenmod.wallpapers.photophase.tasks.AsyncPictureLoaderTask; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * A view that contains the info about an album + */ +public class AlbumInfo extends RelativeLayout + implements OnClickListener, OnMenuItemClickListener { + + /** + * A convenient listener for receive events of the AlbumPictures class + * + */ + public interface CallbacksListener { + /** + * Invoked when an album was selected + * + * @param album The album + */ + void onAlbumSelected(Album album); + + /** + * Invoked when an album was deselected + * + * @param album The album + */ + void onAlbumDeselected(Album album); + } + + private List<CallbacksListener> mCallbacks; + + /*package*/ Album mAlbum; + + /*package*/ AsyncPictureLoaderTask mTask; + + /*package*/ ImageView mIcon; + private TextView mSelectedItems; + private TextView mName; + private TextView mItems; + private View mOverflowButton; + + /** + * Constructor of <code>AlbumInfo</code>. + * + * @param context The current context + */ + public AlbumInfo(Context context) { + super(context); + init(); + } + + /** + * Constructor of <code>AlbumInfo</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public AlbumInfo(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructor of <code>AlbumInfo</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public AlbumInfo(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the internal references + */ + private void init() { + mCallbacks = new ArrayList<AlbumInfo.CallbacksListener>(); + } + + /** + * Method that adds the class that will be listen for events of this class + * + * @param callback The callback class + */ + public void addCallBackListener(CallbacksListener callback) { + this.mCallbacks.add(callback); + } + + /** + * Method that removes the class from the current callbacks + * + * @param callback The callback class + */ + public void removeCallBackListener(CallbacksListener callback) { + this.mCallbacks.remove(callback); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + mIcon = (ImageView)findViewById(R.id.album_thumbnail); + mSelectedItems = (TextView)findViewById(R.id.album_selected_items); + mName = (TextView)findViewById(R.id.album_name); + mItems = (TextView)findViewById(R.id.album_items); + mOverflowButton = findViewById(R.id.overflow); + mOverflowButton.setOnClickListener(this); + + updateView(mAlbum); + + post(new Runnable() { + @Override + public void run() { + // Show as icon, the first picture + mTask = new AsyncPictureLoaderTask(getContext(), mIcon); + mTask.execute(new File(mAlbum.getItems().get(0))); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + // Cancel pending tasks + if (mTask.getStatus().compareTo(Status.PENDING) == 0) { + mTask.cancel(true); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + if (v.equals(mOverflowButton)) { + PopupMenu popup = new PopupMenu(getContext(), v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.album_actions, popup.getMenu()); + onPreparePopupMenu(popup.getMenu()); + popup.setOnMenuItemClickListener(this); + popup.show(); + return; + } + } + + /** + * Method called prior to show the popup menu + * + * @param popup The popup menu + */ + public void onPreparePopupMenu(Menu popup) { + if (isSelected()) { + popup.findItem(R.id.mnu_select_album).setVisible(false); + } else { + popup.findItem(R.id.mnu_deselect_album).setVisible(false); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.mnu_select_album: + doSelection(true); + break; + + case R.id.mnu_deselect_album: + doSelection(false); + break; + + default: + return false; + } + return true; + } + + /** + * Method that select/deselect the album + * + * @param selected whether the album is selected + */ + public void doSelection(boolean selected) { + setSelected(selected); + mAlbum.setSelected(selected); + mAlbum.setSelectedItems(new ArrayList<String>()); + updateView(mAlbum); + notifySelectionChanged(); + } + + /** + * Method that notifies to all the registered callbacks that the selection + * was changed + */ + private void notifySelectionChanged() { + for (CallbacksListener callback : mCallbacks) { + if (mAlbum.isSelected()) { + callback.onAlbumSelected(mAlbum); + } else { + callback.onAlbumDeselected(mAlbum); + } + } + } + + /** + * Method that updates the view + * + * @param album The album data + */ + @SuppressWarnings("boxing") + public void updateView(Album album) { + mAlbum = album; + + if (mIcon != null) { + Resources res = getContext().getResources(); + + String count = String.valueOf(mAlbum.getSelectedItems().size()); + if (mAlbum.getItems().size() > 99) { + count += "+"; + } + mSelectedItems.setText(count); + mSelectedItems.setVisibility(mAlbum.isSelected() ? View.INVISIBLE : View.VISIBLE); + mName.setText(mAlbum.getName()); + int items = mAlbum.getItems().size(); + mItems.setText(String.format(res.getQuantityText( + R.plurals.album_number_of_pictures, items).toString(), items)); + setSelected(album.isSelected()); + } + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java new file mode 100644 index 0000000..324fe15 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.widgets; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.model.Album; + +import java.util.ArrayList; +import java.util.List; + +/** + * A view that contains the pictures of an album + */ +public class AlbumPictures extends RelativeLayout + implements OnClickListener, OnMenuItemClickListener { + + private static final int SELECTION_SELECT_ALL = 1; + private static final int SELECTION_DESELECT_ALL = 2; + private static final int SELECTION_INVERT = 3; + + /** + * A convenient listener for receive events of the AlbumPictures class + * + */ + public interface CallbacksListener { + /** + * Invoked when the user pressed the back button + */ + void onBackButtonClick(View v); + + /** + * Invoked when the selection was changed + * + * @param album The album + */ + void onSelectionChanged(Album album); + } + + private List<CallbacksListener> mCallbacks; + + private PicturesView mScroller; + private LinearLayout mHolder; + private View mBackButton; + private View mOverflowButton; + + private Album mAlbum; + + /** + * Constructor of <code>AlbumPictures</code>. + * + * @param context The current context + */ + public AlbumPictures(Context context) { + super(context); + init(); + } + + /** + * Constructor of <code>AlbumPictures</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public AlbumPictures(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructor of <code>AlbumPictures</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public AlbumPictures(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the internal references + */ + private void init() { + mCallbacks = new ArrayList<AlbumPictures.CallbacksListener>(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mScroller = (PicturesView)findViewById(R.id.album_pictures_scroller); + mHolder = (LinearLayout)findViewById(R.id.album_pictures_holder); + mBackButton = findViewById(R.id.back); + mBackButton.setOnClickListener(this); + mOverflowButton = findViewById(R.id.overflow); + mOverflowButton.setOnClickListener(this); + TextView title = (TextView)findViewById(R.id.album_pictures_title); + title.setText(mAlbum.getName()); + + updateView(mAlbum); + } + + /** + * Method that adds the class that will be listen for events of this class + * + * @param callback The callback class + */ + public void addCallBackListener(CallbacksListener callback) { + this.mCallbacks.add(callback); + } + + /** + * Method that removes the class from the current callbacks + * + * @param callback The callback class + */ + public void removeCallBackListener(CallbacksListener callback) { + this.mCallbacks.remove(callback); + } + + /** + * Method that set the data of the view + * + * @param album The album data + */ + public void updateView(Album album) { + mAlbum = album; + + if (mHolder != null) { + // Create the pictures + final LayoutInflater inflater = (LayoutInflater) getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mScroller.cancelTasks(); + mHolder.removeAllViews(); + for (final String picture : mAlbum.getItems()) { + View v = createPicture(inflater, picture, isPictureSelected(picture)); + mHolder.addView(v); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + // Check which is the view pressed + if (v.equals(mBackButton)) { + for (CallbacksListener callback : mCallbacks) { + callback.onBackButtonClick(v); + } + return; + } + if (v.equals(mOverflowButton)) { + PopupMenu popup = new PopupMenu(getContext(), v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.pictures_actions, popup.getMenu()); + popup.setOnMenuItemClickListener(this); + popup.show(); + return; + } + + // A picture view + v.setSelected(!v.isSelected()); + notifySelectionChanged(); + } + + /** + * Method that notifies to all the registered callbacks that the selection + * was changed + */ + private void notifySelectionChanged() { + List<String> selection = new ArrayList<String>(); + int count = mHolder.getChildCount(); + for (int i = 0; i < count; i++) { + View v = mHolder.getChildAt(i); + if (v.isSelected()) { + selection.add((String)v.getTag()); + } + } + mAlbum.setSelectedItems(selection); + mAlbum.setSelected(false); + + for (CallbacksListener callback : mCallbacks) { + callback.onSelectionChanged(mAlbum); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.mnu_select_all: + doSelection(SELECTION_SELECT_ALL); + break; + + case R.id.mnu_deselect_all: + doSelection(SELECTION_DESELECT_ALL); + break; + + case R.id.mnu_invert_selection: + doSelection(SELECTION_INVERT); + break; + + default: + return false; + } + return true; + } + + /** + * Operate over the selection of the pictures of this album. + * + * @param action Takes the next values: + * <ul> + * <li>SELECTION_SELECT_ALL: select all</li> + * <li>SELECTION_DESELECT_ALL: deselect all</li> + * <li>SELECTION_INVERT: invert selection</li> + * </ul> + */ + private void doSelection(int action) { + int count = mHolder.getChildCount(); + for (int i = 0; i < count; i++) { + View v = mHolder.getChildAt(i); + + boolean selected = true; + if (action == SELECTION_DESELECT_ALL) { + selected = false; + } else if (action == SELECTION_INVERT) { + selected = !v.isSelected(); + } + v.setSelected(selected); + } + notifySelectionChanged(); + } + + /** + * Method invoked when the view is displayed + */ + public void onShow() { + mScroller.requestLoadOfPendingPictures(); + } + + /** + * Method that creates a new picture view + * + * @param inflater The inflater of the parent view + * @param picture The path of the picture + * @param selected If the picture is selected + */ + private View createPicture(LayoutInflater inflater, String picture, boolean selected) { + final View v = inflater.inflate(R.layout.picture_item, mHolder, false); + v.setTag(picture); + v.setSelected(selected); + v.setOnClickListener(this); + return v; + } + + /** + * Method that check if a picture is selected + * + * @param picture The picture to check + * @return boolean whether the picture is selected + */ + private boolean isPictureSelected(String picture) { + for (String item : mAlbum.getSelectedItems()) { + if (item.compareTo(picture) == 0) { + return true; + } + } + return false; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java new file mode 100644 index 0000000..386ffbe --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.widgets; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.widget.LinearLayout; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A "Google Now Card Layout" like layout + */ +public class CardLayout extends LinearLayout { + + boolean inverted = false; + + /** + * Constructor of <code>CardLayout</code>. + * + * @param context The current context + */ + public CardLayout(Context context) { + super(context); + } + + /** + * Constructor of <code>CardLayout</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public CardLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Constructor of <code>CardLayout</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public CardLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (isHardwareAccelerated()) { + setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + } + + /** + * Add a new card to the layout + * + * @param card The card view to add + */ + public void addCard(final View card) { + post(new Runnable() { + @Override + public void run() { + addView(card); + card.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.cards_animation)); + } + }); + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java new file mode 100644 index 0000000..42e701d --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.widgets; + +import afzkl.development.mColorPicker.views.ColorDialogView; +import afzkl.development.mColorPicker.views.ColorPanelView; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; + +import org.cyanogenmod.wallpapers.photophase.R; + +/** + * A {@link Preference} that allow to select/pick a color in a new window dialog. + */ +public class ColorPickerPreference extends DialogPreference { + + private ColorPanelView mColorPicker; + private int mColor; + + private ColorDialogView mColorDlg; + + /** + * Constructor of <code>ColorPickerPreference</code> + * + * @param context The current context + */ + public ColorPickerPreference(Context context) { + this(context, null); + } + + /** + * Constructor of <code>ColorPickerPreference</code> + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the preference. + */ + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setWidgetLayoutResource(R.layout.color_picker_pref_item); + } + + /** + * Returns the color of the picker. + * + * @return The color of the picker. + */ + public int getColor() { + return this.mColor; + } + + /** + * Sets the color of the picker and saves it to the {@link SharedPreferences}. + * + * @param color The new color. + */ + public void setColor(int color) { + // Always persist/notify the first time; don't assume the field's default of false. + final boolean changed = this.mColor != color; + if (changed) { + this.mColor = color; + // when called from onSetInitialValue the view is still not set + if (this.mColorPicker != null) { + this.mColorPicker.setColor(color); + } + persistInt(color); + if (changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return Integer.valueOf(a.getColor(index, 0)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setColor(restoreValue ? getPersistedInt(0) : ((Integer)defaultValue).intValue()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPrepareDialogBuilder(Builder builder) { + super.onPrepareDialogBuilder(builder); + + // Configure the dialog + this.mColorDlg = new ColorDialogView(getContext()); + this.mColorDlg.setColor(this.mColor); + this.mColorDlg.showAlphaSlider(true); + this.mColorDlg.setAlphaSliderText( + getContext().getString(R.string.color_picker_alpha_slider_text)); + this.mColorDlg.setCurrentColorText( + getContext().getString(R.string.color_picker_current_text)); + this.mColorDlg.setNewColorText( + getContext().getString(R.string.color_picker_new_text)); + this.mColorDlg.setColorLabelText( + getContext().getString(R.string.color_picker_color)); + builder.setView(this.mColorDlg); + + // The color is selected by the user and confirmed by clicking ok + builder.setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + @SuppressWarnings("synthetic-access") + public void onClick(DialogInterface dialog, int which) { + int color = ColorPickerPreference.this.mColorDlg.getColor(); + if (callChangeListener(Integer.valueOf(color))) { + setColor(color); + } + dialog.dismiss(); + } + }); + } + + + /** + * {@inheritDoc} + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + View v = view.findViewById(R.id.color_picker); + if (v != null && v instanceof ColorPanelView) { + this.mColorPicker = (ColorPanelView)v; + this.mColorPicker.setColor(this.mColor); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.color = getColor(); + return myState; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + setColor(myState.color); + } + + /** + * A class for managing the instance state of a {@link ColorPickerPreference}. + */ + static class SavedState extends BaseSavedState { + int color; + + /** + * Constructor of <code>SavedState</code> + * + * @param source The source + */ + public SavedState(Parcel source) { + super(source); + this.color = source.readInt(); + } + + /** + * Constructor of <code>SavedState</code> + * + * @param superState The parcelable state + */ + public SavedState(Parcelable superState) { + super(superState); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.color); + } + + /** + * A class that generates instances of the <code>SavedState</code> class from a Parcel. + */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + /** + * {@inheritDoc} + */ + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + /** + * {@inheritDoc} + */ + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java new file mode 100644 index 0000000..bb9332c --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.widgets; + +import android.content.Context; +import android.graphics.Rect; +import android.os.AsyncTask.Status; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; + +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.tasks.AsyncPictureLoaderTask; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; + +/** + * A view that contains all the pictures of an album + */ +public class PicturesView extends HorizontalScrollView { + + private HashMap<File, AsyncPictureLoaderTask> mTasks; + + /** + * Constructor of <code>PicturesView</code>. + * + * @param context The current context + */ + public PicturesView(Context context) { + super(context); + init(); + } + + /** + * Constructor of <code>PicturesView</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public PicturesView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructor of <code>PicturesView</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public PicturesView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the structures of this class + */ + private void init() { + mTasks = new HashMap<File, AsyncPictureLoaderTask>(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + cancelTasks(); + } + + /** + * Method that removes all tasks + */ + public void cancelTasks() { + // Cancel all the pending task + Iterator<AsyncPictureLoaderTask> it = mTasks.values().iterator(); + while (it.hasNext()) { + AsyncPictureLoaderTask task = it.next(); + if (task.getStatus().compareTo(Status.PENDING) == 0) { + task.cancel(true); + } + } + mTasks.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + requestLoadOfPendingPictures(); + } + + /** + * Method that load in background all visible and pending pictures + */ + public void requestLoadOfPendingPictures() { + // Get the visible rect + Rect r = new Rect(); + getHitRect(r); + + // Get all the image views + ViewGroup vg = (ViewGroup)getChildAt(0); + int count = vg.getChildCount(); + for (int i = 0; i < count; i++) { + ViewGroup picView = (ViewGroup)vg.getChildAt(i); + File image = new File((String)picView.getTag()); + if (picView.getLocalVisibleRect(r) && !mTasks.containsKey(image)) { + ImageView iv = (ImageView)picView.findViewById(R.id.picture_thumbnail); + AsyncPictureLoaderTask task = new AsyncPictureLoaderTask(getContext(), iv); + task.execute(image); + mTasks.put(image, task); + } + } + } +} |