aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java96
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java134
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/Colors.java58
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java117
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java207
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java50
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java64
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java498
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java221
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java239
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java309
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java136
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java447
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java175
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java353
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java70
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureManager.java384
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java32
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/Utils.java41
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java147
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java82
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java53
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java33
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java68
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java34
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java56
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/model/Album.java97
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java337
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java86
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java144
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java44
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java129
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java85
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java342
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java318
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java145
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java168
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java30
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java71
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java137
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java182
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java113
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java202
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java104
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java354
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java281
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java308
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java92
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java253
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java139
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);
+ }
+ }
+ }
+}