aboutsummaryrefslogtreecommitdiffstats
path: root/src/org
diff options
context:
space:
mode:
authorJorge Ruesga <jorge@ruesga.com>2013-10-27 13:46:12 +0100
committerJorge Ruesga <jorge@ruesga.com>2013-10-27 13:46:12 +0100
commita9b27719b9f448879fb5ac821ee7ac1cf22b33ce (patch)
tree935967d50d639244786802b849ad15d1af798dd4 /src/org
parent49cba5cf09cf0e4f3b533f163ea183b83e617986 (diff)
downloadandroid_packages_wallpapers_PhotoPhase-a9b27719b9f448879fb5ac821ee7ac1cf22b33ce.tar.gz
android_packages_wallpapers_PhotoPhase-a9b27719b9f448879fb5ac821ee7ac1cf22b33ce.tar.bz2
android_packages_wallpapers_PhotoPhase-a9b27719b9f448879fb5ac821ee7ac1cf22b33ce.zip
Returns as a CyanogenMod project. Update author and Copyrights
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
Diffstat (limited to 'src/org')
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java101
-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/GLESWallpaperService.java230
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java307
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java296
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java171
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java661
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java236
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java419
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java82
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureManager.java595
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java41
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java164
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/Evaluators.java79
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java82
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/BlurEffect.java60
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java338
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/EmbossEffect.java68
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/GlowEffect.java69
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/HalftoneEffect.java128
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/MirrorEffect.java63
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java61
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/OutlineEffect.java74
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java268
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffectFactory.java124
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/PixelateEffect.java118
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/PopArtEffect.java64
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/ScanlinesEffect.java59
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/model/Album.java110
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/model/Disposition.java118
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java496
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java217
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java160
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java92
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java44
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java131
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java85
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java90
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java459
-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/preferences/TouchAction.java72
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java150
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java30
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java292
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java71
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/CubeTransition.java479
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java135
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/FlipTransition.java257
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java171
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java111
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java207
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java161
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java334
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/WindowTransition.java376
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java147
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/DispositionUtil.java115
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java531
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/MERAlgorithm.java134
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/Utils.java83
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java284
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java345
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java91
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java253
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java768
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java164
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/ResizeFrame.java301
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/VerticalEndlessScroller.java114
72 files changed, 14095 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..5b3aff1
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.provider.Settings;
+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) {
+ // CyanogenMod specific featured (DO NOT RELAY IN INTERNAL VARS)
+ boolean hiddenStatusBar =
+ Settings.System.getInt(context.getContentResolver(), "expanded_desktop_state", 0) == 1 &&
+ Settings.System.getInt(context.getContentResolver(), "expanded_desktop_style", 0) == 2;
+ int result = 0;
+ if (!hiddenStatusBar && !(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/Colors.java b/src/org/cyanogenmod/wallpapers/photophase/Colors.java
new file mode 100644
index 0000000..925e058
--- /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.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+/**
+ * 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 = 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/GLESWallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java
new file mode 100644
index 0000000..8ddb443
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java
@@ -0,0 +1,230 @@
+/*
+ * 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);
+ }
+
+ /**
+ * Returns the renderer used by the engine
+ *
+ * @return Renderer The renderer
+ */
+ protected Renderer getRenderer() {
+ return mRenderer;
+ }
+
+ /**
+ * {@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..4228abf
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java
@@ -0,0 +1,307 @@
+/*
+ * 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.os.AsyncTask.Status;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+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 starting to fetch data
+ *
+ * @param userRequest If the user requested this media discovery
+ */
+ void onStartMediaDiscovered(boolean userRequest);
+ /**
+ * Called when the all the data is ready
+ *
+ * @param images All the images paths found
+ * @param userRequest If the user requested this media discovery
+ */
+ void onEndMediaDiscovered(File[] images, boolean userRequest);
+ /**
+ * Called when the partial data is ready
+ *
+ * @param images All the images paths found
+ * @param userRequest If the user requested this media discovery
+ */
+ void onPartialMediaDiscovered(File[] images, boolean userRequest);
+ }
+
+ /**
+ * The asynchronous task for query the MediaStore
+ */
+ private class AsyncDiscoverTask extends AsyncTask<Void, File, List<File>> {
+
+ private final ContentResolver mFinalContentResolver;
+ private final OnMediaPictureDiscoveredListener mFinalCallback;
+ private final Set<String> mFilter;
+ private final Set<String> mLastAlbums;
+ private final Set<String> mNewAlbums;
+ private final boolean mIsAutoSelectNewAlbums;
+ private final boolean mUserRequest;
+
+ /**
+ * Constructor of <code>AsyncDiscoverTask</code>
+ *
+ * @param cr The {@link ContentResolver}
+ * @param cb The {@link OnMediaPictureDiscoveredListener} listener
+ * @param userRequest If the request was generated by the user
+ */
+ public AsyncDiscoverTask(
+ ContentResolver cr, OnMediaPictureDiscoveredListener cb, boolean userRequest) {
+ super();
+ mFinalContentResolver = cr;
+ mFinalCallback = cb;
+ mFilter = Preferences.Media.getSelectedMedia();
+ mLastAlbums = Preferences.Media.getLastDiscorevedAlbums();
+ mIsAutoSelectNewAlbums = Preferences.Media.isAutoSelectNewAlbums();
+ mNewAlbums = new HashSet<String>();
+ mUserRequest = userRequest;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<File> doInBackground(Void...params) {
+ try {
+ // Start progress
+ publishProgress(new File[]{});
+
+ // 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>();
+ } finally {
+ // Save the filter (could have new albums)
+ Preferences.Media.setSelectedMedia(mContext, mFilter);
+ Preferences.Media.setLastDiscorevedAlbums(mContext, mNewAlbums);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onProgressUpdate(File... values) {
+ if (mFinalCallback != null) {
+ if (values == null || values.length == 0) {
+ mFinalCallback.onStartMediaDiscovered(mUserRequest);
+ } else {
+ mFinalCallback.onPartialMediaDiscovered(values, mUserRequest);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPostExecute(List<File> result) {
+ if (mFinalCallback != null) {
+ mFinalCallback.onEndMediaDiscovered(result.toArray(new File[result.size()]), mUserRequest);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCancelled(List<File> result) {
+ // Nothing found
+ if (mFinalCallback != null) {
+ // Overwrite the user request setting. If the task is cancelled then
+ // there is no notification to send to the user
+ mFinalCallback.onEndMediaDiscovered(new File[]{}, false);
+ }
+ }
+
+ /**
+ * 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) {
+ long start = System.currentTimeMillis();
+ List<File> paths = new ArrayList<File>();
+ List<File> partial = new ArrayList<File>();
+ Cursor c = mFinalContentResolver.query(uri, projection, where, args, null);
+ if (c != null) {
+ try {
+ int i = 0;
+ while (c.moveToNext()) {
+ // Only valid files (those i can read)
+ String p = c.getString(0);
+ if (p != null) {
+ File f = new File(p);
+ catalog(f);
+
+ // Check if is a valid filter
+ if (matchFilter(f)) {
+ paths.add(f);
+ partial.add(f);
+ }
+ }
+
+ // Publish partial data
+ if (i % 5 == 0 && partial.size() > 0) {
+ publishProgress(partial.toArray(new File[partial.size()]));
+ partial.clear();
+ }
+ i++;
+ }
+ } finally {
+ try {
+ c.close();
+ } catch (Exception e) {
+ // Ignore: handle exception
+ }
+ }
+ }
+ long end = System.currentTimeMillis();
+ if (DEBUG) Log.v(TAG, "Media reloaded in " + (end - start) + " miliseconds");
+ 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) {
+ return mFilter.contains(picture.getAbsolutePath()) ||
+ mFilter.contains(picture.getParentFile().getAbsolutePath());
+ }
+
+ /**
+ * Method that catalog the file (set its album and determine if is a new album)
+ *
+ * @param f The file to catalog
+ */
+ private void catalog(File f) {
+ File parent = f.getParentFile();
+ String albumPath = parent.getAbsolutePath();
+
+ // Add to new albums
+ mNewAlbums.add(albumPath);
+
+ // Is a new album?
+ if (!mLastAlbums.contains(albumPath)) {
+ // Is in the filter?
+ if (mIsAutoSelectNewAlbums && !mFilter.contains(albumPath)) {
+ // Add the album to the selected filter
+ mFilter.add(parent.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ /*package*/ 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 userRequest If the request was generated by the user
+ */
+ public synchronized void discover(boolean userRequest) {
+ if (mTask != null && mTask.getStatus().compareTo(Status.FINISHED) != 0 &&
+ !mTask.isCancelled()) {
+ mTask.cancel(true);
+ }
+ mTask = new AsyncDiscoverTask(mContext.getContentResolver(), mCallback, userRequest);
+ 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..78fc265
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java
@@ -0,0 +1,296 @@
+/*
+ * 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.RectF;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLESTextureInfo;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+
+/**
+ * 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 default texture coordinates (fit to frame)
+ private static final float[] DEFAULT_TEXTURE_COORDS = {
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f
+ };
+
+
+ private final TextureManager mTextureManager;
+
+ private final float[] mFrameVertex, mPhotoVertex;
+ private final float mFrameWidth, mFrameHeight;
+ private final float mPhotoWidth, mPhotoHeight;
+
+
+ private FloatBuffer mPositionBuffer;
+ 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 photoVertex A 4 dimension array with the coordinates per vertex without padding
+ * @param color Background color
+ */
+ public PhotoFrame(Context ctx, TextureManager textureManager,
+ float[] frameVertex, float[] photoVertex, GLColor color) {
+ super();
+ mLoaded = false;
+ mBackgroundColor = color;
+ mTextureManager = textureManager;
+
+ // Save dimensions
+ mFrameVertex = frameVertex;
+ mFrameWidth = frameVertex[6] - frameVertex[4];
+ mFrameHeight = frameVertex[1] - frameVertex[5];
+ mPhotoVertex = photoVertex;
+ mPhotoWidth = photoVertex[6] - photoVertex[4];
+ mPhotoHeight = photoVertex[5] - photoVertex[1];
+
+ // Initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(photoVertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ mPositionBuffer = bb.asFloatBuffer();
+ mPositionBuffer.put(photoVertex);
+ mPositionBuffer.position(0);
+
+ // Load the texture
+ mTextureInfo = null;
+
+ // Request a new image for this frame
+ textureManager.request(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public RectF getRequestorDimensions() {
+ return new RectF(0, 0, mPhotoWidth, mPhotoHeight);
+ }
+
+ /**
+ * {@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
+ 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) {
+ if (GLES20.glIsTexture(mTextureInfo.handle)) {
+ int[] textures = new int[]{mTextureInfo.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+ if (mTextureInfo.bitmap != null) {
+ mTextureInfo.bitmap.recycle();
+ mTextureInfo.bitmap = null;
+ }
+ }
+
+ // 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) {
+ // Swap buffers
+ 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 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 photo vertex
+ *
+ * @return float[] The photo vertex
+ */
+ public float[] getPhotoVertex() {
+ return mPhotoVertex;
+ }
+
+ /**
+ * Method that returns the photo width
+ *
+ * @return float The photo width
+ */
+ public float getPhotoWidth() {
+ return mPhotoWidth;
+ }
+
+ /**
+ * Method that returns the photo height
+ *
+ * @return float The photo height
+ */
+ public float getPhotoHeight() {
+ return mPhotoHeight;
+ }
+
+ /**
+ * Method that returns the position vertex buffer
+ *
+ * @return FloatBuffer The position vertex buffer
+ */
+ public FloatBuffer getPositionBuffer() {
+ return mPositionBuffer;
+ }
+
+ /**
+ * 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 texture info
+ *
+ * @return GLESTextureInfo The texture info
+ */
+ public GLESTextureInfo getTextureInfo() {
+ return mTextureInfo;
+ }
+
+ /**
+ * 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 (mPositionBuffer != null) {
+ mPositionBuffer.clear();
+ }
+ if (mTextureBuffer != null) {
+ mTextureBuffer.clear();
+ }
+ mPositionBuffer = 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..973618d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java
@@ -0,0 +1,171 @@
+/*
+ * 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.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+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 implements OnTouchListener {
+
+ private static final String TAG = "PhotoPhaseActivity";
+
+ private static final boolean DEBUG = false;
+
+ 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);
+ mGLSurfaceView.setOnTouchListener(this);
+ 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);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+ float x = event.getX();
+ float y = event.getY();
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ mRenderer.onTouch(x, y);
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ Log.i(TAG, "onLowMemory");
+ // Pause the wallpaper and destroy the cached textures
+ mRenderer.onPause();
+ mRenderer.onLowMemory();
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java
new file mode 100644
index 0000000..f57d53b
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java
@@ -0,0 +1,661 @@
+/*
+ * 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.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.effect.EffectContext;
+import android.net.Uri;
+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.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLESTextureInfo;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.preferences.TouchAction;
+import org.cyanogenmod.wallpapers.photophase.shapes.ColorShape;
+import org.cyanogenmod.wallpapers.photophase.shapes.OopsShape;
+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 = false;
+
+ private final long mInstance;
+ private static long sInstances;
+
+ /*package*/ final Context mContext;
+ /*package*/ EffectContext mEffectContext;
+ private final Handler mHandler;
+ /*package*/ final GLESSurfaceDispatcher mDispatcher;
+ /*package*/ TextureManager mTextureManager;
+
+ /*package*/ PhotoPhaseWallpaperWorld mWorld;
+ /*package*/ ColorShape mOverlay;
+ /*package*/ OopsShape mOopsShape;
+
+ /*package*/ long mLastRunningTransition;
+
+ private long mLastTouchTime;
+ private static final long TOUCH_BARRIER_TIME = 1000L;
+
+ /*package*/ int mWidth = -1;
+ /*package*/ int mHeight = -1;
+ private int mStatusBarHeight = 0;
+ /*package*/ 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();
+
+ /*package*/ 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 (emptyTextureQueue) {
+ if (mTextureManager != null) {
+ mTextureManager.emptyTextureQueue(true);
+ }
+ }
+ if (mediaReload) {
+ synchronized (mMediaSync) {
+ if (mTextureManager != null) {
+ boolean userReloadRequest =
+ intent.getBooleanExtra(
+ PreferencesProvider.EXTRA_ACTION_MEDIA_USER_RELOAD_REQUEST, false);
+ mTextureManager.reloadMedia(userReloadRequest);
+ scheduleOrCancelMediaScan();
+ }
+ }
+ }
+ if (mediaIntervalChanged) {
+ scheduleOrCancelMediaScan();
+ }
+ if (recreateWorld && mWorld != null) {
+ // 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();
+ }
+ }
+ };
+
+ private final Runnable mTransitionThread = new Runnable() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ // Run in GLES's thread
+ mDispatcher.dispatch(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Select a new transition
+ mWorld.selectRandomTransition();
+ mLastRunningTransition = System.currentTimeMillis();
+
+ // Now force continuously render while transition is applied
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ } catch (Throwable ex) {
+ Log.e(TAG, "Something was wrong selecting the transition", ex);
+ }
+ }
+ });
+ }
+ };
+
+ /**
+ * 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 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 = Preferences.Media.getRefreshFrecuency();
+ if (interval != Preferences.Media.MEDIA_RELOAD_DISABLED) {
+ // Schedule a media scan
+ scheduleMediaScan(interval);
+ }
+ }
+
+ /**
+ * Method called 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();
+ if (mEffectContext != null) {
+ mEffectContext.release();
+ }
+ mEffectContext = null;
+ mWidth = -1;
+ mHeight = -1;
+ mMeasuredHeight = -1;
+ }
+
+ /**
+ * Method called when system runs under low memory
+ */
+ public void onLowMemory() {
+ mTextureManager.emptyTextureQueue(false);
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Method called when the renderer should process a touch event over the screen
+ *
+ * @param x The x coordinate
+ * @param y The y coordinate
+ */
+ public void onTouch(float x , float y) {
+ if (mWorld != null) {
+ // Do user action
+ TouchAction touchAction = Preferences.General.getTouchAction();
+ if (touchAction.compareTo(TouchAction.NONE) == 0) {
+ //Ignore
+ } else {
+ // Avoid to handle multiple touchs
+ long touchTime = System.currentTimeMillis();
+ long diff = touchTime - mLastTouchTime;
+ mLastTouchTime = touchTime;
+ if (diff < TOUCH_BARRIER_TIME) {
+ return;
+ }
+
+ // Retrieve the photo frame for its coordinates
+ final PhotoFrame frame = mWorld.getFrameFromCoordinates(new PointF(x, y));
+ if (frame == null) {
+ Log.w(TAG, "No frame from coordenates");
+ return;
+ }
+
+ // Apply the action
+ if (touchAction.compareTo(TouchAction.TRANSITION) == 0) {
+ try {
+ // Select the frame with a transition
+ // Run in GLES's thread
+ mDispatcher.dispatch(new Runnable() {
+ @Override
+ public void run() {
+ // Select a new transition
+ deselectCurrentTransition();
+ mWorld.selectTransition(frame);
+ mLastRunningTransition = System.currentTimeMillis();
+
+ // Now force continuously render while transition is applied
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+ });
+
+ } catch (NotFoundException ex) {
+ Log.e(TAG, "The frame not exists " + frame.getTextureInfo().path, ex);
+ }
+
+ } else if (touchAction.compareTo(TouchAction.OPEN) == 0) {
+ // Open the image
+ try {
+ Uri uri = getUriFromFrame(frame);
+ if (uri != null) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setDataAndType(uri, "image/*");
+ mContext.startActivity(intent);
+ }
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Open activity not found for " + frame.getTextureInfo().path, ex);
+ }
+
+ } else if (touchAction.compareTo(TouchAction.SHARE) == 0) {
+ // Send the image
+ try {
+ Uri uri = getUriFromFrame(frame);
+ if (uri != null) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setType("image/*");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ mContext.startActivity(intent);
+ }
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Send activity not found for " + frame.getTextureInfo().path, ex);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Method that returns an Uri reference from a photo frame
+ *
+ * @param frame The photo frame
+ * @return Uri The image uri
+ */
+ private static Uri getUriFromFrame(final PhotoFrame frame) {
+ // Sanity checks
+ GLESTextureInfo info = frame.getTextureInfo();
+ if (info == null) {
+ Log.e(TAG, "The frame has not a valid reference right now." +
+ "Touch action is not available.");
+ return null;
+ }
+ if (info.path == null || !info.path.isFile()) {
+ Log.e(TAG, "The image do not exists. Touch action is not available.");
+ return null;
+ }
+
+ // Return the uri from the path
+ return Uri.fromFile(frame.getTextureInfo().path);
+ }
+
+ /**
+ * Method that deselect the current transition
+ */
+ /*package*/ synchronized void deselectCurrentTransition() {
+ mHandler.removeCallbacks(mTransitionThread);
+ mWorld.deselectTransition(mMVPMatrix);
+ mLastRunningTransition = 0;
+ }
+
+ /*package*/ void scheduleOrCancelMediaScan() {
+ int interval = Preferences.Media.getRefreshFrecuency();
+ if (interval != 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 = 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();
+ if (mOopsShape != null) mOopsShape.recycle();
+ mWorld = null;
+ mTextureManager = null;
+ mOverlay = null;
+ mOopsShape = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ if (DEBUG) Log.d(TAG, "onSurfaceCreated [" + mInstance + "]");
+
+ mWidth = -1;
+ mHeight = -1;
+ mMeasuredHeight = -1;
+ mStatusBarHeight = 0;
+
+ // 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 an effect context
+ if (mEffectContext != null) {
+ mEffectContext.release();
+ }
+ mEffectContext = EffectContext.createWithCurrentGlContext();
+
+ // 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 orientation = mContext.getResources().getConfiguration().orientation;
+ int w = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp);
+ int h = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenHeightDp);
+ Rect dimensions = new Rect(0, 0, w, h);
+ int cc = (orientation == Configuration.ORIENTATION_PORTRAIT)
+ ? Preferences.Layout.getPortraitDisposition().size()
+ : Preferences.Layout.getLandscapeDisposition().size();
+
+ // Recycle the current texture manager and create a new one
+ recycle();
+ mTextureManager = new TextureManager(
+ mContext, mHandler, mEffectContext, mDispatcher, cc, dimensions);
+ } else {
+ mTextureManager.updateEffectContext(mEffectContext);
+ }
+ }
+
+ /**
+ * {@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;
+ mStatusBarHeight = AndroidHelper.calculateStatusBarHeight(mContext);
+ mMeasuredHeight = mHeight + mStatusBarHeight;
+
+ // Calculate a better fixed size for the pictures
+ Rect dimensions = Utils.isTablet(mContext)
+ ? new Rect(0, 0, width / 2, height / 2)
+ : new Rect(0, 0, width / 4, height / 4);
+ Rect screenDimensions = new Rect(0, mStatusBarHeight, width, height);
+ mTextureManager.setDimensions(dimensions);
+ mTextureManager.setScreenDimesions(screenDimensions);
+ mTextureManager.setPause(false);
+
+ // Create the wallpaper (destroy the previous)
+ if (mWorld != null) {
+ mWorld.recycle();
+ }
+ mWorld = new PhotoPhaseWallpaperWorld(mContext, mTextureManager);
+
+ // Create the overlay shape
+ final float[] vertex = {
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ -1.0f, 1.0f,
+ 1.0f, 1.0f
+ };
+ mOverlay = new ColorShape(mContext, vertex, Colors.getOverlay());
+
+ // Create the Oops shape
+ mOopsShape = new OopsShape(mContext, R.string.no_pictures_oops_msg);
+
+ // Set the viewport and the fustrum
+ GLES20.glViewport(0, -mStatusBarHeight, mWidth, mHeight);
+ 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);
+ }
+
+ // Force an immediate redraw of the screen (draw thread could be in dirty mode only)
+ deselectCurrentTransition();
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDrawFrame(GL10 glUnused) {
+ synchronized (mDrawing) {
+ // Set the projection, view and model
+ GLES20.glViewport(0, -mStatusBarHeight, mWidth, mHeight);
+ 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);
+
+ if (mTextureManager != null) {
+ if (mTextureManager.getStatus() == 1 && mTextureManager.isEmpty()) {
+ // Advise the user and stop
+ drawOops();
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+
+ } else {
+ // Draw the background
+ drawBackground();
+
+ if (mWorld != null) {
+ // 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 (Preferences.General.Transitions.getTransitionInterval() > 0) {
+ if (!mWorld.hasRunningTransition() || firedTransitionTimeout()) {
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+
+ // Now start a delayed thread to generate the next effect
+ deselectCurrentTransition();
+ mLastRunningTransition = 0;
+ mHandler.postDelayed(mTransitionThread,
+ Preferences.General.Transitions.getTransitionInterval());
+ }
+ } else {
+ // Just display the initial frames and never make transitions
+ if (!mWorld.hasRunningTransition() || firedTransitionTimeout()) {
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ }
+ }
+
+ // Draw the overlay
+ drawOverlay();
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Check whether the transition has exceed the timeout
+ *
+ * @return boolean if the transition has exceed the timeout
+ */
+ private boolean firedTransitionTimeout() {
+ 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(Preferences.General.getWallpaperDim() / 100.0f);
+ mOverlay.draw(mMVPMatrix);
+ }
+ }
+
+ /**
+ * Method that draws the oops message
+ */
+ private void drawOops() {
+ if (mOopsShape != null) {
+ mOopsShape.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..625212c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java
@@ -0,0 +1,236 @@
+/*
+ * 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.ActivityManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.ViewConfiguration;
+
+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 = false;
+
+ private List<PhotoPhaseRenderer> mRenderers;
+ private PhotoPhaseWallpaperEngine mEngine;
+
+ private boolean mPreserveEGLContext;
+
+ // List of the current top activities. Tap should be ignored when this acitivities are
+ // in the foreground
+ static final String[] TOP_ACTIVITIES = {"com.android.internal.app.ChooserActivity"};
+
+ /**
+ * {@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 {
+
+ private final Handler mHandler;
+ /*package*/ final ActivityManager mActivityManager;
+
+ /**
+ * Constructor of <code>PhotoPhaseWallpaperEngine<code>
+ *
+ * @param wallpaper The wallpaper service reference
+ */
+ PhotoPhaseWallpaperEngine(PhotoPhaseWallpaper wallpaper) {
+ super();
+ mHandler = new Handler();
+ mActivityManager = (ActivityManager)getApplication().getSystemService(ACTIVITY_SERVICE);
+ setOffsetNotificationsEnabled(false);
+ 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 Bundle onCommand(final String action, final int x, final int y, final int z,
+ final Bundle extras, final boolean resultRequested) {
+ if (action.compareTo(WallpaperManager.COMMAND_TAP) == 0) {
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // Only if the wallpaper is visible after a long press and
+ // not in preview mode
+ if (isVisible() && !isPreview()) {
+ List<ActivityManager.RunningTaskInfo> taskInfo =
+ mActivityManager.getRunningTasks(1);
+ String topActivity = taskInfo.get(0).topActivity.getClassName();
+ for (String activity : TOP_ACTIVITIES) {
+ if (activity.compareTo(topActivity) == 0) {
+ // Ignore tap event
+ return;
+ }
+ }
+
+ // Pass the x and y position to the renderer
+ ((PhotoPhaseRenderer)getRenderer()).onTouch(x, y);
+ }
+ }
+ }, ViewConfiguration.getLongPressTimeout() + 100L);
+ }
+ return super.onCommand(action, x, y, z, extras, resultRequested);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ Log.i(TAG, "onLowMemory");
+ for (PhotoPhaseRenderer renderer : mRenderers) {
+ // Pause the wallpaper and destroy the cached textures
+ renderer.onPause();
+ renderer.onLowMemory();
+ }
+ }
+
+ /**
+ * {@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..0310b58
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java
@@ -0,0 +1,419 @@
+/*
+ * 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.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.model.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 org.cyanogenmod.wallpapers.photophase.utils.Utils;
+
+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 int mWidth;
+ private int mHeight;
+
+ 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 ensures the transitions queue
+ */
+ private void ensureTransitionsQueue() {
+ if (mTransitionsQueue.isEmpty()) {
+ mTransitionsQueue.addAll(mUsedTransitionsQueue);
+ mUsedTransitionsQueue.clear();
+ }
+ }
+
+ /**
+ * Method that selects a transition and assign it to a random photo frame.
+ */
+ public void selectRandomTransition() {
+ // Ensure queue
+ ensureTransitionsQueue();
+
+ // Get a random frame to which apply the transition
+ int item = Utils.getNextRandom(0, mTransitionsQueue.size() - 1);
+ int pos = mTransitionsQueue.remove(item).intValue();
+ mUsedTransitionsQueue.add(Integer.valueOf(pos));
+ PhotoFrame frame = mPhotoFrames.get(pos);
+
+ // Select the transition
+ selectTransition(frame, pos);
+ }
+
+ /**
+ * Method that selects a transition and assign it to the photo frame.
+ *
+ * @param frame The photo frame to select
+ */
+ public void selectTransition(PhotoFrame frame) {
+ // Ensure queue
+ ensureTransitionsQueue();
+
+ // Get a random frame to which apply the transition
+ int pos = mPhotoFrames.indexOf(frame);
+ if (pos == -1) {
+ return;
+ }
+ mTransitionsQueue.remove(Integer.valueOf(pos));
+ mUsedTransitionsQueue.add(Integer.valueOf(pos));
+
+ // Select the transition
+ selectTransition(frame, pos);
+ }
+
+ /**
+ * Method that selects a transition and assign it to a photo frame.
+ *
+ * @param frame The frame to select
+ * @param pos The position
+ */
+ private void selectTransition(PhotoFrame frame, int pos) {
+ // Create or use a transition
+ Transition transition = null;
+ boolean isSelectable = false;
+ while (transition == null || !isSelectable) {
+ boolean isRandom = Preferences.General.Transitions.getTransitionTypes().length > 1;
+ 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 && mCurrent < mTransitions.size()) {
+ // 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);
+ }
+ }
+ mCurrent = -1;
+ 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 (mTransitionsQueue != null) {
+ mTransitionsQueue.clear();
+ }
+ if (mUsedTransitionsQueue != null) {
+ 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;
+ }
+
+ // Save the new dimensions of the wallpaper
+ mWidth = w;
+ mHeight = h;
+
+ // 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 = portrait
+ ? Preferences.Layout.getPortraitDisposition()
+ : Preferences.Layout.getLandscapeDisposition();
+ 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, cellw, cellh);
+ float[] photoVertices = getFramePadding(frameVertices, portrait ? w : h, portrait ? h : w);
+ PhotoFrame frame =
+ new PhotoFrame(
+ mContext,
+ mTextureManager,
+ frameVertices,
+ photoVertices,
+ 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 returns a photo frame from a coordinates in screen
+ *
+ * @param coordinates The coordinates
+ * @return The photo frame reference or null if none found
+ */
+ public PhotoFrame getFrameFromCoordinates(PointF coordinates) {
+ // Translate pixels coordinates to GLES coordinates
+ float tx = ((coordinates.x * 2) / mWidth) - 1;
+ float ty = (((coordinates.y * 2) / mHeight) - 1) * -1;
+
+ // Locate the frame
+ for (PhotoFrame frame : mPhotoFrames) {
+ RectF vertex = Utils.rectFromVertex(frame.getPhotoVertex());
+ if (vertex.left < tx && vertex.right > tx && vertex.top > ty && vertex.bottom < ty) {
+ return frame;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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 the non-running transitions; then the active ones
+ for (Transition transition : mTransitions) {
+ if (!transition.isRunning()) {
+ transition.apply(matrix);
+ }
+ }
+ for (Transition transition : mTransitions) {
+ if (transition.isRunning()) {
+ transition.apply(matrix);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method that returns a coordinates per vertex array from a disposition
+ *
+ * @param disposition The source disposition
+ * @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, float cellw, float cellh) {
+ return new float[]
+ {
+ // bottom left
+ -1.0f + (disposition.x * cellw),
+ 1.0f - ((disposition.y * cellh) + (disposition.h * cellh)),
+
+ // bottom right
+ -1.0f + ((disposition.x * cellw) + (disposition.w * cellw)),
+ 1.0f - ((disposition.y * cellh) + (disposition.h * cellh)),
+
+ // top left
+ -1.0f + (disposition.x * cellw),
+ 1.0f - (disposition.y * cellh),
+
+ // top right
+ -1.0f + ((disposition.x * cellw) + (disposition.w * cellw)),
+ 1.0f - (disposition.y * cellh)
+ };
+ }
+
+ /**
+ * 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[2] -= pxw;
+ paddingCoords[3] += pxh;
+ paddingCoords[4] += pxw;
+ paddingCoords[5] -= pxh;
+ paddingCoords[6] -= pxw;
+ paddingCoords[7] -= 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..9d73313
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.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;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A helper class to deal with android storage
+ */
+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..6c8de05
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
@@ -0,0 +1,595 @@
+/*
+ * 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.graphics.RectF;
+import android.media.ThumbnailUtils;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.opengl.GLES20;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.cyanogenmod.wallpapers.photophase.FixedQueue.EmptyQueueException;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLESTextureInfo;
+import org.cyanogenmod.wallpapers.photophase.MediaPictureDiscoverer.OnMediaPictureDiscoveredListener;
+import org.cyanogenmod.wallpapers.photophase.effects.Effects;
+
+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 = 3;
+
+ final Context mContext;
+ final Handler mHandler;
+ Effects mEffects;
+ final Object mSync;
+ final List<TextureRequestor> mPendingRequests;
+ final FixedQueue<GLESTextureInfo> mQueue = new FixedQueue<GLESTextureInfo>(QUEUE_SIZE);
+ BackgroundPictureLoaderThread mBackgroundTask;
+ /*protected*/ final MediaPictureDiscoverer mPictureDiscoverer;
+
+ /*package*/ Rect mScreenDimensions;
+ /*package*/ Rect mDimensions;
+
+ final GLESSurfaceDispatcher mDispatcher;
+
+ // The status of the texture manager:
+ // 0 - Loading
+ // 1 - Loaded
+ // 2 - Error
+ private byte mStatus;
+
+ /**
+ * A private runnable that will run in the GLThread
+ */
+ /*package*/ class PictureDispatcher implements Runnable {
+ File mImage;
+ GLESTextureInfo ti = null;
+ final Object mWait = new Object();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ Effect effect = null;
+ synchronized (mEffects) {
+ effect = mEffects.getNextEffect();
+ }
+
+ // Load and bind to the GLES context. The effect is applied when the image
+ // is associated to the destination target (only if aspect ratio will be applied)
+ if (!Preferences.General.isFixAspectRatio()) {
+ ti = GLESUtil.loadTexture(
+ mImage, mDimensions, effect, mDimensions, false);
+ } else {
+ ti = GLESUtil.loadTexture(mImage, mDimensions, null, null, false);
+ ti.effect = effect;
+ }
+
+ synchronized (mSync) {
+ // Notify the new images to all pending frames
+ if (mPendingRequests.size() > 0) {
+ // Invalid textures are also reported, so requestor can handle it
+ TextureRequestor requestor = mPendingRequests.remove(0);
+ fixAspectRatio(requestor, ti);
+ requestor.setTextureHandle(ti);
+
+ // Clean up memory
+ if (ti.bitmap != null) {
+ ti.bitmap.recycle();
+ ti.bitmap = null;
+ }
+
+ } else {
+ // Add to the queue (only valid textures)
+ if (ti.handle > 0) {
+ mQueue.insert(ti);
+ }
+ }
+ }
+
+ } catch (Throwable 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 effectCtx The current effect context
+ * @param dispatcher The GLES dispatcher
+ * @param requestors The number of requestors
+ * @param screenDimensions The screen dimensions
+ */
+ public TextureManager(final Context ctx, final Handler handler, final EffectContext effectCtx,
+ GLESSurfaceDispatcher dispatcher, int requestors, Rect screenDimensions) {
+ super();
+ mContext = ctx;
+ mHandler = handler;
+ mEffects = new Effects(effectCtx);
+ mDispatcher = dispatcher;
+ mScreenDimensions = screenDimensions;
+ mDimensions = screenDimensions; // For now, use the screen dimensions as the preferred dimensions for bitmaps
+ 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(false);
+ }
+
+ /**
+ * Method that update the effect context if the EGL context change
+ *
+ * @param effectCtx The new effect context
+ */
+ protected void updateEffectContext(final EffectContext effectCtx) {
+ synchronized (mEffects) {
+ if (mEffects != null) {
+ mEffects.release();
+ mEffects = null;
+ }
+ mEffects = new Effects(effectCtx);
+ }
+ emptyTextureQueue(true);
+ }
+
+ /**
+ * 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 allow to change the screen dimensions
+ *
+ * @param dimensions The new dimensions
+ */
+ public void setScreenDimesions(Rect dimensions) {
+ mScreenDimensions = 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
+ *
+ * @param userRequest If the request was generated by the user
+ */
+ void reloadMedia(final boolean userRequest) {
+ Log.d(TAG, "Reload media picture data");
+ // Discovery new media
+ // GLThread doesn't run in the UI thread and AsyncThread can't create a
+ // valid handler in ICS (it's fixed in JB+) so we force to run the async
+ // thread in a valid UI thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mPictureDiscoverer.discover(userRequest);
+ }
+ });
+ }
+
+ /**
+ * 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 {
+ GLESTextureInfo ti = mQueue.remove();
+ fixAspectRatio(requestor, ti);
+ requestor.setTextureHandle(ti);
+
+ // Clean up memory
+ if (ti.bitmap != null) {
+ ti.bitmap.recycle();
+ ti.bitmap = null;
+ }
+
+ } 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) {
+ // Recycle the textures
+ 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
+ info.bitmap.recycle();
+ info.bitmap = null;
+ }
+ } catch (EmptyQueueException eqex) {
+ // Ignore
+ }
+
+ // Remove all pictures in the queue
+ try {
+ mQueue.removeAll();
+ } catch (EmptyQueueException ex) {
+ // Ignore
+ }
+
+ // Reload the queue
+ if (reload) {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.resetAvailableImages();
+ 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 onStartMediaDiscovered(boolean userRequest) {
+ // No images but thread should start here to received partial data
+ this.mStatus = 0; // Loading
+ if (mBackgroundTask != null) {
+ mBackgroundTask.setAvailableImages(new File[]{});
+ if (!mBackgroundTask.mRun) {
+ mBackgroundTask.start();
+ } else {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPartialMediaDiscovered(File[] images, boolean userRequest) {
+ if (mBackgroundTask != null) {
+ mBackgroundTask.setPartialAvailableImages(images);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("boxing")
+ public void onEndMediaDiscovered(File[] images, boolean userRequest) {
+ // Now we have the paths of the images to use. Notify to the thread to
+ // load pictures in background
+ if (mBackgroundTask != null) {
+ mBackgroundTask.setAvailableImages(images);
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ this.mStatus = 1; // Loaded
+
+ // Audit
+ int found = images == null ? 0 : images.length;
+ Log.d(TAG, "Media picture data reloaded: " + found + " images found.");
+ if (userRequest) {
+ CharSequence msg =
+ String.format(mContext.getResources().getQuantityText(
+ R.plurals.msg_media_reload_complete, found).toString(), found);
+ Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ this.mStatus = 2; // Error
+ }
+ }
+
+ /**
+ * Method that destroy the references of this class
+ */
+ public void recycle() {
+ // Destroy the media discovery task
+ mPictureDiscoverer.recycle();
+ mEffects.release();
+
+ // Destroy the background task
+ if (mBackgroundTask != null) {
+ mBackgroundTask.mRun = false;
+ try {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.interrupt();
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ mBackgroundTask = null;
+ }
+
+
+ /**
+ * Returns the status of the texture manager
+ *
+ * @return byte The status
+ */
+ public byte getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Returns if the texture manager is empty
+ *
+ * @return boolean If the texture manager is empty
+ */
+ public boolean isEmpty() {
+ return mBackgroundTask != null && mBackgroundTask.mEmpty;
+ }
+
+ /**
+ * Method that fix the aspect ratio of a image to fit the destination target
+ *
+ * @param request The requestor target
+ * @param ti The original texture information
+ * @param effect The effect to apply to the destination picture
+ */
+ /*package*/ void fixAspectRatio(TextureRequestor requestor, GLESTextureInfo ti) {
+ // Check if we have to apply any correction to the image
+ if (Preferences.General.isFixAspectRatio()) {
+ // Transform requestor dimensions to screen dimensions
+ RectF dimens = requestor.getRequestorDimensions();
+ Rect pixels = new Rect(
+ 0,
+ 0,
+ (int)(mScreenDimensions.width() * dimens.width() / 2),
+ (int)(mScreenDimensions.height() * dimens.height() / 2));
+
+ // Create a thumbnail of the image
+ Bitmap thumb = ThumbnailUtils.extractThumbnail(
+ ti.bitmap,
+ pixels.width(),
+ pixels.height(),
+ ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
+ GLESTextureInfo dst = GLESUtil.loadTexture(thumb, ti.effect, pixels);
+
+ // Destroy references
+ int[] textures = new int[]{ti.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ if (ti.bitmap != null) {
+ ti.bitmap.recycle();
+ ti.bitmap = null;
+ }
+
+ // Swap references
+ ti.bitmap = dst.bitmap;
+ ti.handle = dst.handle;
+ ti.effect = null;
+ }
+ }
+
+ /**
+ * An internal thread to load pictures in background
+ */
+ private class BackgroundPictureLoaderThread extends Thread {
+
+ final Object mLoadSync = new Object();
+ boolean mRun;
+ boolean mTaskPaused;
+
+ /*package*/ boolean mEmpty;
+ 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.clear();
+ mNewImages.addAll(Arrays.asList(images));
+
+ // Retain used images
+ int count = mUsedImages.size() - 1;
+ for (int i = count; i >= 0; i--) {
+ File image = mUsedImages.get(i);
+ if (!mNewImages.contains(image)) {
+ mUsedImages.remove(image);
+ } else {
+ mNewImages.remove(image);
+ }
+ }
+
+ mEmpty = images.length == 0;
+ }
+ }
+
+ /**
+ * Method that adds some available images.
+ *
+ * @param images The current images
+ */
+ public void setPartialAvailableImages(File[] images) {
+ synchronized (mLoadSync) {
+ mNewImages.addAll(Arrays.asList(images));
+ mEmpty = images.length == 0;
+ }
+ }
+
+ /**
+ * Method that reset the current available images queue.
+ */
+ public void resetAvailableImages() {
+ synchronized (mLoadSync) {
+ mNewImages.addAll(mUsedImages);
+ 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) {
+ if (!mEmpty) {
+ reloadMedia(false);
+ }
+ break;
+ }
+
+ // Extract a random image
+ int low = 0;
+ int high = mNewImages.size() - 1;
+ image = mNewImages.remove(Utils.getNextRandom(low, high));
+ }
+
+ // 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..9c62eb2
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.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.graphics.RectF;
+
+import org.cyanogenmod.wallpapers.photophase.utils.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);
+
+ /**
+ * Method that returns the dimension of the requestor
+ *
+ * @return RectF The dimensions of the requestor
+ */
+ RectF getRequestorDimensions();
+}
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..228f827
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java
@@ -0,0 +1,164 @@
+/*
+ * 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 unregister the controller
+ */
+ public void unregister() {
+ getFrontView().setOnClickListener(null);
+ getBackView().setOnClickListener(null);
+ }
+
+ /**
+ * Method that reset the controller
+ */
+ public void reset() {
+ if (!mFrontFace) {
+ applyAnimation(true);
+ }
+ }
+
+ /**
+ * 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/Evaluators.java b/src/org/cyanogenmod/wallpapers/photophase/animations/Evaluators.java
new file mode 100644
index 0000000..a0e52aa
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/animations/Evaluators.java
@@ -0,0 +1,79 @@
+/*
+ * 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.animation.IntEvaluator;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A class with helpful evaluators
+ */
+public class Evaluators {
+
+ /**
+ * A width evaluator
+ */
+ public static class WidthEvaluator extends IntEvaluator {
+ private View mView;
+
+ /**
+ * Constructor of <code>WidthEvaluator</code>
+ *
+ * @param v The view
+ */
+ public WidthEvaluator(View v) {
+ super();
+ mView = v;
+ }
+
+ @Override
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ Integer num = super.evaluate(fraction, startValue, endValue);
+ ViewGroup.LayoutParams params = mView.getLayoutParams();
+ params.width = num.intValue();
+ mView.setLayoutParams(params);
+ return num;
+ }
+ }
+
+ /**
+ * A height evaluator
+ */
+ public static class HeightEvaluator extends IntEvaluator {
+ private View mView;
+
+ /**
+ * Constructor of <code>HeightEvaluator</code>
+ *
+ * @param v The view
+ */
+ public HeightEvaluator(View v) {
+ super();
+ mView = v;
+ }
+
+ @Override
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ Integer num = super.evaluate(fraction, startValue, endValue);
+ ViewGroup.LayoutParams params = mView.getLayoutParams();
+ params.height = num.intValue();
+ mView.setLayoutParams(params);
+ return num;
+ }
+ }
+}
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/BlurEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/BlurEffect.java
new file mode 100644
index 0000000..465cb99
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/BlurEffect.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * A blur effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class BlurEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " float step = 0.02;\n" +
+ " vec3 c1 = texture2D(tex_sampler, vec2(v_texcoord.s - step, v_texcoord.t - step)).bgr;\n" +
+ " vec3 c2 = texture2D(tex_sampler, vec2(v_texcoord.s + step, v_texcoord.t + step)).bgr;\n" +
+ " vec3 c3 = texture2D(tex_sampler, vec2(v_texcoord.s - step, v_texcoord.t + step)).bgr;\n" +
+ " vec3 c4 = texture2D(tex_sampler, vec2(v_texcoord.s + step, v_texcoord.t - step)).bgr;\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = (c1 + c2 + c3 + c4) / 4.0;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>BlurEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public BlurEffect(EffectContext ctx, String name) {
+ super(ctx, BlurEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
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..0ab3f6c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java
@@ -0,0 +1,338 @@
+/*
+ * 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.Color;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that manages all the supported effects
+ */
+public class Effects {
+
+ /**
+ * Enumeration of the supported effects
+ */
+ public enum EFFECTS {
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_NULL
+ */
+ NO_EFFECT,
+ /**
+ * @see EffectFactory#EFFECT_AUTOFIX
+ */
+ AUTOFIX,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_BLUR
+ */
+ BLUR,
+ /**
+ * @see EffectFactory#EFFECT_CROSSPROCESS
+ */
+ CROSSPROCESS,
+ /**
+ * @see EffectFactory#EFFECT_DOCUMENTARY
+ */
+ DOCUMENTARY,
+ /**
+ * @see EffectFactory#EFFECT_DUOTONE
+ */
+ DUOTONE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_EMBOSS
+ */
+ EMBOSS,
+ /**
+ * @see EffectFactory#EFFECT_FISHEYE
+ */
+ FISHEYE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_GLOW
+ */
+ GLOW,
+ /**
+ * @see EffectFactory#EFFECT_GRAIN
+ */
+ GRAIN,
+ /**
+ * @see EffectFactory#EFFECT_GRAYSCALE
+ */
+ GRAYSCALE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_HALFTONE
+ */
+ HALFTONE,
+ /**
+ * @see EffectFactory#EFFECT_LOMOISH
+ */
+ LOMOISH,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_MIRROR
+ */
+ MIRROR,
+ /**
+ * @see EffectFactory#EFFECT_NEGATIVE
+ */
+ NEGATIVE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_OUTLINE
+ */
+ OUTLINE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_PIXELATE
+ */
+ PIXELATE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_POPART
+ */
+ POPART,
+ /**
+ * @see EffectFactory#EFFECT_POSTERIZE
+ */
+ POSTERIZE,
+ /**
+ * @see EffectFactory#EFFECT_SATURATE
+ */
+ SATURATE,
+ /**
+ * @see PhotoPhaseEffectFactory#EFFECT_SCANLINES
+ */
+ SCANLINES,
+ /**
+ * @see EffectFactory#EFFECT_SEPIA
+ */
+ SEPIA,
+ /**
+ * @see EffectFactory#EFFECT_TEMPERATURE
+ */
+ TEMPERATURE,
+ /**
+ * @see EffectFactory#EFFECT_TINT
+ */
+ TINT,
+ /**
+ * @see EffectFactory#EFFECT_VIGNETTE
+ */
+ VIGNETTE;
+
+ /**
+ * Method that returns the effect from its ordinal position
+ *
+ * @param ordinal The ordinal position
+ * @return EFFECTS The effect or null if wasn't found
+ */
+ public static EFFECTS fromOrdinal(int ordinal) {
+ for (EFFECTS effect : EFFECTS.values()) {
+ if (effect.ordinal() == ordinal) {
+ return effect;
+ }
+ }
+ return null;
+ }
+ }
+
+ private final Map<EFFECTS, Effect> mCachedEffects;
+ private final EffectContext mEffectContext;
+
+ /**
+ * Constructor of <code>Effects</code>
+ *
+ * @param effectContext The current effect context
+ */
+ public Effects(EffectContext effectContext) {
+ super();
+ mCachedEffects = new HashMap<Effects.EFFECTS, Effect>();
+ mEffectContext = effectContext;
+ }
+
+ /**
+ * Method that that release the cached data
+ */
+ public void release() {
+ if (mCachedEffects != null) {
+ for (Effect effect : mCachedEffects.values()) {
+ effect.release();
+ }
+ mCachedEffects.clear();
+ }
+ }
+
+ /**
+ * Method that return the next effect to use with the picture.
+ *
+ * @return Effect The next effect to use or null if no need to apply any effect
+ */
+ @SuppressWarnings("boxing")
+ public Effect getNextEffect() {
+ // Get a new instance of a effect factory
+ EffectFactory effectFactory = mEffectContext.getFactory();
+ Effect effect = null;
+
+ // Get an effect based on the user preference
+ List<EFFECTS> effects = Arrays.asList(Preferences.General.Effects.getEffectTypes());
+ EFFECTS nextEffect = null;
+ if (effects.size() > 0) {
+ int low = 0;
+ int high = effects.size() - 1;
+ int pos = Utils.getNextRandom(low, high);
+ nextEffect = effects.get(pos);
+ }
+ if (nextEffect == null) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_NULL)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_NULL);
+ mCachedEffects.put(nextEffect, effect);
+ }
+ return effect;
+ }
+
+ // Has a cached effect?
+ if (mCachedEffects.containsKey(nextEffect)) {
+ return mCachedEffects.get(nextEffect);
+ }
+
+ // Select the effect if is available
+ if (nextEffect.compareTo(EFFECTS.AUTOFIX) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_AUTOFIX)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_AUTOFIX);
+ effect.setParameter("scale", 0.5f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.BLUR) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_BLUR)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_BLUR);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.CROSSPROCESS) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_CROSSPROCESS)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_CROSSPROCESS);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.DOCUMENTARY) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_DOCUMENTARY)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_DOCUMENTARY);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.DUOTONE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_DUOTONE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_DUOTONE);
+ effect.setParameter("first_color", Color.parseColor("#FF8CACFF"));
+ effect.setParameter("second_color", Color.WHITE);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.EMBOSS) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_EMBOSS)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_EMBOSS);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.FISHEYE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_FISHEYE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_FISHEYE);
+ effect.setParameter("scale", 1.0f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.GLOW) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_GLOW)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_GLOW);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.GRAIN) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_GRAIN)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_GRAIN);
+ effect.setParameter("strength", 1.0f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.GRAYSCALE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_GRAYSCALE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_GRAYSCALE);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.HALFTONE) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_HALFTONE)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_HALFTONE);
+ effect.setParameter("strength", 8.0f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.MIRROR) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_MIRROR)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_MIRROR);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.LOMOISH) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_LOMOISH)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_LOMOISH);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.NEGATIVE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_NEGATIVE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_NEGATIVE);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.OUTLINE) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_OUTLINE)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_OUTLINE);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.PIXELATE) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_PIXELATE)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_PIXELATE);
+ effect.setParameter("strength", 8.0f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.POPART) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_POPART)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_POPART);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.POSTERIZE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_POSTERIZE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_POSTERIZE);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.SATURATE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_SATURATE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_SATURATE);
+ effect.setParameter("scale", .5f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.SCANLINES) == 0) {
+ if (EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_SCANLINES)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_SCANLINES);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.SEPIA) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_SEPIA)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_SEPIA);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.TEMPERATURE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_TEMPERATURE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_TEMPERATURE);
+ effect.setParameter("scale", .9f);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.TINT) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_TINT)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_TINT);
+ }
+ } else if (nextEffect.compareTo(EFFECTS.VIGNETTE) == 0) {
+ if (EffectFactory.isEffectSupported(EffectFactory.EFFECT_VIGNETTE)) {
+ effect = effectFactory.createEffect(EffectFactory.EFFECT_VIGNETTE);
+ effect.setParameter("scale", .5f);
+ }
+ }
+
+ // Instead of not to apply any effect, just use one null effect to follow the same
+ // effect model. This allow to use the same height when Effect.apply is applied for all
+ // the frames
+ if (effect == null && EffectFactory.isEffectSupported(PhotoPhaseEffectFactory.EFFECT_NULL)) {
+ effect = effectFactory.createEffect(PhotoPhaseEffectFactory.EFFECT_NULL);
+ nextEffect = EFFECTS.NO_EFFECT;
+ }
+
+ // Cache the effects
+ mCachedEffects.put(nextEffect, effect);
+ return effect;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/EmbossEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/EmbossEffect.java
new file mode 100644
index 0000000..691ef39
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/EmbossEffect.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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * An emboss effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class EmbossEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "const float step_w = 0.0015625;\n" +
+ "const float step_h = 0.0027778;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec3 t1 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t2 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t3 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t4 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t5 = texture2D(tex_sampler, v_texcoord).bgr;\n" +
+ " vec3 t6 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t7 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t8 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t9 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 rr = -4.0 * t1 - 4.0 * t2 - 4.0 * t4 + 12.0 * t5;\n" +
+ " float y = (rr.r + rr.g + rr.b) / 3.0;\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = vec3(y, y, y) + 0.3;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>EmbossEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public EmbossEffect(EffectContext ctx, String name) {
+ super(ctx, EmbossEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/GlowEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/GlowEffect.java
new file mode 100644
index 0000000..8520e09
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/GlowEffect.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * A glow effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class GlowEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "const float step_w = 0.0015625;\n" +
+ "const float step_h = 0.0027778;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec3 t1 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t2 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t3 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t4 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t5 = texture2D(tex_sampler, v_texcoord).bgr;\n" +
+ " vec3 t6 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t7 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t8 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t9 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 xx= t1 + 2.0*t2 + t3 - t7 - 2.0*t8 - t9;\n" +
+ " vec3 yy = t1 - t3 + 2.0*t4 - 2.0*t6 + t7 - t9;\n" +
+ " vec3 rr = sqrt(xx * xx + yy * yy);\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = rr * 2.0 * t5;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>GlowEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public GlowEffect(EffectContext ctx, String name) {
+ super(ctx, GlowEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/HalftoneEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/HalftoneEffect.java
new file mode 100644
index 0000000..07da491
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/HalftoneEffect.java
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+
+/**
+ * A halftone effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * <tr>
+ * <td><code>strength</code></td>
+ * <td>The halftone strength.</td>
+ * <td>Positive float (>0). Higher numbers produce smallest points.</td>
+ * </tr>
+ * </table>
+ */
+public class HalftoneEffect extends PhotoPhaseEffect {
+
+ private static final String TAG = "HalftoneEffect";
+
+ private static final String STRENGTH_PARAMETER = "strength";
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "const float step_w = 0.0015625;\n" +
+ "const float step_h = 0.0027778;\n" +
+ "uniform float strength;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " float offx = floor(v_texcoord.s / (strength * step_w));\n" +
+ " float offy = floor(v_texcoord.t / (strength * step_h));\n" +
+ " vec3 res = texture2D(tex_sampler, vec2(offx * strength * step_w , offy * strength * step_h)).bgr;\n" +
+ " vec2 prc = fract(v_texcoord.st / vec2(strength * step_w, strength * step_h));\n" +
+ " vec2 pw = pow(abs(prc - 0.5), vec2(2.0));\n" +
+ " float rs = pow(0.45, 2.0);\n" +
+ " float gr = smoothstep(rs - 0.1, rs + 0.1, pw.x + pw.y);\n" +
+ " float y = (res.r + res.g + res.b) / 3.0; \n" +
+ " vec3 ra = res / y;\n" +
+ " float ls = 0.3;\n" +
+ " float lb = ceil(y / ls);\n" +
+ " float lf = ls * lb + 0.3;\n" +
+ " res = lf * res;\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = mix(res, vec3(0.1, 0.1, 0.1), gr);\n" +
+ "}";
+
+ private float mStrength = 16.0f;
+ private int mStepsHandle;
+
+ /**
+ * Constructor of <code>HalftoneEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public HalftoneEffect(EffectContext ctx, String name) {
+ super(ctx, HalftoneEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void init(String vertexShader, String fragmentShader) {
+ super.init(vertexShader, fragmentShader);
+
+ // Parameters
+ mStepsHandle = GLES20.glGetUniformLocation(mProgram, "strength");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void applyParameters() {
+ // Set parameters
+ GLES20.glUniform1f(mStepsHandle, mStrength);
+ GLESUtil.glesCheckError("glUniform1f");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setParameter(String parameterKey, Object value) {
+ if (parameterKey.compareTo(STRENGTH_PARAMETER) == 0) {
+ try {
+ float strength = Float.parseFloat(value.toString());
+ if (strength <= 0) {
+ Log.w(TAG, "strength parameter must be >= 0");
+ return;
+ }
+ mStrength = strength;
+ } catch (NumberFormatException ex) {
+ // Ignore
+ }
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/MirrorEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/MirrorEffect.java
new file mode 100644
index 0000000..27233d2
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/MirrorEffect.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * A mirror effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class MirrorEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec2 off = vec2(0.0, 0.0);\n" +
+ " if (v_texcoord.t > 0.5) {\n" +
+ " off.t = 1.0 - v_texcoord.t;\n" +
+ " off.s = v_texcoord.s;\n" +
+ " } else {\n" +
+ " off = v_texcoord;\n" +
+ " }\n" +
+ " vec3 color = texture2D(tex_sampler, vec2(off)).bgr;\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = color;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>MirrorEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public MirrorEffect(EffectContext ctx, String name) {
+ super(ctx, MirrorEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
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..5a8b520
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * This effect only copies the source texture to the destination texture.<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class NullEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +
+ "}";
+
+ /**
+ * Constructor of <code>NullEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public NullEffect(EffectContext ctx, String name) {
+ super(ctx, NullEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void apply(int inputTexId) {
+ // Nothing to draw
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/OutlineEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/OutlineEffect.java
new file mode 100644
index 0000000..8dc7854
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/OutlineEffect.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * An outline effect (highlight edges)<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class OutlineEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "const float step_w = 0.0015625;\n" +
+ "const float step_h = 0.0027778;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec3 t1 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t2 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t3 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y - step_h)).bgr;\n" +
+ " vec3 t4 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t5 = texture2D(tex_sampler, v_texcoord).bgr;\n" +
+ " vec3 t6 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y)).bgr;\n" +
+ " vec3 t7 = texture2D(tex_sampler, vec2(v_texcoord.x - step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t8 = texture2D(tex_sampler, vec2(v_texcoord.x, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 t9 = texture2D(tex_sampler, vec2(v_texcoord.x + step_w, v_texcoord.y + step_h)).bgr;\n" +
+ " vec3 xx= t1 + 2.0*t2 + t3 - t7 - 2.0*t8 - t9;\n" +
+ " vec3 yy = t1 - t3 + 2.0*t4 - 2.0*t6 + t7 - t9;\n" +
+ " vec3 rr = sqrt(xx * xx + yy * yy);\n" +
+ " float y = (rr.r + rr.g + rr.b) / 3.0;\n" +
+ " if (y > 0.2)\n" +
+ " rr = vec3(0.0, 0.0, 0.0);\n" +
+ " else\n" +
+ " rr = vec3(1.0, 1.0, 1.0);\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = rr;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>OutlineEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public OutlineEffect(EffectContext ctx, String name) {
+ super(ctx, OutlineEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java
new file mode 100644
index 0000000..2fd0e2b
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java
@@ -0,0 +1,268 @@
+/*
+ * 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.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * An abstract class definition for all the PhotoPhase custom effects
+ */
+public abstract class PhotoPhaseEffect extends Effect {
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+
+ private static final String MCA_IDENTITY_EFFECT = "IdentityEffect";
+
+ static final String VERTEX_SHADER =
+ "attribute vec4 a_position;\n" +
+ "attribute vec2 a_texcoord;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main() {\n" +
+ " gl_Position = vec4(a_position.xy, 0.0, 1.0);\n" +
+ " gl_Position = sign(gl_Position);\n" +
+ " v_texcoord = a_texcoord;\n" +
+ "}\n";
+
+ private static final float[] TEX_VERTICES = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
+ private static final float[] POS_VERTICES = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
+
+ private final int GL_STATE_FBO = 0;
+ private final int GL_STATE_PROGRAM = 1;
+ private final int GL_STATE_ARRAYBUFFER = 2;
+ private final int GL_STATE_COUNT = 3;
+
+ private int[] mOldState = new int[GL_STATE_COUNT];
+
+ private final EffectContext mEffectContext;
+ private final String mName;
+
+ private Effect mIdentityEffect;
+
+ int mProgram;
+ int mTexSamplerHandle;
+ int mTexCoordHandle;
+ int mPosCoordHandle;
+
+ FloatBuffer mTexVertices;
+ FloatBuffer mPosVertices;
+
+ /**
+ * An abstract constructor of <code>Effect</code> to follow the rules
+ * defined by {@link EffectFactory}.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public PhotoPhaseEffect(EffectContext ctx, String name) {
+ super();
+ mEffectContext = ctx;
+ mName = name;
+
+ // Stand on MCA identity effect for the initialization work
+ EffectFactory effectFactory = mEffectContext.getFactory();
+ mIdentityEffect = effectFactory.createEffect(MCA_IDENTITY_EFFECT);
+ }
+
+ /**
+ * Method that initializes the effect
+ */
+ void init(String vertexShader, String fragmentShader) {
+ // Create program
+ mProgram = GLESUtil.createProgram(vertexShader, fragmentShader);
+
+ // Bind attributes and uniforms
+ mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram, "tex_sampler");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+ mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texcoord");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_position");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+
+ // Setup coordinate buffers
+ mTexVertices = ByteBuffer.allocateDirect(
+ TEX_VERTICES.length * FLOAT_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTexVertices.put(TEX_VERTICES).position(0);
+ mPosVertices = ByteBuffer.allocateDirect(
+ POS_VERTICES.length * FLOAT_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mPosVertices.put(POS_VERTICES).position(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Method that returns the effect context
+ *
+ * @return EffectContext The effect context
+ */
+ public EffectContext getEffectContext() {
+ return mEffectContext;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final synchronized void apply(int inputTexId, int width, int height, int outputTexId) {
+ // Save the GLES state
+ saveGLState();
+
+ try {
+ // Create a framebuffer object and call the effect apply method to draw the effect
+ int[] fb = new int[1];
+ GLES20.glGenFramebuffers(1, fb, 0);
+ GLESUtil.glesCheckError("glGenFramebuffers");
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[0]);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Render on the whole framebuffer
+ GLES20.glViewport(0, 0, width, height);
+ GLESUtil.glesCheckError("glViewport");
+
+ // Create a new output texture (Use the MCA identity to clone the input to the output)
+ mIdentityEffect.apply(inputTexId, width, height, outputTexId);
+
+ // Create the framebuffer
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20. GL_TEXTURE_2D, outputTexId, 0);
+ GLESUtil.glesCheckError("glFramebufferTexture2D");
+
+ // Check if the buffer was built successfully
+ if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+ // Something when wrong. Throw an exception
+ GLESUtil.glesCheckError("glCheckFramebufferStatus");
+ int error = GLES20.glGetError();
+ throw new android.opengl.GLException(error, GLUtils.getEGLErrorString(error));
+ }
+
+ // Apply the effect
+ apply(inputTexId);
+
+ } finally {
+ // Restore the GLES state
+ restoreGLState();
+ }
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setParameter(String parameterKey, Object value) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void release() {
+ if (GLES20.glIsProgram(mProgram)) {
+ GLES20.glDeleteProgram(mProgram);
+ GLESUtil.glesCheckError("glDeleteProgram");
+ }
+ mTexVertices = null;
+ mPosVertices = null;
+ }
+
+ /**
+ * Method that applies the effect.
+ *
+ * @param inputTexId The input texture
+ */
+ void apply(int inputTexId) {
+ // Use our shader program
+ GLES20.glUseProgram(mProgram);
+ GLESUtil.glesCheckError("glUseProgram");
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the vertex attributes
+ GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0, mTexVertices);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTexCoordHandle);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+ GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false, 0, mPosVertices);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPosCoordHandle);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Set parameters
+ applyParameters();
+
+ // Set the input texture
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, inputTexId);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTexSamplerHandle, 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Draw
+ GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GLESUtil.glesCheckError("glClearColor");
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLESUtil.glesCheckError("glClear");
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawArrays");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mTexCoordHandle);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mPosCoordHandle);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Method that applies the parameters of the effect.
+ */
+ void applyParameters() {
+ // Do nothing
+ }
+
+
+ private final void saveGLState() {
+ GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, mOldState, GL_STATE_FBO);
+ GLES20.glGetIntegerv(GLES20.GL_CURRENT_PROGRAM, mOldState, GL_STATE_PROGRAM);
+ GLES20.glGetIntegerv(GLES20.GL_ARRAY_BUFFER_BINDING, mOldState, GL_STATE_ARRAYBUFFER);
+ }
+
+ private final void restoreGLState() {
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mOldState[GL_STATE_FBO]);
+ GLES20.glUseProgram(mOldState[GL_STATE_PROGRAM]);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mOldState[GL_STATE_ARRAYBUFFER]);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffectFactory.java b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffectFactory.java
new file mode 100644
index 0000000..450bb47
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffectFactory.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+/**
+ * A class that defines the own PhotoPhase's effects implementation. This class follows the
+ * rules of the MCA aosp library.
+ */
+public class PhotoPhaseEffectFactory {
+
+ /**
+ * <p>Applies a blur effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_BLUR = "org.cyanogenmod.wallpapers.photophase.effects.BlurEffect";
+
+ /**
+ * <p>Applies an emboss effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_EMBOSS = "org.cyanogenmod.wallpapers.photophase.effects.EmbossEffect";
+
+ /**
+ * <p>Applies a glow effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_GLOW = "org.cyanogenmod.wallpapers.photophase.effects.GlowEffect";
+
+ /**
+ * <p>Applies a halftone effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * <tr>
+ * <td><code>strength</code></td>
+ * <td>The halftone steps multiplier.</td>
+ * <td>Positive float (>0). Higher numbers produce smallest points</td>
+ * </tr>
+ * </table>
+ */
+ public static final String EFFECT_HALFTONE = "org.cyanogenmod.wallpapers.photophase.effects.HalftoneEffect";
+
+ /**
+ * <p>Applies a mirror effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_MIRROR = "org.cyanogenmod.wallpapers.photophase.effects.MirrorEffect";
+
+ /**
+ * <p>Doesn't apply any effect.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_NULL = "org.cyanogenmod.wallpapers.photophase.effects.NullEffect";
+
+ /**
+ * <p>Applies an outline effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_OUTLINE = "org.cyanogenmod.wallpapers.photophase.effects.OutlineEffect";
+
+ /**
+ * <p>Applies a pixelate effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * <tr>
+ * <td><code>strength</code></td>
+ * <td>The pixelate steps multiplier.</td>
+ * <td>Positive float (>0). Higher numbers produce more pixelation.</td>
+ * </tr>
+ * </table>
+ */
+ public static final String EFFECT_PIXELATE = "org.cyanogenmod.wallpapers.photophase.effects.PixelateEffect";
+
+ /**
+ * <p>Applies a pop art (Warhol) effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_POPART = "org.cyanogenmod.wallpapers.photophase.effects.PopArtEffect";
+
+ /**
+ * <p>Applies a TV scan line effect to the image.</p>
+ * <p>Available parameters:</p>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+ public static final String EFFECT_SCANLINES = "org.cyanogenmod.wallpapers.photophase.effects.ScanlinesEffect";
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/PixelateEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/PixelateEffect.java
new file mode 100644
index 0000000..fe37d4c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/PixelateEffect.java
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+
+/**
+ * A pixelate effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * <tr>
+ * <td><code>strength</code></td>
+ * <td>The pixelate strength.</td>
+ * <td>Positive float (>0). Higher numbers produce more pixelation.</td>
+ * </tr>
+ * </table>
+ */
+public class PixelateEffect extends PhotoPhaseEffect {
+
+ private static final String TAG = "PixelateEffect";
+
+ private static final String STRENGTH_PARAMETER = "strength";
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "const float step_w = 0.0015625;\n" +
+ "const float step_h = 0.0027778;\n" +
+ "uniform float strength;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " float offx = floor(v_texcoord.s / (strength * step_w));\n" +
+ " float offy = floor(v_texcoord.t / (strength * step_h));\n" +
+ " vec3 res = texture2D(tex_sampler, vec2(offx * strength * step_w , offy * strength * step_h)).bgr;\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = res;\n" +
+ "}";
+
+ private float mStrength = 8.0f;
+ private int mStepsHandle;
+
+ /**
+ * Constructor of <code>PixelateEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public PixelateEffect(EffectContext ctx, String name) {
+ super(ctx, PixelateEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void init(String vertexShader, String fragmentShader) {
+ super.init(vertexShader, fragmentShader);
+
+ // Parameters
+ mStepsHandle = GLES20.glGetUniformLocation(mProgram, "strength");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void applyParameters() {
+ // Set parameters
+ GLES20.glUniform1f(mStepsHandle, mStrength);
+ GLESUtil.glesCheckError("glUniform1f");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setParameter(String parameterKey, Object value) {
+ if (parameterKey.compareTo(STRENGTH_PARAMETER) == 0) {
+ try {
+ float strength = Float.parseFloat(value.toString());
+ if (strength <= 0) {
+ Log.w(TAG, "strength parameter must be >= 0");
+ return;
+ }
+ mStrength = strength;
+ } catch (NumberFormatException ex) {
+ // Ignore
+ }
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/PopArtEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/PopArtEffect.java
new file mode 100644
index 0000000..df2b18c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/PopArtEffect.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.
+ */
+//
+// Based on the shaders of kodemongki:
+// http://kodemongki.blogspot.com.es/2011/06/kameraku-custom-shader-effects-example.html
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * A pop art (Warhol) effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class PopArtEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec3 col = texture2D(tex_sampler, v_texcoord).bgr;\n" +
+ " float y = 0.3 *col.r + 0.59 * col.g + 0.11 * col.b;\n" +
+ " y = y < 0.3 ? 0.0 : (y < 0.6 ? 0.5 : 1.0);\n" +
+ " if (y == 0.5)\n" +
+ " col = vec3(0.8, 0.0, 0.0);\n" +
+ " else if (y == 1.0)\n" +
+ " col = vec3(0.9, 0.9, 0.0);\n" +
+ " else\n" +
+ " col = vec3(0.0, 0.0, 0.0);\n" +
+ " gl_FragColor.a = 1.0;\n" +
+ " gl_FragColor.rgb = col;\n" +
+ "}";
+
+ /**
+ * Constructor of <code>PopArtEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public PopArtEffect(EffectContext ctx, String name) {
+ super(ctx, PopArtEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/ScanlinesEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/ScanlinesEffect.java
new file mode 100644
index 0000000..cd78307
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/ScanlinesEffect.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+//
+// Based on the shaders of Max Maischein of App-VideoMixer:
+// http://cpansearch.perl.org/src/CORION/App-VideoMixer-0.02/filters/scanlines.glsl
+//
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.media.effect.EffectContext;
+
+/**
+ * A TV scanline effect<br/>
+ * <table>
+ * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+ * </table>
+ */
+public class ScanlinesEffect extends PhotoPhaseEffect {
+
+ private static final String FRAGMENT_SHADER =
+ "precision mediump float;\n" +
+ "uniform sampler2D tex_sampler;\n" +
+ "uniform float offset;\n" +
+ "float frequency = 83.0;\n" +
+ "varying vec2 v_texcoord;\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " float global_pos = (v_texcoord.y + offset) * frequency;\n" +
+ " float wave_pos = cos((fract(global_pos) - 0.5)*3.14);\n" +
+ " vec4 pel = texture2D(tex_sampler, v_texcoord);\n" +
+ " gl_FragColor = mix(vec4(0,0,0,0), pel, wave_pos);\n" +
+ "}";
+
+ /**
+ * Constructor of <code>ScanlinesEffect</code>.
+ *
+ * @param ctx The effect context
+ * @param name The effect name
+ */
+ public ScanlinesEffect(EffectContext ctx, String name) {
+ super(ctx, ScanlinesEffect.class.getName());
+ init(VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+}
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..83f067d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/model/Album.java
@@ -0,0 +1,110 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+/**
+ * A class that represents an album
+ */
+public class Album implements Comparable<Album>, Cloneable {
+
+ private Drawable mIcon;
+ private String mPath;
+ private String mName;
+ private String mDate;
+ private boolean mSelected;
+ 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 mSelected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.mSelected = 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);
+ }
+
+ @Override
+ public Object clone() {
+ Album album = new Album();
+ album.mIcon = mIcon;
+ album.mPath = mPath;
+ album.mName = mName;
+ album.mDate = mDate;
+ album.mItems = new ArrayList<String>(mItems);
+ album.mSelectedItems = new ArrayList<String>(mSelectedItems);
+ album.mSelected = mSelected;
+ return album;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/model/Disposition.java b/src/org/cyanogenmod/wallpapers/photophase/model/Disposition.java
new file mode 100644
index 0000000..35cc0e9
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/model/Disposition.java
@@ -0,0 +1,118 @@
+/*
+ * 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 org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+
+/**
+ * A class that holds a {@link PhotoFrame} disposition.
+ */
+public class Disposition implements Comparable<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 + "]";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int compareTo(Disposition another) {
+ if (x == another.x && y == another.y && w == another.w && h == another.h) {
+ return 0;
+ }
+ if (x < another.x) {
+ return -1;
+ }
+ if (x > another.x) {
+ return 1;
+ }
+ if (y < another.y) {
+ return -1;
+ }
+ if (y > another.y) {
+ return 1;
+ }
+ if (w < another.w) {
+ return -1;
+ }
+ if (w > another.w) {
+ return 1;
+ }
+ if (h < another.h) {
+ return -1;
+ }
+ return 1;
+ }
+}
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..6e41308
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java
@@ -0,0 +1,496 @@
+/*
+ * 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.content.res.Resources;
+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.util.Log;
+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.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures;
+import org.cyanogenmod.wallpapers.photophase.widgets.CardLayout;
+import org.cyanogenmod.wallpapers.photophase.widgets.VerticalEndlessScroller;
+import org.cyanogenmod.wallpapers.photophase.widgets.VerticalEndlessScroller.OnEndScrollListener;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+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 implements OnEndScrollListener {
+
+ private static final String TAG = "ChoosePicturesFragment";
+
+ private static final boolean DEBUG = false;
+
+ private static final int AMOUNT_OF_ADDED_STEPS = 5;
+
+ 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;
+ unregister();
+ Cursor c = mContentResolver.query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[]{ MediaStore.MediaColumns.DATA },
+ null,
+ null,
+ MediaStore.MediaColumns.DATA);
+ if (c != null) {
+ try {
+ long start = System.currentTimeMillis();
+ if (DEBUG) Log.v(TAG, "Media library:");
+ while (c.moveToNext()) {
+ // Only valid files (those i can read)
+ String p = c.getString(0);
+ if (DEBUG) Log.v(TAG, "\t" + p);
+ if (p != null) {
+ File f = new File(p);
+ if (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);
+ mOriginalAlbums.add((Album)album.clone());
+ }
+ 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());
+ }
+ }
+ }
+ }
+
+ // Add the last album
+ if (album != null) {
+ mAlbums.add(album);
+ mOriginalAlbums.add((Album)album.clone());
+ }
+ long end = System.currentTimeMillis();
+ if (DEBUG) Log.v(TAG, "Library loaded in " + (end - start) + " miliseconds");
+
+ } finally {
+ c.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPostExecute(Void result) {
+ Resources res = getActivity().getResources();
+ int size = (int)(res.getDimension(R.dimen.album_size) +
+ res.getDimension(R.dimen.small_margin));
+ mScroller.setEndPadding(size * AMOUNT_OF_ADDED_STEPS);
+ int height = mScroller.getMeasuredHeight();
+ int steps = (height / size) + AMOUNT_OF_ADDED_STEPS;
+
+ // Create the views an force a redraw the items
+ mAlbumViews = new ArrayList<View>(mAlbums.size());
+ for (Album item : mAlbums) {
+ mAlbumViews.add(createAlbumView(item));
+ }
+ doEndScroll(steps, true);
+
+ // We not need Hardware acceleration anymore (no more animations)
+ // Disable drawing cache
+ mScroller.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ mScroller.setDrawingCacheEnabled(false);
+ mScroller.setSmoothScrollingEnabled(true);
+ }
+ };
+
+ /*package*/ ContentResolver mContentResolver;
+
+ /*package*/ List<Album> mAlbums;
+ /*package*/ List<View> mAlbumViews;
+ /*package*/ List<Album> mOriginalAlbums;
+ /*package*/ List<AlbumsFlip3dAnimationController> mAnimationControllers;
+
+ /*package*/ Set<String> mSelectedAlbums;
+ private Set<String> mOriginalSelectedAlbums;
+
+ /*package*/ VerticalEndlessScroller mScroller;
+ private CardLayout mAlbumsPanel;
+
+ /*package*/ boolean mSelectionChanged;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContentResolver = getActivity().getContentResolver();
+
+ // Create an empty album
+ mAlbums = new ArrayList<Album>();
+ mOriginalAlbums = new ArrayList<Album>();
+ mAnimationControllers = new ArrayList<AlbumsFlip3dAnimationController>();
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ // Load the albums user selection
+ mOriginalSelectedAlbums = Preferences.Media.getSelectedMedia();
+ mSelectedAlbums = new HashSet<String>(mOriginalSelectedAlbums);
+ mSelectionChanged = false;
+
+ setHasOptionsMenu(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mAlbumsLoaderTask.getStatus().compareTo(Status.PENDING) == 0) {
+ mAlbumsLoaderTask.cancel(true);
+ }
+ unbindDrawables(mAlbumsPanel);
+ unregister();
+
+ // Notify that the settings was changed
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ if (mSelectionChanged) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE);
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, Boolean.TRUE);
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE);
+ }
+ getActivity().sendBroadcast(intent);
+ }
+
+ /*package*/ void unregister() {
+ mAlbums.clear();
+ mOriginalAlbums.clear();
+ mAnimationControllers.clear();
+ }
+
+ /**
+ * 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
+ mScroller =
+ (VerticalEndlessScroller)inflater.inflate(
+ R.layout.choose_picture_fragment, container, false);
+ mScroller.setCallback(this);
+ mAlbumsPanel = (CardLayout)mScroller.findViewById(R.id.albums_panel);
+
+ // Force Hardware acceleration
+ if (!mScroller.isHardwareAccelerated()) {
+ mScroller.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ if (!mAlbumsPanel.isHardwareAccelerated()) {
+ mAlbumsPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ // Load the albums
+ mAlbumsLoaderTask.execute();
+
+ return mScroller;
+ }
+
+ /**
+ * {@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;
+ case R.id.mnu_restore:
+ restoreData();
+ return true;
+ case R.id.mnu_invert_all:
+ invertAll();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Method that restores the albums to its original state
+ */
+ private void restoreData() {
+ // Restore and the albums the selection
+ mSelectedAlbums = new HashSet<String>(mOriginalSelectedAlbums);
+ mAlbums.clear();
+ for (Album album : mOriginalAlbums) {
+ mAlbums.add((Album)album.clone());
+ }
+
+ // Update all the views
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ updateAll();
+ }
+
+ /**
+ * Method that inverts the selection of all the albums
+ */
+ private void invertAll() {
+ // Restore and the albums the selection
+ mSelectedAlbums = new HashSet<String>();
+ for (Album album : mAlbums) {
+ album.setSelected(!album.isSelected());
+ album.setSelectedItems(new ArrayList<String>());
+ if (album.isSelected()) {
+ mSelectedAlbums.add(album.getPath());
+ } else {
+ mSelectedAlbums.addAll(album.getSelectedItems());
+ }
+ }
+
+ // Update all the views
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ updateAll();
+ }
+
+ /**
+ * Method that updates the current state of all the albums
+ */
+ private void updateAll() {
+ // Update every view (albums and views should have the same size)
+ int count = mAlbumsPanel.getChildCount();
+ for (int i = 0; i < count; i++) {
+ Album album = mAlbums.get(i);
+ View v = mAlbumsPanel.getChildAt(i);
+ AlbumInfo albumInfo = (AlbumInfo)v.findViewById(R.id.album_info);
+ AlbumPictures albumPictures = (AlbumPictures)v.findViewById(R.id.album_pictures);
+ albumInfo.updateView(album);
+ albumPictures.updateView(album, true);
+ }
+
+ // Restore the preference
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+
+ // Restore all the animations states
+ for (AlbumsFlip3dAnimationController controller : mAnimationControllers) {
+ controller.reset();
+ }
+ }
+
+ /**
+ * Method that creates a new album to the card layout
+ *
+ * @param album The album to create
+ * @return View The view create
+ */
+ View createAlbumView(final 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.post(new Runnable() {
+ @Override
+ public void run() {
+ 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, true);
+
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+
+ @Override
+ public void onAlbumDeselected(Album ref) {
+ // Remove all pictures of the album
+ removeAlbumItems(ref);
+ ref.setSelected(false);
+ albumPictures.updateView(ref, true);
+
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+
+
+ });
+
+ // Load the album picture data
+ albumPictures.updateView(album, false);
+ 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);
+
+ Preferences.Media.setSelectedMedia(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+ });
+
+ // Register the animation controller
+ AlbumsFlip3dAnimationController controller = new AlbumsFlip3dAnimationController(albumInfo, albumPictures);
+ controller.register();
+ mAnimationControllers.add(controller);
+
+ return 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();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onEndScroll() {
+ doEndScroll(AMOUNT_OF_ADDED_STEPS, false);
+ }
+
+ /**
+ * Method that performs a scroll creating new items
+ *
+ * @param amount The amount of items to create
+ * @param animate If the add should be animated
+ */
+ /*package*/ synchronized void doEndScroll(int amount, boolean animate) {
+ for (int i = 0; i < amount; i++) {
+ //Add to the panel of cards
+ if (mAlbumViews == null || mAlbumViews.isEmpty()) {
+ break;
+ }
+ mAlbumsPanel.addCard(mAlbumViews.remove(0), animate);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java
new file mode 100644
index 0000000..b0204c3
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java
@@ -0,0 +1,217 @@
+/*
+ * 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.PreferenceFragment;
+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.model.Disposition;
+import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView;
+import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView.OnFrameSelectedListener;
+import org.cyanogenmod.wallpapers.photophase.widgets.ResizeFrame;
+
+import java.util.List;
+
+/**
+ * An abstract fragment class that allow to choose the layout disposition of the wallpaper.
+ */
+public abstract class DispositionFragment
+ extends PreferenceFragment implements OnFrameSelectedListener {
+
+ private Runnable mRedraw = new Runnable() {
+ @Override
+ public void run() {
+ if (getActivity() == null) return;
+ try {
+ mDispositionView.setDispositions(getUserDispositions(), getCols(), getRows());
+ } catch (Exception ex) {
+ // Ignored
+ }
+ }
+ };
+
+ /*package*/ DispositionView mDispositionView;
+
+ private MenuItem mDeleteMenu;
+
+ /**
+ * Constructor of <code>DispositionFragment</code>
+ */
+ public DispositionFragment() {
+ super();
+ }
+
+ /**
+ * Method that returns the current user preference for the disposition
+ *
+ * @return List<Disposition> The current user preference dispositions
+ */
+ public abstract List<Disposition> getUserDispositions();
+
+ /**
+ * Method that returns the default preference for the disposition
+ *
+ * @return List<Disposition> The default preference dispositions
+ */
+ public abstract List<Disposition> getDefaultDispositions();
+
+ /**
+ * Method that request to save the dispositions
+ *
+ * @param dispositions The dispositions to save
+ */
+ public abstract void saveDispositions(List<Disposition> dispositions);
+
+ /**
+ * Method that returns the number of rows to use
+ *
+ * @return int The number of rows
+ */
+ public abstract int getRows();
+
+ /**
+ * Method that returns the number of cols to use
+ *
+ * @return int The number of cols
+ */
+ public abstract int getCols();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ setHasOptionsMenu(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ ViewGroup v = (ViewGroup)inflater.inflate(R.layout.choose_disposition_fragment, container, false);
+ mDispositionView = (DispositionView)v.findViewById(R.id.disposition_view);
+ mDispositionView.setResizeFrame((ResizeFrame)v.findViewById(R.id.resize_frame));
+ mDispositionView.setOnFrameSelectedListener(this);
+ mDispositionView.post(mRedraw);
+ return v;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (mDispositionView != null) {
+ mDispositionView.removeCallbacks(mRedraw);
+ if (mDispositionView.isChanged()) {
+ saveDispositions(mDispositionView.getDispositions());
+ }
+ }
+
+ // Notify that the settings was changed
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ if (mDispositionView.isChanged()) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE);
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, Boolean.TRUE);
+ }
+ getActivity().sendBroadcast(intent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.dispositions, menu);
+ mDeleteMenu = menu.findItem(R.id.mnu_delete);
+ if (mDeleteMenu != null) {
+ mDeleteMenu.setVisible(false);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mnu_ok:
+ getActivity().finish();
+ return true;
+ case R.id.mnu_restore:
+ restoreData();
+ return true;
+ case R.id.mnu_delete:
+ deleteFrame();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Method that restores the disposition view to the default state
+ */
+ private void restoreData() {
+ mDispositionView.setDispositions(getUserDispositions(), getCols(), getRows());
+ }
+
+ /**
+ * Method that restores the disposition view to the default state
+ */
+ private void deleteFrame() {
+ mDispositionView.deleteCurrentFrame();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFrameSelectedListener(View v) {
+ if (mDeleteMenu != null) {
+ mDeleteMenu.setVisible(true);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFrameUnselectedListener() {
+ if (mDeleteMenu != null) {
+ mDeleteMenu.setVisible(false);
+ }
+ }
+}
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..4dd6da8
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java
@@ -0,0 +1,160 @@
+/*
+ * 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.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.MultiSelectListPreference;
+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.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.preferences.SeekBarProgressPreference.OnDisplayProgress;
+import org.cyanogenmod.wallpapers.photophase.widgets.ColorPickerPreference;
+
+/**
+ * A fragment class with all the general settings
+ */
+public class GeneralPreferenceFragment extends PreferenceFragment {
+
+ private static final String TAG = "GeneralPreferenceFragment";
+
+ private static final boolean DEBUG = false;
+
+ private SeekBarProgressPreference mWallpaperDim;
+ private ColorPickerPreference mBackgroundColor;
+ private ListPreference mTouchActions;
+ private CheckBoxPreference mFixAspectRatio;
+ private MultiSelectListPreference mTransitionsTypes;
+ private SeekBarProgressPreference mTransitionsInterval;
+ private MultiSelectListPreference 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_fix_aspect_ratio") == 0) {
+ mRedrawFlag = true;
+ } 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 SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final Resources res = getActivity().getResources();
+
+ // 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);
+
+ mTouchActions = (ListPreference)findPreference("ui_touch_action");
+ mTouchActions.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mFixAspectRatio = (CheckBoxPreference)findPreference("ui_fix_aspect_ratio");
+ mFixAspectRatio.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mTransitionsTypes = (MultiSelectListPreference)findPreference("ui_transition_types");
+ mTransitionsTypes.setOnPreferenceChangeListener(mOnChangeListener);
+
+ final int[] transitionsIntervals = res.getIntArray(R.array.transitions_intervals_values);
+ mTransitionsInterval = (SeekBarProgressPreference)findPreference("ui_transition_interval");
+ mTransitionsInterval.setFormat(getString(R.string.pref_general_transitions_interval_format));
+ mTransitionsInterval.setMax(transitionsIntervals.length - 1);
+ int transitionInterval = prefs.getInt("ui_transition_interval",
+ Preferences.General.Transitions.DEFAULT_TRANSITION_INTERVAL_INDEX);
+ if (transitionInterval > (transitionsIntervals.length - 1)) {
+ mTransitionsInterval.setProgress(
+ Preferences.General.Transitions.DEFAULT_TRANSITION_INTERVAL_INDEX);
+ }
+ mTransitionsInterval.setOnDisplayProgress(new OnDisplayProgress() {
+ @Override
+ public String onDisplayProgress(int progress) {
+ return String.valueOf(transitionsIntervals[progress] / 1000);
+ }
+ });
+ mTransitionsInterval.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mEffectsTypes = (MultiSelectListPreference)findPreference("ui_effect_types");
+ mEffectsTypes.setOnPreferenceChangeListener(mOnChangeListener);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java
new file mode 100644
index 0000000..77267e6
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.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.preferences;
+
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.model.Disposition;
+import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil;
+
+import java.util.List;
+
+/**
+ * A fragment class that allow to choose the layout disposition of the wallpaper for landscape
+ * screen.
+ */
+public class LandscapeDispositionFragment extends DispositionFragment {
+
+ /**
+ * Constructor of <code>LandscapeDispositionFragment</code>
+ */
+ public LandscapeDispositionFragment() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Disposition> getUserDispositions() {
+ return Preferences.Layout.getLandscapeDisposition();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Disposition> getDefaultDispositions() {
+ return DispositionUtil.toDispositions(
+ Preferences.Layout.DEFAULT_LANDSCAPE_DISPOSITION);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void saveDispositions(List<Disposition> dispositions) {
+ Preferences.Layout.setLandscapeDisposition(getActivity(), dispositions);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getRows() {
+ // inverted
+ return Preferences.Layout.getCols();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCols() {
+ // inverted
+ return Preferences.Layout.getRows();
+ }
+}
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..e03116e
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+/**
+ * A fragment class with all the media settings
+ */
+public class MediaPreferenceFragment extends PreferenceFragment {
+
+ private static final String TAG = "MediaPreferenceFragment";
+
+ private static final boolean DEBUG = false;
+
+ 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(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);
+ intent.putExtra(PreferencesProvider.EXTRA_ACTION_MEDIA_USER_RELOAD_REQUEST, 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..4e8c46e
--- /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/PortraitDispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java
new file mode 100644
index 0000000..8763e1e
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java
@@ -0,0 +1,90 @@
+/*
+ * 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.pm.ActivityInfo;
+import android.os.Bundle;
+
+import org.cyanogenmod.wallpapers.photophase.model.Disposition;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil;
+
+import java.util.List;
+
+/**
+ * A fragment class that allow to choose the layout disposition of the wallpaper for portrait
+ * screen.
+ */
+public class PortraitDispositionFragment extends DispositionFragment {
+
+ /**
+ * Constructor of <code>PortraitDispositionFragment</code>
+ */
+ public PortraitDispositionFragment() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Disposition> getUserDispositions() {
+ return Preferences.Layout.getPortraitDisposition();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Disposition> getDefaultDispositions() {
+ return DispositionUtil.toDispositions(
+ Preferences.Layout.DEFAULT_PORTRAIT_DISPOSITION);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void saveDispositions(List<Disposition> dispositions) {
+ Preferences.Layout.setPortraitDisposition(getActivity(), dispositions);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getRows() {
+ return Preferences.Layout.getRows();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCols() {
+ return Preferences.Layout.getCols();
+ }
+}
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..1bf8359
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java
@@ -0,0 +1,459 @@
+/*
+ * 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 android.content.res.Resources;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.effects.Effects.EFFECTS;
+import org.cyanogenmod.wallpapers.photophase.model.Disposition;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil;
+
+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";
+
+ /**
+ * An extra setting that indicates that the media reload becomes from a user
+ *
+ * @see #EXTRA_FLAG_MEDIA_RELOAD
+ * {@hide}
+ */
+ public static final String EXTRA_ACTION_MEDIA_USER_RELOAD_REQUEST = "action_media_user_reload_req";
+
+ /**
+ * The shared preferences file
+ */
+ public static final String PREFERENCES_FILE = "org.cyanogenmod.wallpapers.photophase";
+
+ private static Map<String, ?> mPreferences = new HashMap<String, Object>();
+
+ /**
+ * @hide
+ */
+ /*package*/ static int[] TRANSITIONS_INTERVALS;
+
+ /**
+ * 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();
+
+ final Resources res = context.getResources();
+ TRANSITIONS_INTERVALS = res.getIntArray(R.array.transitions_intervals_values);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Return the current user preference of the action to do when a frame is tap.
+ *
+ * @return TouchAction The action (default NONE)
+ */
+ public static TouchAction getTouchAction() {
+ return TouchAction.fromValue(Integer.valueOf(getString("ui_touch_action", "0")));
+ }
+
+ /**
+ * Return the current user preference about fix or not fix the aspect ratio
+ * of the image by cropping the image.
+ *
+ * @return boolean Indicates if the image should be cropped
+ */
+ public static boolean isFixAspectRatio() {
+ return getBoolean("ui_fix_aspect_ratio", true);
+ }
+
+ /**
+ * Transitions preferences
+ */
+ public static class Transitions {
+ /**
+ * The default transition interval
+ */
+ public static final int DEFAULT_TRANSITION_INTERVAL_INDEX = 2;
+
+ /**
+ * Return the current user preference about the transition to apply to
+ * the pictures of the wallpaper.
+ *
+ * @return TRANSITIONS[] The transition to apply to the wallpaper's pictures
+ */
+ public static TRANSITIONS[] getTransitionTypes() {
+ Set<String> set = getStringSet("ui_transition_types", new HashSet<String>());
+ if (set.isEmpty()) {
+ // Return all the transitions if no one is selected
+ return TRANSITIONS.getValidTranstions();
+ }
+ String[] values = set.toArray(new String[set.size()]);
+ int count = values.length;
+ TRANSITIONS[] transitions = new TRANSITIONS[count];
+ for (int i = 0; i < count; i++) {
+ transitions[i] = TRANSITIONS.fromOrdinal(Integer.valueOf(values[i]));
+ }
+ return transitions;
+ }
+
+ /**
+ * 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 interval = getInt("ui_transition_interval",
+ DEFAULT_TRANSITION_INTERVAL_INDEX);
+ return TRANSITIONS_INTERVALS[interval];
+ }
+ }
+
+ /**
+ * Effects preferences
+ */
+ public static class Effects {
+ /**
+ * Return the current user preference about the effect to apply to
+ * the pictures of the wallpaper.
+ *
+ * @return EFFECTS[] The effects to apply to the wallpaper's pictures
+ */
+ public static EFFECTS[] getEffectTypes() {
+ Set<String> defaults = new HashSet<String>();
+ defaults.add(String.valueOf(EFFECTS.NO_EFFECT.ordinal()));
+ Set<String> set = getStringSet("ui_effect_types", defaults);
+ String[] values = set.toArray(new String[set.size()]);
+ int count = values.length;
+ EFFECTS[] effects = new EFFECTS[count];
+ for (int i = 0; i < count; i++) {
+ effects[i] = EFFECTS.fromOrdinal(Integer.valueOf(values[i]));
+ }
+ return effects;
+ }
+ }
+ }
+
+ /**
+ * 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 if the app must be select new albums when they are discovered.
+ *
+ * @return boolean If the app must be select new albums when they are discovered.
+ */
+ public static boolean isAutoSelectNewAlbums() {
+ return getBoolean("ui_media_auto_select_new", Boolean.TRUE);
+ }
+
+ // Internal settings (non-UI)
+ /**
+ * 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> getSelectedMedia() {
+ return getStringSet("media_selected_media", 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 setSelectedMedia(Context context, Set<String> selection) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ Editor editor = preferences.edit();
+ editor.putStringSet("media_selected_media", selection);
+ editor.commit();
+ reload(context);
+ }
+
+ /**
+ * Method that returns the list of the name of the albums seen by the
+ * last media discovery scan.
+ *
+ * @return Set<String> The list of albums and pictures to be displayed
+ */
+ public static Set<String> getLastDiscorevedAlbums() {
+ return getStringSet("media_last_disvored_albums", new HashSet<String>());
+ }
+
+ /**
+ * Method that sets the list of the name of the albums seen by the
+ * last media discovery scan.
+ *
+ * @param context The current context
+ * @param albums The albums seen by the last media discovery scan
+ */
+ public static synchronized void setLastDiscorevedAlbums(Context context, Set<String> albums) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ Editor editor = preferences.edit();
+ editor.putStringSet("media_last_disvored_albums", albums);
+ editor.commit();
+ reload(context);
+ }
+ }
+
+ /**
+ * Layout preferences
+ */
+ public static class Layout {
+
+ private static final int DEFAULT_COLS = 4;
+ private static final int DEFAULT_ROWS = 7;
+ public static final String DEFAULT_PORTRAIT_DISPOSITION = "0x0:2x1|0x2:1x3|0x4:3x6|2x2:3x3|3x0:3x0|3x1:3x1";
+ public static final String DEFAULT_LANDSCAPE_DISPOSITION = "0x0:2x3|3x0:5x1|3x2:4x3|5x2:6x3|6x0:6x0|6x1:6x1";
+
+ /**
+ * 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 on portrait screen. 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> getPortraitDisposition() {
+ return DispositionUtil.toDispositions(
+ getString("ui_layout_portrait_disposition", DEFAULT_PORTRAIT_DISPOSITION));
+ }
+
+ /**
+ * Sets the disposition of the photo frames in the wallpaper on landscape screen.
+ *
+ * @param context The current context
+ * @param dispositions The photo frames dispositions
+ */
+ public static void setPortraitDisposition(Context context, List<Disposition> dispositions) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ Editor editor = preferences.edit();
+ editor.putString("ui_layout_portrait_disposition",
+ DispositionUtil.fromDispositions(dispositions));
+ editor.commit();
+ reload(context);
+ }
+
+ /**
+ * Returns the disposition of the photo frames in the wallpaper on landscape screen. 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> getLandscapeDisposition() {
+ return DispositionUtil.toDispositions(
+ getString("ui_layout_landscape_disposition", DEFAULT_LANDSCAPE_DISPOSITION));
+ }
+
+ /**
+ * Sets the disposition of the photo frames in the wallpaper on landscape screen.
+ *
+ * @param context The current context
+ * @param dispositions The photo frames dispositions
+ */
+ public static void setLandscapeDisposition(Context context, List<Disposition> dispositions) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ Editor editor = preferences.edit();
+ editor.putString("ui_layout_landscape_disposition",
+ DispositionUtil.fromDispositions(dispositions));
+ editor.commit();
+ reload(context);
+ }
+ }
+
+ }
+}
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/preferences/TouchAction.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/TouchAction.java
new file mode 100644
index 0000000..498c1bd
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/TouchAction.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+/**
+ * An enumeration with all the touch actions supported
+ */
+public enum TouchAction {
+ /**
+ * No action
+ */
+ NONE(0),
+ /**
+ * Force transition of the frame
+ */
+ TRANSITION(1),
+ /**
+ * Open the picture of the frame
+ */
+ OPEN(2),
+ /**
+ * Share/send the picture of the frame
+ */
+ SHARE(3);
+
+ private final int mValue;
+
+ /**
+ * Constructor of <code>TouchAction</code>
+ *
+ * @param id The unique identifier
+ */
+ private TouchAction(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Method that returns the value
+ *
+ * @return int The value
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Method that gets the reference of a TouchAction from its value
+ *
+ * @param value The value
+ * @return TouchAction The reference
+ */
+ public static final TouchAction fromValue(int value) {
+ if (value == TRANSITION.mValue) return TRANSITION;
+ if (value == OPEN.mValue) return OPEN;
+ if (value == SHARE.mValue) return SHARE;
+ return NONE;
+ }
+}
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..20b5fa4
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java
@@ -0,0 +1,150 @@
+/*
+ * 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.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.R;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * 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 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);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void draw(float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Enable properties
+ if (mColor.a != 1.0f) {
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glEnable");
+ GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ GLESUtil.glesCheckError("glBlendFunc");
+ }
+
+ // Set the program and its attributes
+ GLES20.glUseProgram(mProgramHandler);
+ GLESUtil.glesCheckError("glUseProgram");
+
+ // Position
+ mVertexBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandler, 2, GLES20.GL_FLOAT, false, 0, 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
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ 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();
+ mVertexBuffer = 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/shapes/OopsShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java
new file mode 100644
index 0000000..ad69687
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java
@@ -0,0 +1,292 @@
+/*
+ * 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.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.Colors;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil.GLESTextureInfo;
+import org.cyanogenmod.wallpapers.photophase.R;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * A shape to draw an oops message
+ */
+public class OopsShape implements DrawableShape {
+
+ private static final int VERTEX_SHADER = R.raw.default_vertex_shader;
+ private static final int FRAGMENT_SHADER = R.raw.default_fragment_shader;
+
+ // The texture coordinates
+ private static final float[] TEXTURE_COORDS = {
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f
+ };
+
+ // The vertex position coordinates
+ private static final float[] VERTEX_COORDS_PORTRAIT = {
+ -0.75f, -0.5f,
+ 0.75f, -0.5f,
+ -0.75f, 0.5f,
+ 0.75f, 0.5f
+ };
+ private static final float[] VERTEX_COORDS_LANDSCAPE = {
+ -0.5f, -0.75f,
+ 0.5f, -0.75f,
+ -0.5f, 0.75f,
+ 0.5f, 0.75f
+ };
+
+ private FloatBuffer mPositionBuffer;
+ private FloatBuffer mTextureBuffer;
+
+ private int[] mProgramHandlers;
+ private int[] mTextureHandlers;
+ private int[] mPositionHandlers;
+ private int[] mTextureCoordHandlers;
+ private int[] mMVPMatrixHandlers;
+
+ private String mMessage;
+
+ private GLESTextureInfo mOopsImageTexture;
+ private GLESTextureInfo mOopsTextTexture;
+
+ /**
+ * Constructor of <code>OopsShape</code>
+ *
+ * @param ctx The current context
+ * @param resourceMessageId The resource identifier with the message
+ */
+ public OopsShape(Context ctx, int resourceMessageId) {
+ super();
+
+ int orientation = ctx.getResources().getConfiguration().orientation;
+ float[] vertex = VERTEX_COORDS_PORTRAIT;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ vertex = VERTEX_COORDS_LANDSCAPE;
+ }
+
+ // Load the buffers
+ ByteBuffer bb1 = ByteBuffer.allocateDirect(vertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb1.order(ByteOrder.nativeOrder());
+ mPositionBuffer = bb1.asFloatBuffer();
+ mPositionBuffer.put(vertex);
+ mPositionBuffer.position(0);
+ // -
+ ByteBuffer bb2 = ByteBuffer.allocateDirect(TEXTURE_COORDS.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb2.order(ByteOrder.nativeOrder());
+ mTextureBuffer = bb2.asFloatBuffer();
+ mTextureBuffer.put(TEXTURE_COORDS);
+ mTextureBuffer.position(0);
+
+ // Initialize the structures
+ mProgramHandlers = new int[2];
+ mTextureHandlers = new int[2];
+ mPositionHandlers = new int[2];
+ mTextureCoordHandlers = new int[2];
+ mMVPMatrixHandlers = new int[2];
+
+ // Create all the params
+ for (int i = 0; i < 2; i++) {
+ mProgramHandlers[i] =
+ GLESUtil.createProgram(
+ ctx.getResources(), VERTEX_SHADER, FRAGMENT_SHADER);
+ mTextureHandlers[i] =
+ GLES20.glGetAttribLocation(mProgramHandlers[i], "sTexture");
+ mPositionHandlers[i] =
+ GLES20.glGetAttribLocation(mProgramHandlers[i], "aPosition");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mTextureCoordHandlers[i] =
+ GLES20.glGetAttribLocation(mProgramHandlers[i], "aTextureCoord");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mMVPMatrixHandlers[i] =
+ GLES20.glGetUniformLocation(mProgramHandlers[i], "uMVPMatrix");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+ }
+
+ // Get the localized message
+ mMessage = ctx.getString(resourceMessageId);
+
+ // Load the textures
+ mOopsImageTexture = GLESUtil.loadTexture(ctx, R.drawable.bg_cid_oops, null, null, false);
+ Bitmap textBitmap = text2Bitmap(ctx, mMessage);
+ mOopsTextTexture = GLESUtil.loadTexture(textBitmap, null, null);
+
+ // Recycle
+ mOopsImageTexture.bitmap.recycle();
+ mOopsImageTexture.bitmap = null;
+ textBitmap.recycle();
+ textBitmap = null;
+ mOopsTextTexture.bitmap.recycle();
+ mOopsTextTexture.bitmap = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void draw(float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Clear background
+ 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");
+
+ // Enable blend
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glEnable");
+ GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
+ GLESUtil.glesCheckError("glBlendFunc");
+
+ // Draw the textures
+ drawTexture(matrix, 0, mOopsImageTexture.handle);
+ drawTexture(matrix, 1, mOopsTextTexture.handle);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+ }
+
+ /**
+ * Method that draws a texture
+ *
+ * @param matrix The model-view-projection matrix
+ * @param index The index of the texture
+ * @param texture The texture handler
+ */
+ private void drawTexture(float[] matrix, int index, int texture) {
+ // Use our shader program
+ GLES20.glUseProgram(mProgramHandlers[index]);
+ GLESUtil.glesCheckError("glUseProgram()");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[index], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Texture
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[index], 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[index]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ GLES20.glVertexAttribPointer(mPositionHandlers[index], 2, GLES20.GL_FLOAT, false, 0, mPositionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[index]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Set the input textures
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[index], 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[index]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[index]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Method that requests to remove the internal references and resources.
+ */
+ public void recycle() {
+ // Remove textures
+ if (mOopsImageTexture != null && mOopsImageTexture.handle != 0) {
+ int[] textures = new int[]{mOopsImageTexture.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+ mOopsImageTexture = null;
+ if (mOopsTextTexture != null && mOopsTextTexture.handle != 0) {
+ int[] textures = new int[]{mOopsTextTexture.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+ mOopsTextTexture = null;
+
+ // Remove buffers
+ if (mPositionBuffer != null) {
+ mPositionBuffer.clear();
+ }
+ if (mTextureBuffer != null) {
+ mTextureBuffer.clear();
+ }
+ mPositionBuffer = null;
+ mTextureBuffer = null;
+
+ for (int i = 0; i < 2; i++) {
+ if (GLES20.glIsProgram(mProgramHandlers[i])) {
+ GLES20.glDeleteProgram(mProgramHandlers[i]);
+ GLESUtil.glesCheckError("glDeleteProgram(" + i + ")");
+ }
+ mProgramHandlers[i] = 0;
+ mTextureHandlers[i] = 0;
+ mPositionHandlers[i] = 0;
+ mTextureCoordHandlers[i] = 0;
+ mMVPMatrixHandlers[i] = 0;
+ }
+ }
+
+ /**
+ * Method that converts a text to a bitmap
+ *
+ * @param ctx The current context
+ * @param text The text to draw to the bitmap
+ * @return Bitmap The bitmap with the text
+ */
+ public Bitmap text2Bitmap(Context ctx, String text) {
+ Paint paint = new Paint();
+ Typeface font = Typeface.createFromAsset(ctx.getAssets(), "fonts/Roboto-Bold.ttf");
+ paint.setTypeface(font);
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(24.0f);
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Paint.Align.CENTER);
+ Bitmap src = mOopsImageTexture.bitmap;
+ Bitmap image = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(image);
+ canvas.drawText(text, src.getWidth()/2, src.getHeight() - (src.getHeight() * 0.33f), paint);
+ return image;
+ }
+}
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..bc07b17
--- /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.utils.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/CubeTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/CubeTransition.java
new file mode 100644
index 0000000..bfe300b
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/CubeTransition.java
@@ -0,0 +1,479 @@
+/*
+ * 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.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+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.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A transition that applies a cube effect transition to the picture.
+ */
+public class CubeTransition extends Transition {
+
+ /**
+ * The enumeration of all possibles window movements
+ */
+ public enum WINDOW_MODES {
+ /**
+ * Open the picture from left to right
+ */
+ LEFT_TO_RIGHT,
+ /**
+ * Open the picture from right to left
+ */
+ RIGHT_TO_LEFT
+ }
+
+ private static final int[] VERTEX_SHADER = {R.raw.default_vertex_shader, R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader, R.raw.default_fragment_shader};
+
+ private static final float TRANSITION_TIME = 1000.0f;
+
+ private static final float SCALE_AMOUNT = 0.2f;
+
+ private WINDOW_MODES mMode;
+
+ private boolean mRunning;
+ private long mTime;
+
+ private FloatBuffer mPositionBuffer;
+ private float[] mTranslationMatrix;
+ private float[] mVertex;
+
+ private float mAmount;
+
+ /**
+ * Constructor of <code>CubeTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public CubeTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER);
+
+ // Initialized
+ mTranslationMatrix = new float[16];
+ reset();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.WINDOW;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+ mAmount = getAmount();
+
+ // Create the interal buffer
+ float[] vertex = target.getFrameVertex();
+ if (mPositionBuffer == null) {
+ ByteBuffer bb = ByteBuffer.allocateDirect(vertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ mPositionBuffer = bb.asFloatBuffer();
+ }
+ if (mVertex == null) {
+ mVertex = new float[vertex.length];
+ }
+
+ // Random mode
+ List<WINDOW_MODES> modes =
+ new ArrayList<CubeTransition.WINDOW_MODES>(
+ Arrays.asList(WINDOW_MODES.values()));
+ int low = 0;
+ int high = modes.size() - 1;
+ mMode = modes.get(Utils.getNextRandom(low, high));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ return true;
+ }
+
+ /**
+ * {@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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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
+ if (delta < 1) {
+ applyDstTransition(delta, matrix);
+ applySrcTransition(delta, matrix);
+ } else {
+ applyFinalTransition(matrix);
+ }
+
+ // Transition ending
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+ /**
+ * Apply the source transition
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applySrcTransition(float delta, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(0);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = mTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[0], 0);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Texture
+ FloatBuffer textureBuffer = mTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[0], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ setInternalVertex();
+ float interpolation = delta > 0.5f
+ ? 1 - delta
+ : delta;
+ float w = Math.abs(mVertex[6] - mVertex[4]);
+ switch (mMode) {
+ case RIGHT_TO_LEFT:
+ mVertex[1] -= interpolation * mAmount;
+ mVertex[5] += interpolation * mAmount;
+ mVertex[4] += w * delta;
+ mVertex[0] = mVertex[4];
+ break;
+ case LEFT_TO_RIGHT:
+ mVertex[3] -= interpolation * mAmount;
+ mVertex[7] += interpolation * mAmount;
+ mVertex[6] -= w * delta;
+ mVertex[2] = mVertex[6];
+ break;
+ default:
+ break;
+ }
+ mPositionBuffer.position(0);
+ mPositionBuffer.put(mVertex);
+ mPositionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[0], 2, GLES20.GL_FLOAT, false, 0, mPositionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Calculate the delta angle and the translation and rotate parameters
+ float angle = 0.0f;
+ float translateX = 0.0f;
+ float rotateY = 0.0f;
+ switch (mMode) {
+ case RIGHT_TO_LEFT:
+ angle = delta * 90;
+ rotateY = -1.0f;
+ translateX = mVertex[2] * -1;
+ break;
+ case LEFT_TO_RIGHT:
+ angle = delta * -90;
+ rotateY = -1.0f;
+ translateX = mVertex[0] * -1;
+ break;
+
+ default:
+ break;
+ }
+
+ // Apply the projection and view transformation
+ Matrix.setIdentityM(matrix, 0);
+ Matrix.translateM(mTranslationMatrix, 0, matrix, 0, -translateX, 0.0f, 0.0f);
+ Matrix.rotateM(mTranslationMatrix, 0, mTranslationMatrix, 0, angle, 0.0f, rotateY, 0.0f);
+ Matrix.translateM(mTranslationMatrix, 0, mTranslationMatrix, 0, translateX, 0.0f, 0.0f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, mTranslationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Apply the destination transition
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyDstTransition(float delta, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(1);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = mTransitionTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[1], 0);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Texture
+ FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[1], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ setInternalVertex();
+ float interpolation = delta > 0.5f
+ ? 1 - delta
+ : delta;
+ float w = Math.abs(mVertex[6] - mVertex[4]);
+ switch (mMode) {
+ case LEFT_TO_RIGHT:
+ mVertex[1] -= interpolation * mAmount;
+ mVertex[5] += interpolation * mAmount;
+ mVertex[4] += w * (1 - delta);
+ mVertex[0] = mVertex[4];
+ break;
+ case RIGHT_TO_LEFT:
+ mVertex[3] -= interpolation * mAmount;
+ mVertex[7] += interpolation * mAmount;
+ mVertex[6] -= w * (1 - delta);
+ mVertex[2] = mVertex[6];
+ break;
+ default:
+ break;
+ }
+ mPositionBuffer.position(0);
+ mPositionBuffer.put(mVertex);
+ mPositionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[1], 2, GLES20.GL_FLOAT, false, 0, mPositionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Calculate the delta angle and the translation and rotate parameters
+ float angle = 0.0f;
+ float translateX = 0.0f;
+ float rotateY = 0.0f;
+ switch (mMode) {
+ case LEFT_TO_RIGHT:
+ angle = 90 - (delta * 90);
+ rotateY = -1.0f;
+ translateX = mVertex[2] * -1;
+ break;
+ case RIGHT_TO_LEFT:
+ angle = -90 + (delta * 90);
+ rotateY = -1.0f;
+ translateX = mVertex[0] * -1;
+ break;
+
+ default:
+ break;
+ }
+
+ // Apply the projection and view transformation
+ Matrix.setIdentityM(matrix, 0);
+ Matrix.translateM(mTranslationMatrix, 0, matrix, 0, -translateX, 0.0f, 0.0f);
+ Matrix.rotateM(mTranslationMatrix, 0, mTranslationMatrix, 0, angle, 0.0f, rotateY, 0.0f);
+ Matrix.translateM(mTranslationMatrix, 0, mTranslationMatrix, 0, translateX, 0.0f, 0.0f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, mTranslationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Apply the destination transition (just draw the image)
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyFinalTransition(float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Use our shader program
+ useProgram(1);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Texture
+ FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[1], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = mTransitionTarget.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[1], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Set the input texture
+ int textureHandle = mTransitionTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[1], 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Method that prepares the internal vertex array
+ */
+ private void setInternalVertex() {
+ float[] originalVertex = mTarget.getFrameVertex();
+ System.arraycopy(originalVertex, 0, mVertex, 0, originalVertex.length);
+ }
+
+ /**
+ * Return the scale amount to apply to the transition
+ *
+ * @return float The scale amount
+ */
+ private float getAmount() {
+ return ((mTarget.getFrameWidth() * SCALE_AMOUNT) / 2);
+ }
+}
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..ce50fa4
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java
@@ -0,0 +1,135 @@
+/*
+ * 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 = 600.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.getFrameVertex(), Colors.getBackground());
+ mOverlay.setAlpha(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void apply(float[] matrix) throws GLException {
+ // Check internal vars
+ if (mTarget == null ||
+ mTarget.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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/FlipTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/FlipTransition.java
new file mode 100644
index 0000000..8d0bf79
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/FlipTransition.java
@@ -0,0 +1,257 @@
+/*
+ * 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.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+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;
+
+/**
+ * A transition that applies a translation transition to the picture.
+ */
+public class FlipTransition extends Transition {
+
+ /**
+ * The enumeration of all possibles translations movements
+ */
+ public enum FLIP_MODES {
+ /**
+ * Flip the picture horizontally
+ */
+ HORIZONTAL,
+ /**
+ * Flip the picture vertically
+ */
+ VERTICAL
+ }
+
+ private static final int[] VERTEX_SHADER = {R.raw.default_vertex_shader, R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader, R.raw.default_fragment_shader};
+
+ private static final float TRANSITION_TIME = 600.0f;
+
+ private FLIP_MODES mMode;
+
+ private float[] mTranslationMatrix;
+
+ private boolean mRunning;
+ private long mTime;
+
+ /**
+ * Constructor of <code>FlipTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public FlipTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER);
+
+ // Initialized
+ mTranslationMatrix = new float[16];
+ reset();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.FLIP;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+
+ // Random mode
+ FLIP_MODES[] modes = FLIP_MODES.values();
+ int low = 0;
+ int high = modes.length - 1;
+ mMode = modes[Utils.getNextRandom(low, high)];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ return true;
+ }
+
+ /**
+ * {@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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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
+ applyTransition(delta, matrix, delta <= 0.5 ? mTarget : mTransitionTarget);
+
+ // Transition ending
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+ /**
+ * Apply the transition
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ * @param target The photo frame target
+ */
+ private void applyTransition(float delta, float[] matrix, PhotoFrame target) {
+ // Retrieve the index of the structures
+ int index = delta <= 0.5f ? 0 : 1;
+
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(index);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = target.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[index], 0);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Texture
+ FloatBuffer textureBuffer = target.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[index], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[index]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = target.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[index], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[index]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Calculate the delta angle and the translation and rotate parameters
+ float angle = (delta * 90) / 0.5f;
+ if (index == 1) {
+ angle = 90 - ((delta - 0.5f) * 90) / 0.5f;
+ }
+ float translateX = 0.0f;
+ float translateY = 0.0f;
+ float rotateX = 0.0f;
+ float rotateY = 0.0f;
+ switch (mMode) {
+ case HORIZONTAL:
+ rotateY = -1.0f;
+ translateX = (mTarget.getFrameVertex()[2] - ((mTarget.getFrameVertex()[2] - mTarget.getFrameVertex()[0]) / 2)) * -1;
+ break;
+ case VERTICAL:
+ rotateX = -1.0f;
+ translateY = (mTarget.getFrameVertex()[5] - ((mTarget.getFrameVertex()[5] - mTarget.getFrameVertex()[1]) / 2)) * -1;
+ break;
+
+ default:
+ break;
+ }
+
+ // Apply the projection and view transformation
+ Matrix.setIdentityM(matrix, 0);
+ Matrix.translateM(mTranslationMatrix, 0, matrix, 0, -translateX, -translateY, 0.0f);
+ Matrix.rotateM(mTranslationMatrix, 0, mTranslationMatrix, 0, angle, rotateX, rotateY, 0.0f);
+ Matrix.translateM(mTranslationMatrix, 0, mTranslationMatrix, 0, translateX, translateY, 0.0f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[index], 1, false, mTranslationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[index]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[index]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+}
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..24344d6
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java
@@ -0,0 +1,171 @@
+/*
+ * 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.utils.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;
+/**
+ * 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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+
+ // Draw the current target
+ draw(mTarget, matrix);
+ }
+
+ /**
+ * Method that draws the picture texture
+ *
+ * @param target The target to draw
+ * @param matrix The model-view-projection matrix
+ */
+ protected void draw(PhotoFrame target, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Use our shader program
+ useProgram(0);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Texture
+ FloatBuffer textureBuffer = target.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[0], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = target.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[0], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Set the input texture
+ int textureHandle = target.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[0], 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ 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..d5cf15f
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java
@@ -0,0 +1,111 @@
+/*
+ * 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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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..cdd5438
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.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.transitions;
+
+import android.content.Context;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.utils.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[] mTextureHandlers;
+ 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];
+ mTextureHandlers = 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.getPhotoVertex(),
+ 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]);
+ mTextureHandlers[index] =
+ GLES20.glGetAttribLocation(mProgramHandlers[index], "sTexture");
+ 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()");
+ }
+
+ /**
+ * 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] = -1;
+ mTextureHandlers[i] = -1;
+ mPositionHandlers[i] = -1;
+ mTextureCoordHandlers[i] = -1;
+ mMVPMatrixHandlers[i] = -1;
+ }
+ 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..86dfeb8
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A class that manages all the supported transitions
+ */
+public class Transitions {
+
+ /**
+ * Enumeration of the supported transitions
+ */
+ public enum TRANSITIONS {
+ /**
+ * @see NullTransition
+ */
+ NO_TRANSITION,
+ /**
+ * @see CubeTransition
+ */
+ CUBE,
+ /**
+ * @see FadeTransition
+ */
+ FADE,
+ /**
+ * @see FlipTransition
+ */
+ FLIP,
+ /**
+ * @see SwapTransition
+ */
+ SWAP,
+ /**
+ * @see TranslateTransition
+ */
+ TRANSLATION,
+ /**
+ * @see WindowTransition
+ */
+ WINDOW;
+
+ /**
+ * Method that returns the transition from its ordinal position
+ *
+ * @param ordinal The ordinal position
+ * @return TRANSITIONS The transition or null if wasn't found
+ */
+ public static TRANSITIONS fromOrdinal(int ordinal) {
+ for (TRANSITIONS transition : TRANSITIONS.values()) {
+ if (transition.ordinal() == ordinal) {
+ return transition;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method that the returns an array with all the valid transitions (NO_TRANSITION is not
+ * a valid one).
+ *
+ * @return TRANSITIONS[] The valid transitions
+ */
+ public static TRANSITIONS[] getValidTranstions() {
+ TRANSITIONS[] src = TRANSITIONS.values();
+ TRANSITIONS[] dst = new TRANSITIONS[src.length-1];
+ System.arraycopy(src, 1, dst, 0, src.length-1);
+ return dst;
+ }
+ }
+
+ /**
+ * Method that return the next type of transition to apply the picture.
+ *
+ * @param frame The frame which the translation will be applied to
+ * @return TRANSITIONS The next type of transition to apply
+ */
+ public static TRANSITIONS getNextTypeOfTransition(PhotoFrame frame) {
+ // Get a transition based on the user preference
+ List<TRANSITIONS> transitions =
+ Arrays.asList(Preferences.General.Transitions.getTransitionTypes());
+ TRANSITIONS nextTransition = null;
+ if (transitions.size() > 0) {
+ int low = 0;
+ int high = transitions.size() - 1;
+ int pos = Utils.getNextRandom(low, high);
+ nextTransition = transitions.get(pos);
+ }
+ if (nextTransition == null) {
+ return TRANSITIONS.NO_TRANSITION;
+ }
+
+ // Select the transition if is available
+ if (nextTransition.compareTo(TRANSITIONS.SWAP) == 0) {
+ return TRANSITIONS.SWAP;
+ } else if (nextTransition.compareTo(TRANSITIONS.FADE) == 0) {
+ return TRANSITIONS.FADE;
+ } else if (nextTransition.compareTo(TRANSITIONS.TRANSLATION) == 0) {
+ return TRANSITIONS.TRANSLATION;
+ } else if (nextTransition.compareTo(TRANSITIONS.FLIP) == 0) {
+ return TRANSITIONS.FLIP;
+ } else if (nextTransition.compareTo(TRANSITIONS.WINDOW) == 0) {
+ return TRANSITIONS.WINDOW;
+ } else if (nextTransition.compareTo(TRANSITIONS.CUBE) == 0) {
+ return TRANSITIONS.CUBE;
+ }
+ 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 translation 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);
+ } else if (type.compareTo(TRANSITIONS.FADE) == 0) {
+ return new FadeTransition(ctx, tm);
+ } else if (type.compareTo(TRANSITIONS.TRANSLATION) == 0) {
+ return new TranslateTransition(ctx, tm);
+ } else if (type.compareTo(TRANSITIONS.FLIP) == 0) {
+ return new FlipTransition(ctx, tm);
+ } else if (type.compareTo(TRANSITIONS.WINDOW) == 0) {
+ return new WindowTransition(ctx, tm);
+ } else if (type.compareTo(TRANSITIONS.CUBE) == 0) {
+ return new CubeTransition(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..eb1080b
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java
@@ -0,0 +1,334 @@
+/*
+ * 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.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+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.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.default_vertex_shader, R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader, R.raw.default_fragment_shader};
+
+ private static final float TRANSITION_TIME = 600.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[4] != -1.0f) {
+ modes.remove(TRANSLATE_MODES.RIGHT_TO_LEFT);
+ }
+ if (vertex[6] != 1.0f) {
+ modes.remove(TRANSLATE_MODES.LEFT_TO_RIGHT);
+ }
+ if (vertex[5] != 1.0f) {
+ modes.remove(TRANSLATE_MODES.DOWN_TO_UP);
+ }
+ if (vertex[1] != -1.0f) {
+ modes.remove(TRANSLATE_MODES.UP_TO_DOWN);
+ }
+
+ // Random mode
+ int low = 0;
+ int high = modes.size() - 1;
+ mMode = modes.get(Utils.getNextRandom(low, high));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ float[] vertex = frame.getFrameVertex();
+ if (vertex[4] == -1.0f || vertex[6] == 1.0f ||
+ vertex[5] == 1.0f || vertex[1] == -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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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
+ applyTransitionToDst(delta, matrix);
+ if (delta < 1) {
+ applyTransitionToSrc(delta, matrix);
+ }
+
+ // Transition ending
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+ /**
+ * Apply the transition to the source frame
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyTransitionToSrc(float delta, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(0);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = mTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[0], 0);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Texture
+ FloatBuffer textureBuffer = mTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[0], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = mTarget.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[0], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[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.getFrameWidth()
+ : mTarget.getFrameHeight();
+ 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
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Apply the transition to the destination frame
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyTransitionToDst(float delta, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(1);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = mTransitionTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[1], 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Texture
+ FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[1], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = mTransitionTarget.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[1], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ 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/transitions/WindowTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/WindowTransition.java
new file mode 100644
index 0000000..b09cfaa
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/WindowTransition.java
@@ -0,0 +1,376 @@
+/*
+ * 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 android.view.animation.AccelerateInterpolator;
+
+import org.cyanogenmod.wallpapers.photophase.utils.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.Utils;
+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.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A transition that applies a window effect transition to the picture.
+ */
+public class WindowTransition extends Transition {
+
+ /**
+ * The enumeration of all possibles window movements
+ */
+ public enum WINDOW_MODES {
+ /**
+ * Open the picture from left to right
+ */
+ LEFT_TO_RIGHT,
+ /**
+ * Open the picture from right to left
+ */
+ RIGHT_TO_LEFT
+ }
+
+ private static final int[] VERTEX_SHADER = {R.raw.default_vertex_shader, R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader, R.raw.default_fragment_shader};
+
+ private static final float TRANSITION_TIME = 800.0f;
+
+ private static final float SCALE_AMOUNT = 0.2f;
+
+ private WINDOW_MODES mMode;
+
+ private float[] mTranslationMatrix;
+
+ private boolean mRunning;
+ private long mTime;
+
+ private AccelerateInterpolator mInterpolation;
+ private float mAmount;
+
+ /**
+ * Constructor of <code>WindowTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public WindowTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER);
+
+ // Initialized
+ mTranslationMatrix = new float[16];
+ reset();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.WINDOW;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+ mInterpolation = new AccelerateInterpolator();
+ mAmount = getAmount();
+
+ // Discard all non-supported modes
+ List<WINDOW_MODES> modes =
+ new ArrayList<WindowTransition.WINDOW_MODES>(
+ Arrays.asList(WINDOW_MODES.values()));
+ float[] vertex = target.getFrameVertex();
+ if (vertex[4] != -1.0f) {
+ modes.remove(WINDOW_MODES.RIGHT_TO_LEFT);
+ }
+ if (vertex[6] != 1.0f) {
+ modes.remove(WINDOW_MODES.LEFT_TO_RIGHT);
+ }
+
+ // Random mode
+ int low = 0;
+ int high = modes.size() - 1;
+ mMode = modes.get(Utils.getNextRandom(low, high));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ float[] vertex = frame.getFrameVertex();
+ if (vertex[4] == -1.0f || vertex[6] == 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.getPositionBuffer() == null ||
+ mTarget.getTextureBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPositionBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == 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
+ applyDstTransition(matrix);
+ if (delta < 1) {
+ applySrcTransition(delta, matrix);
+ }
+
+ // Transition ending
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+ /**
+ * Apply the source transition
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ * @param target The photo frame target
+ */
+ private void applySrcTransition(float delta, float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Set the program
+ useProgram(0);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Set the input texture
+ int textureHandle = mTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[0], 0);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Texture
+ FloatBuffer textureBuffer = mTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[0], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ float[] vertex = cloneVertex();
+ float interpolation = mInterpolation.getInterpolation(delta);
+ switch (mMode) {
+ case LEFT_TO_RIGHT:
+ vertex[1] -= interpolation * mAmount;
+ vertex[5] += interpolation * mAmount;
+ break;
+ case RIGHT_TO_LEFT:
+ vertex[3] -= interpolation * mAmount;
+ vertex[7] += interpolation * mAmount;
+ break;
+ default:
+ break;
+ }
+ ByteBuffer bb = ByteBuffer.allocateDirect(vertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ FloatBuffer positionBuffer = bb.asFloatBuffer();
+ positionBuffer.put(vertex);
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[0], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Calculate the delta angle and the translation and rotate parameters
+ float angle = 0.0f;
+ float translateX = 0.0f;
+ float rotateY = 0.0f;
+ switch (mMode) {
+ case LEFT_TO_RIGHT:
+ angle = delta * 90;
+ rotateY = -1.0f;
+ translateX = mTarget.getFrameVertex()[2] * -1;
+ break;
+ case RIGHT_TO_LEFT:
+ angle = delta * -90;
+ rotateY = -1.0f;
+ translateX = mTarget.getFrameVertex()[0] * -1;
+ break;
+
+ default:
+ break;
+ }
+
+ // Apply the projection and view transformation
+ Matrix.setIdentityM(matrix, 0);
+ Matrix.translateM(mTranslationMatrix, 0, matrix, 0, -translateX, 0.0f, 0.0f);
+ Matrix.rotateM(mTranslationMatrix, 0, mTranslationMatrix, 0, angle, 0.0f, rotateY, 0.0f);
+ Matrix.translateM(mTranslationMatrix, 0, mTranslationMatrix, 0, translateX, 0.0f, 0.0f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, mTranslationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Apply the destination transition (just draw the image)
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyDstTransition(float[] matrix) {
+ // Bind default FBO
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GLESUtil.glesCheckError("glBindFramebuffer");
+
+ // Use our shader program
+ useProgram(1);
+
+ // Disable blending
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Texture
+ FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(mTextureCoordHandlers[1], 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Position
+ FloatBuffer positionBuffer = mTransitionTarget.getPositionBuffer();
+ positionBuffer.position(0);
+ GLES20.glVertexAttribPointer(mPositionHandlers[1], 2, GLES20.GL_FLOAT, false, 0, positionBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Set the input texture
+ int textureHandle = mTransitionTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+ GLES20.glUniform1i(mTextureHandlers[1], 0);
+ GLESUtil.glesCheckError("glUniform1i");
+
+ // Draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Method that copy the vertex array
+ *
+ * @return The copy of the vertex
+ */
+ private float[] cloneVertex() {
+ float[] originalVertex = mTarget.getFrameVertex();
+ float[] vertex = new float[originalVertex.length];
+ System.arraycopy(originalVertex, 0, vertex, 0, originalVertex.length);
+ return vertex;
+ }
+
+ /**
+ * Return the scale amount to apply to the transition
+ *
+ * @return float The scale amount
+ */
+ private float getAmount() {
+ return ((mTarget.getFrameWidth() * SCALE_AMOUNT) / 2);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java b/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java
new file mode 100644
index 0000000..f7b32b5
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.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.utils;
+
+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;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+
+ // Calculate inSampleSize (use 1024 as maximum size, the minimum supported
+ // by all the gles20 devices)
+ options.inSampleSize = calculateBitmapRatio(
+ options,
+ Math.min(reqWidth, 1024),
+ Math.min(reqHeight, 1024));
+
+ // Decode the bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ options.inPreferQualityOverSpeed = false;
+ options.inPurgeable = true;
+ options.inInputShareable = true;
+ options.inDither = true;
+ Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ if (bitmap == null) {
+ return null;
+ }
+
+ // Test if the bitmap has exif format, and decode properly
+ Bitmap out = decodeExifBitmap(file, bitmap);
+ if (!out.equals(bitmap)) {
+ bitmap.recycle();
+ bitmap = null;
+ }
+ return out;
+ }
+
+ /**
+ * Method that decodes an Exif bitmap
+ *
+ * @param file The file to decode
+ * @param src The bitmap reference
+ * @return Bitmap The decoded bitmap
+ */
+ private static Bitmap decodeExifBitmap(File file, Bitmap src) {
+ try {
+ // Try to load the bitmap as a bitmap file
+ ExifInterface exif = new ExifInterface(file.getAbsolutePath());
+ int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
+ if (orientation == 0) {
+ return src;
+ }
+ 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(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
+ } catch (IOException e) {
+ // Ignore
+ }
+ return src;
+ }
+
+ /**
+ * 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/utils/DispositionUtil.java b/src/org/cyanogenmod/wallpapers/photophase/utils/DispositionUtil.java
new file mode 100644
index 0000000..ddd8d3a
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/DispositionUtil.java
@@ -0,0 +1,115 @@
+/*
+ * 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.utils;
+
+import android.graphics.Rect;
+
+import org.cyanogenmod.wallpapers.photophase.model.Disposition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class with disposition utils
+ */
+public final class DispositionUtil {
+
+ /**
+ * Method that converts a disposition string to a disposition reference
+ *
+ * @param value The value to convert
+ * @return List<Disposition> The dispositions reference
+ */
+ public static List<Disposition> toDispositions(String value) {
+ String[] v = value.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.x + 1;
+ disposition.h = Integer.parseInt(s3[1]) - disposition.y + 1;
+ dispositions.add(disposition);
+ }
+ Collections.sort(dispositions);
+ return dispositions;
+ }
+
+ /**
+ * Method that converts a disposition reference to a disposition string
+ *
+ * @param dispositions The value to convert
+ * @return String The dispositions string
+ */
+ public static String fromDispositions(List<Disposition> dispositions) {
+ Collections.sort(dispositions);
+ StringBuilder sb = new StringBuilder();
+ int count = dispositions.size();
+ for (int i = 0; i < count; i++) {
+ Disposition disposition = dispositions.get(i);
+ sb.append(disposition.x)
+ .append("x")
+ .append(disposition.y)
+ .append(":")
+ .append(disposition.x + disposition.w - 1)
+ .append("x")
+ .append(disposition.y + disposition.h - 1);
+ if (i < (count - 1)) {
+ sb.append("|");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Method that transform the disposition to a byte matrix
+ *
+ * @param dispositions The
+ * @return byte[][] The boolean matrix of the disposition
+ */
+ public static byte[][] toMatrix(List<Disposition> dispositions, int cols, int rows) {
+ byte[][] matrix = new byte[rows][cols];
+ for (Disposition disposition : dispositions) {
+ int count = disposition.y + disposition.h;
+ for (int row = disposition.y; row < count; row++) {
+ int count2 = disposition.x + disposition.w;
+ for (int col = disposition.x; col < count2; col++) {
+ matrix[row][col] = 1;
+ }
+ }
+ }
+ return matrix;
+ }
+
+ /**
+ * Method that returns a disposition from a {@link Rect} reference
+ *
+ * @return Disposition The disposition
+ */
+ public static Disposition fromRect(Rect r) {
+ Disposition disposition = new Disposition();
+ disposition.x = r.left;
+ disposition.y = r.top;
+ disposition.w = r.width();
+ disposition.h = r.height();
+ return disposition;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java b/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java
new file mode 100644
index 0000000..4707431
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java
@@ -0,0 +1,531 @@
+/*
+ * 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.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.effect.Effect;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+
+/**
+ * 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 = false;
+
+ private static final Object sSync = new Object();
+
+ /**
+ * 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 number
+ */
+ 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;
+ /**
+ * The path to the texture
+ */
+ public File path;
+ /**
+ * The effect to apply
+ */
+ public Effect effect;
+ }
+
+ /**
+ * 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) {
+ Log.e(TAG, "Cannot create a shader");
+ 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 or null if no effect is needed
+ * @param dimen The new dimensions
+ * @param recycle If the bitmap should be recycled
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(
+ File file, Rect dimensions, Effect effect, Rect dimen, 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) {
+ Log.e(TAG, "Failed to decode the file bitmap");
+ return new GLESTextureInfo();
+ }
+
+ if (DEBUG) Log.d(TAG, "image: " + file.getAbsolutePath());
+ GLESTextureInfo ti = loadTexture(bitmap, effect, dimen);
+ ti.path = file;
+ return ti;
+
+ } catch (Exception e) {
+ String msg = "Failed to generate a valid texture from file: " + file.getAbsolutePath();
+ Log.e(TAG, msg, e);
+ return new GLESTextureInfo();
+
+ } finally {
+ // Recycle the bitmap
+ if (bitmap != null && recycle) {
+ bitmap.recycle();
+ bitmap = null;
+ }
+ }
+ }
+
+ /**
+ * 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 or null if no effect is needed
+ * @param dimen The new dimensions
+ * @param recycle If the bitmap should be recycled
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(
+ Context ctx, int resourceId, Effect effect, Rect dimen, 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";
+ Log.e(TAG, msg);
+ return new GLESTextureInfo();
+ }
+
+ if (DEBUG) Log.d(TAG, "resourceId: " + resourceId);
+ GLESTextureInfo ti = loadTexture(bitmap, effect, dimen);
+ return ti;
+
+ } catch (Exception e) {
+ String msg = "Failed to generate a valid texture from resource: " + resourceId;
+ 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();
+ bitmap = null;
+ }
+ }
+ }
+
+ /**
+ * Method that loads texture from a bitmap reference.
+ *
+ * @param bitmap The bitmap reference
+ * @param effect The effect to apply to the image or null if no effect is needed
+ * @param dimen The new dimensions
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(Bitmap bitmap, Effect effect, Rect dimen) {
+ // Check that we have a valid image name reference
+ if (bitmap == null) {
+ return new GLESTextureInfo();
+ }
+
+ int num = effect == null ? 1 : 2;
+
+ int[] textureHandles = new int[num];
+ GLES20.glGenTextures(num, textureHandles, 0);
+ GLESUtil.glesCheckError("glGenTextures");
+ if (textureHandles[0] <= 0 || (effect != null && textureHandles[1] <= 0)) {
+ Log.e(TAG, "Failed to generate a valid texture");
+ return new GLESTextureInfo();
+ }
+
+ // Bind the texture to the name
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandles[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(textureHandles[0])) {
+ Log.e(TAG, "Failed to load a valid texture");
+ return new GLESTextureInfo();
+ }
+
+ // Has a effect?
+ int handle = textureHandles[0];
+ if (effect != null) {
+ // Apply the effect (we need a thread-safe call here)
+ synchronized (sSync) {
+ // No more than 1024 (the minimum supported by all the gles20 devices)
+ int w = Math.min(dimen.width(), 1024);
+ int h = Math.min(dimen.width(), 1024);
+ effect.apply(textureHandles[0], w, h, textureHandles[1]);
+ }
+ handle = textureHandles[1];
+
+ // Delete the unused texture
+ int[] textures = {textureHandles[0]};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+
+ // Return the texture handle identifier and the associated info
+ GLESTextureInfo ti = new GLESTextureInfo();
+ ti.handle = handle;
+ ti.bitmap = bitmap;
+ ti.path = null;
+ 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) {
+ 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) {
+ Reader reader = new InputStreamReader(res.openRawResource(resId));
+ try {
+ final int BUFFER = 1024;
+ char[] data = new char[BUFFER];
+ int read = 0;
+ StringBuilder sb = new StringBuilder();
+ while ((read = reader.read(data, 0, BUFFER)) != -1) {
+ sb.append(data, 0, read);
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to read the resource " + resId);
+ return null;
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/MERAlgorithm.java b/src/org/cyanogenmod/wallpapers/photophase/utils/MERAlgorithm.java
new file mode 100644
index 0000000..53d44f3
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/MERAlgorithm.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.utils;
+
+import android.graphics.Rect;
+import java.util.Stack;
+
+/**
+ * The maximal empty rectangle algorithm that allows to find the rectangle with the maximal
+ * area that could be create in empty areas (in this case 0 in a byte matrix)
+ */
+//
+// Based on the source discussed at http://discuss.leetcode.com/questions/260/maximal-rectangle
+//
+public final class MERAlgorithm {
+
+ /**
+ * Method that returns the maximal empty rectangle (MER) for a matrix of bytes (1/0)
+ *
+ * @param matrix The matrix
+ * @return Rect The maximal empty rectangle
+ */
+ public static Rect getMaximalEmptyRectangle(byte[][] matrix) {
+ // Check matrix
+ int rows = matrix.length;
+ if (rows == 0) return null;
+ int cols = matrix[0].length;
+
+ // Convert to histogram
+ int[][] histogram = toHistogram(matrix);
+
+ // Find the maximal area of every histogram
+ Rect maxRect = new Rect();
+ for (int i = 0; i < rows; ++i) {
+ Rect rect = maximalRectangle(histogram[i], i);
+ if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) {
+ maxRect = rect;
+ }
+ }
+ return ensureBounds(maxRect, cols, rows);
+ }
+
+ /**
+ * Method that ensure the bounds of the max rectangle
+ *
+ * @param rect The rectangle to check
+ * @param cols The number of cols
+ * @param rows The number of rows
+ * @return Rect The rectangle checked
+ */
+ private static Rect ensureBounds(Rect rect, int cols, int rows) {
+ if (rect.right - rect.left >= cols) rect.right = cols;
+ if (rect.bottom - rect.top >= rows) rect.bottom = rows;
+ return rect;
+ }
+
+ /**
+ * Method that returns the maximal rectangle for an histogram of areas
+ *
+ * @return Rect The maximal rectangle histogram/area
+ */
+ @SuppressWarnings("boxing")
+ private static Rect maximalRectangle(int[] histogram, int row) {
+ Stack<Integer> stack = new Stack<Integer>();
+ int length = histogram.length;
+ Rect maxRect = new Rect();
+ int i = 0;
+ while (i < length) {
+ if (stack.isEmpty() || histogram[i] >= histogram[stack.peek()]) {
+ stack.push(i++);
+ } else {
+ Rect rect = new Rect();
+ rect.left = stack.pop();
+ rect.right = rect.left + (stack.isEmpty() ? i : (i - stack.peek() - 1));
+ rect.top = row - histogram[rect.left] + 1;
+ rect.bottom = rect.top + histogram[rect.left];
+ if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) {
+ maxRect = rect;
+ }
+ }
+ }
+ while (!stack.isEmpty()) {
+ Rect rect = new Rect();
+ rect.left = stack.pop();
+ rect.right = rect.left + (stack.isEmpty() ? i : (i - stack.peek() - 1));
+ rect.top = row - histogram[rect.left] + 1;
+ rect.bottom = rect.top + histogram[rect.left];
+ if ((maxRect.width() * maxRect.height()) < (rect.width() * rect.height())) {
+ maxRect = rect;
+ }
+ }
+ return maxRect;
+ }
+
+ /**
+ * Method that converts the empty areas to a histogram
+ *
+ * @param matrix The matrix where to find the MER
+ * return int[][] The histogram of empty areas
+ */
+ private static int[][] toHistogram(byte[][] matrix) {
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ int[][] histogram = new int[rows][cols];
+ for (int h=0; h < cols; h++) {
+ if (matrix[0][h] == 0) {
+ histogram[0][h] = 1;
+ }
+ }
+ for (int w=1; w < rows; w++) {
+ for (int h=0; h < cols; h++) {
+ if (matrix[w][h] == 1) {
+ continue;
+ }
+ histogram[w][h] = histogram[w-1][h] + 1;
+ }
+ }
+ return histogram;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/Utils.java b/src/org/cyanogenmod/wallpapers/photophase/utils/Utils.java
new file mode 100644
index 0000000..65d7f97
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/Utils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.utils;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.RectF;
+import android.util.DisplayMetrics;
+
+import java.util.Random;
+
+/**
+ * A helper class with utilities
+ */
+public class Utils {
+
+ private static Random sRandom = new Random();
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Used to determine if the device is a tablet or not
+ *
+ * @param context The {@link Context} to use.
+ * @return True if the device is a tablet, false otherwise.
+ */
+ public static final boolean isTablet(final Context context) {
+ final int layout = context.getResources().getConfiguration().screenLayout;
+ return (layout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ }
+
+ /**
+ * Method that converts a rect from a vertex data
+ *
+ * @param vertex The vertex array
+ * @return RectF The rect data
+ */
+ public static RectF rectFromVertex(float[] vertex) {
+ RectF rect = new RectF();
+ rect.left = vertex[0];
+ rect.top = vertex[7];
+ rect.right = vertex[6];
+ rect.bottom = vertex[1];
+ return rect;
+ }
+
+ /**
+ * Method that returns a random number between two numbers.
+ *
+ * @param low The low number
+ * @param high The high number
+ * @return int The random number
+ */
+ public static int getNextRandom(int low, int high) {
+ return low + (int)(sRandom.nextDouble() * ((high - low) + 1));
+ }
+} \ No newline at end of file
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..8e335c3
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java
@@ -0,0 +1,284 @@
+/*
+ * 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);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // Cancel pending tasks
+ if (mTask != null && 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 (mAlbum != null && mIcon != null) {
+ Resources res = getContext().getResources();
+
+ int selectedItems = mAlbum.getSelectedItems().size();
+ String count = String.valueOf(selectedItems);
+ if (selectedItems > 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());
+
+ if (mTask == null) {
+ 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)));
+ }
+ });
+ }
+ }
+ }
+}
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..5d0c60f
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java
@@ -0,0 +1,345 @@
+/*
+ * 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.os.Handler;
+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 Handler mHandler;
+
+ /*package*/ PicturesView mScroller;
+ /*package*/ LinearLayout mHolder;
+ private View mBackButton;
+ private View mOverflowButton;
+
+ private boolean mInitialized;
+
+ /*package*/ 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>();
+ mHandler = new Handler();
+ mInitialized = false;
+ }
+
+ /**
+ * {@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, false);
+ }
+
+ /**
+ * 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
+ * @param recreate If the view should be recreated
+ */
+ public void updateView(Album album, boolean recreate) {
+ mAlbum = album;
+ recreateView(false);
+ }
+
+ /**
+ * Method that recreates the the view
+ *
+ * @param propagateShow If should propagate the show event
+ */
+ private void recreateView(final boolean propagateShow) {
+ if (mHolder != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ int pictures = mHolder.getChildCount();
+ if (pictures != mAlbum.getItems().size()) {
+ // Recreate 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);
+ }
+ } else {
+ int i = 0;
+ for (final String picture : mAlbum.getItems()) {
+ View v = mHolder.getChildAt(i);
+ v.setSelected(isPictureSelected(picture));
+ i++;
+ }
+ }
+ if (propagateShow) {
+ mScroller.onShow();
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * {@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() {
+ if (!mInitialized) {
+ mInitialized = true;
+ recreateView(true);
+ }
+ }
+
+ /**
+ * 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
+ */
+ /*package*/ 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
+ */
+ /*package*/ 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..e1953cd
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java
@@ -0,0 +1,91 @@
+/*
+ * 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);
+ }
+
+ /**
+ * Add a new card to the layout
+ *
+ * @param card The card view to add
+ * @param animate If the add should be animated
+ */
+ public void addCard(final View card, final boolean animate) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ addView(card);
+ if (animate) {
+ if (inverted) {
+ card.startAnimation(AnimationUtils.loadAnimation(
+ getContext(), R.anim.cards_animation_up_right));
+ } else {
+ card.startAnimation(AnimationUtils.loadAnimation(
+ getContext(), R.anim.cards_animation_up_left));
+ }
+ inverted = !inverted;
+ }
+ }
+ });
+ }
+}
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/DispositionView.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java
new file mode 100644
index 0000000..22d00a6
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java
@@ -0,0 +1,768 @@
+/*
+ * 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+import android.widget.ImageView.ScaleType;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.animations.Evaluators;
+import org.cyanogenmod.wallpapers.photophase.model.Disposition;
+import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil;
+import org.cyanogenmod.wallpapers.photophase.utils.MERAlgorithm;
+import org.cyanogenmod.wallpapers.photophase.widgets.ResizeFrame.OnResizeListener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class that allow to select the frames disposition visually
+ */
+public class DispositionView extends RelativeLayout implements OnLongClickListener, OnResizeListener {
+
+ /**
+ * An interface to communicate the selection/unselection of a frame
+ */
+ public interface OnFrameSelectedListener {
+ /**
+ * Invoked when a frame is selected
+ *
+ * @param v The frame view selected
+ */
+ void onFrameSelectedListener(View v);
+ /**
+ * Invoked when a frame is unselected
+ */
+ void onFrameUnselectedListener();
+ }
+
+ private boolean mChanged;
+ private List<Disposition> mDispositions;
+ private int mCols;
+ private int mRows;
+
+ /*package*/ View mTarget;
+ private ResizeFrame mResizeFrame;
+ private int mInternalPadding;
+ private Rect mOldResizeFrameLocation;
+
+ private OnFrameSelectedListener mOnFrameSelectedListener;
+
+ private Vibrator mVibrator;
+
+ /**
+ * Constructor of <code>DispositionView</code>.
+ *
+ * @param context The current context
+ */
+ public DispositionView(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Constructor of <code>DispositionView</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public DispositionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>DispositionView</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 DispositionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Initialize the view
+ */
+ private void init() {
+ mVibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
+ mInternalPadding = (int)getResources().getDimension(R.dimen.disposition_frame_padding);
+ }
+
+ /**
+ * Method that returns the dispositions drawn on this view
+ *
+ * @return List<Disposition> The dispositions drawn
+ */
+ public List<Disposition> getDispositions() {
+ return mDispositions;
+ }
+
+ /**
+ * Method that sets the disposition to draw on this view
+ *
+ * @param dispositions The dispositions to draw
+ * @param cols The number of cols
+ * @param rows The number of rows
+ */
+ public void setDispositions(
+ List<Disposition> dispositions, int cols, int rows) {
+ mDispositions = dispositions;
+ mCols = cols;
+ mRows = rows;
+
+ // Remove all the current views and add the new ones
+ recreateDispositions(true);
+ mResizeFrame.setVisibility(View.GONE);
+ mChanged = false;
+ }
+
+ /**
+ * Method that sets the resize frame view
+ *
+ * @param resizeFrame The resize frame view
+ */
+ public void setResizeFrame(ResizeFrame resizeFrame) {
+ mResizeFrame = resizeFrame;
+ mResizeFrame.setOnResizeListener(this);
+ }
+
+ /**
+ * Method that set the listener for listen frame selection/unselection events
+ *
+ * @param onFrameSelectedListener The callback
+ */
+ public void setOnFrameSelectedListener(OnFrameSelectedListener onFrameSelectedListener) {
+ this.mOnFrameSelectedListener = onFrameSelectedListener;
+ }
+
+ /**
+ * Method that returns if the view was changed
+ *
+ * @return boolean true if the view was changed
+ */
+ public boolean isChanged() {
+ return mChanged;
+ }
+
+ /**
+ * Method that recreates all the dispositions
+ *
+ * @param animate If the recreate should be done with an animation
+ */
+ private void recreateDispositions(boolean animate) {
+ // Remove all the current views and add the new ones
+ removeAllViews();
+ for (Disposition disposition : mDispositions) {
+ createFrame(getLocationFromDisposition(disposition), animate);
+ }
+ mOldResizeFrameLocation = null;
+ mTarget = null;
+ if (mOnFrameSelectedListener != null) {
+ mOnFrameSelectedListener.onFrameUnselectedListener();
+ }
+ }
+
+ /**
+ * Method that request the deletion of the current selected frame
+ */
+ @SuppressWarnings("boxing")
+ public void deleteCurrentFrame() {
+ if (mTarget == null) return;
+
+ final Disposition targetDisposition = resizerToDisposition();
+
+ // Get valid dispositions to move
+ final List<Disposition> adjacents = findAdjacentsDispositions(targetDisposition);
+ if (adjacents == null) {
+ // Nothing to do
+ Toast.makeText(getContext(),
+ R.string.pref_disposition_unable_delete_advise, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Hide resizer
+ mResizeFrame.setVisibility(View.GONE);
+
+ // Animate adjacents views
+ List<Animator> animators = new ArrayList<Animator>();
+ animators.add(ObjectAnimator.ofFloat(mTarget, "scaleX", 1.0f, 0.0f));
+ animators.add(ObjectAnimator.ofFloat(mTarget, "scaleY", 1.0f, 0.0f));
+
+ Disposition first = null;
+ for (Disposition adjacent : adjacents) {
+ // Extract the view and remove from dispositions
+ View v = findViewFromRect(getLocationFromDisposition(adjacent));
+ mDispositions.remove(adjacent);
+
+ // Clone first disposition
+ if (first == null) {
+ first = new Disposition();
+ first.x = adjacent.x;
+ first.y = adjacent.y;
+ first.w = adjacent.w;
+ first.h = adjacent.h;
+ }
+
+ // Add animators and fix the adjacent
+ if (v != null) {
+ if (first.x < targetDisposition.x) {
+ // From Left to Right
+ int width = mTarget.getWidth() + mInternalPadding;
+ animators.add(ValueAnimator.ofObject(
+ new Evaluators.WidthEvaluator(v), v.getWidth(), v.getWidth() + width));
+
+ // Update the adjacent
+ adjacent.w += targetDisposition.w;
+ mDispositions.add(adjacent);
+
+ } else if (first.x > targetDisposition.x) {
+ // From Right to Left
+ int width = mTarget.getWidth() + mInternalPadding;
+ animators.add(ValueAnimator.ofObject(
+ new Evaluators.WidthEvaluator(v), v.getWidth(), v.getWidth() + width));
+ animators.add(ObjectAnimator.ofFloat(v, "x", v.getX(), mTarget.getX()));
+
+ // Update the adjacent
+ adjacent.x = targetDisposition.x;
+ adjacent.w += targetDisposition.w;
+ mDispositions.add(adjacent);
+
+ } else if (first.y < targetDisposition.y) {
+ // From Top to Bottom
+ int height = mTarget.getHeight() + mInternalPadding;
+ animators.add(ValueAnimator.ofObject(
+ new Evaluators.HeightEvaluator(v), v.getHeight(), v.getHeight() + height));
+
+ // Update the adjacent
+ adjacent.h += targetDisposition.h;
+ mDispositions.add(adjacent);
+
+ } else if (first.y > targetDisposition.y) {
+ // From Bottom to Top
+ int height = mTarget.getHeight() + mInternalPadding;
+ animators.add(ValueAnimator.ofObject(
+ new Evaluators.HeightEvaluator(v), v.getHeight(), v.getHeight() + height));
+ animators.add(ObjectAnimator.ofFloat(v, "y", v.getY(), mTarget.getY()));
+
+ // Update the adjacent
+ adjacent.y = targetDisposition.y;
+ adjacent.h += targetDisposition.h;
+ mDispositions.add(adjacent);
+ }
+ }
+ }
+ if (animators.size() > 0) {
+ AnimatorSet animSet = new AnimatorSet();
+ animSet.playTogether(animators);
+ animSet.setDuration(getResources().getInteger(R.integer.disposition_hide_anim));
+ animSet.setInterpolator(new AccelerateInterpolator());
+ animSet.addListener(new AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Ignore
+ }
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // Ignore
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishDeleteAnimation(targetDisposition);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finishDeleteAnimation(targetDisposition);
+ }
+ });
+ animSet.start();
+ }
+ }
+
+ /**
+ * Method that finalizes the delete animation
+ *
+ * @param target The disposition target
+ */
+ /*package*/ void finishDeleteAnimation(Disposition target) {
+ removeView(mTarget);
+ mDispositions.remove(target);
+ Collections.sort(mDispositions);
+ mChanged = true;
+
+ // Clean status
+ mOldResizeFrameLocation = null;
+ mTarget = null;
+ if (mOnFrameSelectedListener != null) {
+ mOnFrameSelectedListener.onFrameUnselectedListener();
+ }
+ }
+
+ /**
+ * Method that create a new frame to be drawn in the specified location
+ *
+ * @param r The location relative to the parent layout
+ * @return v The new view
+ */
+ private View createFrame(Rect r, boolean animate) {
+ int padding = (int)getResources().getDimension(R.dimen.disposition_frame_padding);
+ final ImageView v = new ImageView(getContext());
+ v.setImageResource(R.drawable.ic_camera);
+ v.setScaleType(ScaleType.CENTER);
+ v.setBackgroundColor(getResources().getColor(R.color.disposition_frame_bg_color));
+ RelativeLayout.LayoutParams params =
+ new RelativeLayout.LayoutParams(r.width() - padding, r.height() - padding);
+ v.setX(r.left + padding);
+ v.setY(r.top + padding);
+ v.setOnLongClickListener(this);
+ if (animate) {
+ v.setVisibility(View.INVISIBLE);
+ }
+ addView(v, params);
+
+ // Animate the view
+ if (animate) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ Animation anim = AnimationUtils.loadAnimation(
+ getContext(), R.anim.display_with_bounce);
+ anim.setFillBefore(true);
+ anim.setFillAfter(true);
+ v.startAnimation(anim);
+ }
+ });
+ }
+
+ return v;
+ }
+
+ /**
+ * Method that returns the location of the frame from its disposition
+ *
+ * @param disposition The source disposition
+ * @return Rect The location on parent view
+ */
+ private Rect getLocationFromDisposition(Disposition disposition) {
+ int w = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
+ int h = getMeasuredHeight() - (getPaddingTop() + getPaddingBottom());
+ int cw = w / mCols;
+ int ch = h / mRows;
+
+ Rect location = new Rect();
+ location.left = disposition.x * cw;
+ location.top = disposition.y * ch;
+ location.right = location.left + disposition.w * cw;
+ location.bottom = location.top + disposition.h * ch;
+ return location;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onLongClick(View v) {
+ if (!selectTarget(v)) return false;
+ mVibrator.vibrate(300);
+ return true;
+ }
+
+ @Override
+ public void onStartResize(int mode) {
+ mOldResizeFrameLocation = new Rect(
+ mResizeFrame.getLeft(),
+ mResizeFrame.getTop(),
+ mResizeFrame.getRight(),
+ mResizeFrame.getBottom());
+ }
+
+ @Override
+ public void onResize(int mode, int delta) {
+ if (mTarget == null) return;
+
+ int w = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
+ int h = getMeasuredHeight() - (getPaddingTop() + getPaddingBottom());
+ int minWidth = (w / mCols) + (w / mCols) / 2;
+ int minHeight = (h / mRows) + (h / mRows) / 2;
+
+ FrameLayout.LayoutParams params =
+ (FrameLayout.LayoutParams)mResizeFrame.getLayoutParams();
+ switch (mode) {
+ case Gravity.LEFT:
+ float newpos = mResizeFrame.getX() + delta;
+ if ((delta < 0 && newpos < (getPaddingLeft() * -1)) ||
+ (delta > 0 && newpos > (mResizeFrame.getX() + params.width - minWidth))) {
+ return;
+ }
+ mResizeFrame.setX(newpos);
+ params.width -= delta;
+ break;
+ case Gravity.RIGHT:
+ if ((delta < 0 && ((params.width + delta) < minWidth)) ||
+ (delta > 0 && (mResizeFrame.getX() + delta + params.width) > (getPaddingLeft() + getMeasuredWidth()))) {
+ return;
+ }
+ params.width += delta;
+ break;
+ case Gravity.TOP:
+ newpos = mResizeFrame.getY() + delta;
+ if ((delta < 0 && newpos < (getPaddingTop() * -1)) ||
+ (delta > 0 && newpos > (mResizeFrame.getY() + params.height - minHeight))) {
+ return;
+ }
+ mResizeFrame.setY(newpos);
+ params.height -= delta;
+ break;
+ case Gravity.BOTTOM:
+ if ((delta < 0 && ((params.height + delta) < minHeight)) ||
+ (delta > 0 && (mResizeFrame.getY() + delta + params.height) > (getPaddingTop() + getMeasuredHeight()))) {
+ return;
+ }
+ params.height += delta;
+ break;
+
+ default:
+ break;
+ }
+ mResizeFrame.setLayoutParams(params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onEndResize(final int mode) {
+ if (mTarget == null) return;
+
+ // Compute the removed dispositions
+ computeRemovedDispositions(mode);
+ recreateDispositions(false);
+ computeNewDispositions(mode);
+
+ // Finish resize (select the target and create the new dispositions)
+ post(new Runnable() {
+ @Override
+ public void run() {
+ // Select the target
+ View v = findTargetFromResizeFrame();
+ if (v != null) {
+ selectTarget(v);
+ }
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCancel() {
+ if (mOldResizeFrameLocation != null) {
+ mTarget.setLeft(mOldResizeFrameLocation.left);
+ mTarget.setRight(mOldResizeFrameLocation.right);
+ mTarget.setTop(mOldResizeFrameLocation.top);
+ mTarget.setBottom(mOldResizeFrameLocation.bottom);
+ }
+ mOldResizeFrameLocation = null;
+ mTarget = null;
+ if (mOnFrameSelectedListener != null) {
+ mOnFrameSelectedListener.onFrameUnselectedListener();
+ }
+ }
+
+ /**
+ * Method that returns the target view for the current resize frame
+ *
+ * @return The target view
+ */
+ /*package*/ View findTargetFromResizeFrame() {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = getChildAt(i);
+ if (v.getX() < (mResizeFrame.getX() + (mResizeFrame.getWidth() / 2)) &&
+ (v.getX() + v.getWidth()) > (mResizeFrame.getX() + (mResizeFrame.getWidth() / 2)) &&
+ v.getY() < (mResizeFrame.getY() + (mResizeFrame.getHeight() / 2)) &&
+ (v.getY() + v.getHeight()) > (mResizeFrame.getY() + (mResizeFrame.getHeight() / 2))) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method that returns the view under the rect
+ *
+ * @return The view
+ */
+ private View findViewFromRect(Rect r) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = getChildAt(i);
+ if (v.getX() < (r.left + (r.width() / 2)) &&
+ (v.getX() + v.getWidth()) > (r.left + (r.width() / 2)) &&
+ v.getY() < (r.top + (r.height() / 2)) &&
+ (v.getY() + v.getHeight()) > (r.top + (r.height() / 2))) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method that select a view as the target of to resize
+ *
+ * @param v The target view
+ */
+ /*package*/ boolean selectTarget(View v) {
+ //Do not do long click if we do not have a target
+ if (mTarget != null && v.equals(mTarget)) return false;
+
+ // Show the resize frame view just in place of the current clicked view
+ mResizeFrame.hide();
+ FrameLayout.LayoutParams frameParams =
+ (FrameLayout.LayoutParams)mResizeFrame.getLayoutParams();
+ int padding = mInternalPadding + mResizeFrame.getNeededPadding();
+ frameParams.width = v.getWidth() + (padding * 2);
+ frameParams.height = v.getHeight() + (padding * 2);
+ mResizeFrame.setX(v.getX() - padding);
+ mResizeFrame.setY(v.getY() - padding);
+ mResizeFrame.show();
+
+ // Save the new view
+ mTarget = v;
+ if (mOnFrameSelectedListener != null) {
+ mOnFrameSelectedListener.onFrameSelectedListener(v);
+ }
+ return true;
+ }
+
+ /**
+ * Computes the removed layout disposition based on the actual resize frame
+ *
+ * @param mode The resize mode
+ */
+ private void computeRemovedDispositions(int mode) {
+ // Transform the resizer to a dispositions object
+ Disposition resizer = resizerToDisposition();
+
+ // Delete all overlapped
+ int count = mDispositions.size();
+ for (int i = count - 1; i >= 0; i--) {
+ Disposition disposition = mDispositions.get(i);
+ if (!isVisible(disposition) || isOverlapped(resizer, disposition)) {
+ mDispositions.remove(disposition);
+ }
+ }
+
+ // Add the new disposition
+ mDispositions.add(resizer);
+ Collections.sort(mDispositions);
+
+ mChanged = true;
+ }
+
+ /**
+ * Computes the new layout disposition based on the actual resize frame
+ *
+ * @param mode The resize mode
+ */
+ private void computeNewDispositions(int mode) {
+ // Fill the empty areas
+ do {
+ byte[][] dispositionMatrix = DispositionUtil.toMatrix(mDispositions, mCols, mRows);
+ Rect rect = MERAlgorithm.getMaximalEmptyRectangle(dispositionMatrix);
+ if (rect.width() == 0 && rect.height() == 0) {
+ // No more empty areas
+ break;
+ }
+ Disposition disposition = DispositionUtil.fromRect(rect);
+ createFrame(getLocationFromDisposition(disposition), true);
+ mDispositions.add(disposition);
+ } while (true);
+
+ // Now the view was changed and should be reported
+ Collections.sort(mDispositions);
+ mChanged = true;
+ }
+
+ /**
+ * Method that converts the resize frame to a dispostion reference
+ *
+ * @return Disposition The disposition reference
+ */
+ private Disposition resizerToDisposition() {
+ int w = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
+ int h = getMeasuredHeight() - (getPaddingTop() + getPaddingBottom());
+ int cw = w / mCols;
+ int ch = h / mRows;
+
+ //Remove overlapped areas
+ Disposition resizer = new Disposition();
+ resizer.x = Math.round(mResizeFrame.getX() / cw);
+ resizer.y = Math.round(mResizeFrame.getY() / ch);
+ resizer.w = Math.round(mResizeFrame.getWidth() / cw);
+ resizer.h = Math.round(mResizeFrame.getHeight() / ch);
+
+ // Fix disposition (limits)
+ resizer.x = Math.max(resizer.x, 0);
+ resizer.y = Math.max(resizer.y, 0);
+ resizer.w = Math.min(resizer.w, mCols - resizer.x);
+ resizer.h = Math.min(resizer.h, mRows - resizer.y);
+
+ return resizer;
+ }
+
+ /**
+ * Method that returns all dispositions that matched exactly (in one side) with
+ * the argument disposition.
+ *
+ * @param disposition The disposition to check
+ */
+ private List<Disposition> findAdjacentsDispositions(Disposition disposition) {
+ if (mDispositions.size() <= 1) return null;
+
+ // Check left size
+ if (disposition.x != 0) {
+ List<Disposition> dispositions = new ArrayList<Disposition>();
+ for (Disposition d : mDispositions) {
+ if (d.compareTo(disposition) != 0) {
+ if ((d.x + d.w) == disposition.x &&
+ (d.y >= disposition.y) && ((d.y + d.h) <= (disposition.y + disposition.h))) {
+ dispositions.add(d);
+ }
+ }
+ }
+ // Check if the sum of all the dispositions matches the disposition
+ int sum = 0;
+ for (Disposition d : dispositions) {
+ sum += d.h;
+ }
+ if (sum == disposition.h) {
+ return dispositions;
+ }
+ }
+ // Check top size
+ if (disposition.y != 0) {
+ List<Disposition> dispositions = new ArrayList<Disposition>();
+ for (Disposition d : mDispositions) {
+ if (d.compareTo(disposition) != 0) {
+ if ((d.y + d.h) == disposition.y &&
+ (d.x >= disposition.x) && ((d.x + d.w) <= (disposition.x + disposition.w))) {
+ dispositions.add(d);
+ }
+ }
+ }
+ // Check if the sum of all the dispositions matches the disposition
+ int sum = 0;
+ for (Disposition d : dispositions) {
+ sum += d.w;
+ }
+ if (sum == disposition.w) {
+ return dispositions;
+ }
+ }
+ // Check right size
+ if ((disposition.x + disposition.w) != mCols) {
+ List<Disposition> dispositions = new ArrayList<Disposition>();
+ for (Disposition d : mDispositions) {
+ if (d.compareTo(disposition) != 0) {
+ if ((d.x) == (disposition.x + disposition.w) &&
+ (d.y >= disposition.y) && ((d.y + d.h) <= (disposition.y + disposition.h))) {
+ dispositions.add(d);
+ }
+ }
+ }
+ // Check if the sum of all the dispositions matches the disposition
+ int sum = 0;
+ for (Disposition d : dispositions) {
+ sum += d.h;
+ }
+ if (sum == disposition.h) {
+ return dispositions;
+ }
+ }
+ // Check bottom size
+ if ((disposition.y + disposition.h) != mRows) {
+ List<Disposition> dispositions = new ArrayList<Disposition>();
+ for (Disposition d : mDispositions) {
+ if (d.compareTo(disposition) != 0) {
+ if ((d.y) == (disposition.y + disposition.h) &&
+ (d.x >= disposition.x) && ((d.x + d.w) <= (disposition.x + disposition.w))) {
+ dispositions.add(d);
+ }
+ }
+ }
+ // Check if the sum of all the dispositions matches the disposition
+ int sum = 0;
+ for (Disposition d : dispositions) {
+ sum += d.w;
+ }
+ if (sum == disposition.w) {
+ return dispositions;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method that checks if a dispositions overlaps another other disposition
+ *
+ * @param d1 One disposition
+ * @param d2 Another disposition
+ * @return boolean true if d1 overlaps d2
+ */
+ private static boolean isOverlapped(Disposition d1, Disposition d2) {
+ Rect r1 = new Rect(d1.x, d1.y, d1.x + d1.w, d1.y + d1.h);
+ Rect r2 = new Rect(d2.x, d2.y, d2.x + d2.w, d2.y + d2.h);
+ return r1.intersect(r2);
+ }
+
+ /**
+ * Method that checks if a dispositions is visible
+ *
+ * @param d The disposition to check
+ * @return boolean true if d is visible
+ */
+ private static boolean isVisible(Disposition d) {
+ return d.w > 0 && d.h > 0;
+ }
+}
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..0744751
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java
@@ -0,0 +1,164 @@
+/*
+ * 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.os.Handler;
+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;
+ private Handler mHandler;
+
+ /**
+ * 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>();
+ mHandler = new Handler();
+ }
+
+ /**
+ * {@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);
+ // Estimated velocity (in some moment we must obtain some scrolling with an estimated
+ // velocity below of 3)
+ int velocity = Math.abs(l - oldl);
+ if (velocity <= 3) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ requestLoadOfPendingPictures();
+ }
+ });
+ }
+ }
+
+ /**
+ * Method invoked when the view is displayed
+ */
+ public void onShow() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ requestLoadOfPendingPictures();
+ }
+ });
+ }
+
+ /**
+ * Method that load in background all visible and pending pictures
+ */
+ /*package*/ 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);
+ }
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/ResizeFrame.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/ResizeFrame.java
new file mode 100644
index 0000000..218a4e4
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/ResizeFrame.java
@@ -0,0 +1,301 @@
+/*
+ * 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.BitmapFactory;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * The hold view to resize a frame. A square with 4 handles in every border
+ * to drag and resize a view
+ */
+public class ResizeFrame extends RelativeLayout {
+
+ /**
+ * An interface to communicate resize event states
+ */
+ public interface OnResizeListener {
+ /**
+ * Called when the resize is going to start
+ *
+ * @param mode The resize mode (left, right, top, bottom)
+ * @see Gravity
+ */
+ void onStartResize(int mode);
+ /**
+ * Called when the resize is going to start
+ *
+ * @param mode The resize mode (left, right, top, bottom)
+ * @param delta The delta motion
+ * @see Gravity
+ */
+ void onResize(int mode, int delta);
+ /**
+ * Called when the resize was ended
+ *
+ * @param mode The resize mode (left, right, top, bottom)
+ * @see Gravity
+ */
+ void onEndResize(int mode);
+ /**
+ * Called when the resize was cancelled
+ *
+ * @param mode The resize mode (left, right, top, bottom)
+ * @see Gravity
+ */
+ void onCancel();
+ }
+
+ private int mNeededPadding;
+
+ private ImageView mLeftHandle;
+ private ImageView mRightHandle;
+ private ImageView mTopHandle;
+ private ImageView mBottomHandle;
+
+ private float mExtraHandlingSpace;
+
+ private View mHandle;
+
+ private float mLastTouchX;
+ private float mLastTouchY;
+
+ private OnResizeListener mOnResizeListener;
+
+ /**
+ * Constructor of <code>ResizeFrame</code>.
+ *
+ * @param context The current context
+ */
+ public ResizeFrame(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Constructor of <code>ResizeFrame</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public ResizeFrame(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>ResizeFrame</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 ResizeFrame(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Method that initializes the view
+ */
+ @SuppressWarnings("boxing")
+ private void init() {
+ setBackgroundResource(R.drawable.resize_frame);
+ setPadding(0, 0, 0, 0);
+
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ o.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+ BitmapFactory.decodeResource(getContext().getResources(), R.drawable.resize_handle_left, o);
+ mNeededPadding = (int)(o.outWidth / 1.5f);
+
+ LayoutParams lp;
+ mLeftHandle = new ImageView(getContext());
+ mLeftHandle.setImageResource(R.drawable.resize_handle_left);
+ mLeftHandle.setTag(Gravity.LEFT);
+ lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
+ lp.addRule(RelativeLayout.CENTER_VERTICAL);
+ addView(mLeftHandle, lp);
+
+ mRightHandle = new ImageView(getContext());
+ mRightHandle.setImageResource(R.drawable.resize_handle_right);
+ mRightHandle.setTag(Gravity.RIGHT);
+ lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
+ lp.addRule(RelativeLayout.CENTER_VERTICAL);
+ addView(mRightHandle, lp);
+
+ mTopHandle = new ImageView(getContext());
+ mTopHandle.setImageResource(R.drawable.resize_handle_top);
+ mTopHandle.setTag(Gravity.TOP);
+ lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ addView(mTopHandle, lp);
+
+ mBottomHandle = new ImageView(getContext());
+ mBottomHandle.setImageResource(R.drawable.resize_handle_bottom);
+ mBottomHandle.setTag(Gravity.BOTTOM);
+ lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ addView(mBottomHandle, lp);
+
+ mExtraHandlingSpace = getResources().getDimension(R.dimen.resize_frame_extra_handling_space);
+ }
+
+ /**
+ * Method that set the callback for resize events
+ *
+ * @param onResizeListener The callback
+ */
+ public void setOnResizeListener(OnResizeListener onResizeListener) {
+ mOnResizeListener = onResizeListener;
+ }
+
+ /**
+ * Method that hides the view
+ */
+ public void hide() {
+ setVisibility(View.GONE);
+ }
+
+ /**
+ * Method that shows the view
+ */
+ public void show() {
+ setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Method that returns the extra padding to draw the handlers
+ *
+ * @return The extra padding space
+ */
+ public int getNeededPadding() {
+ return mNeededPadding;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mHandle = getHandleFromCoordinates(x, y);
+ if (mHandle != null) {
+ // Start moving the resize frame
+ mLastTouchX = x;
+ mLastTouchY = y;
+
+ // Start motion
+ if (mOnResizeListener != null) {
+ mOnResizeListener.onStartResize(((Integer)mHandle.getTag()).intValue());
+ }
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mHandle != null) {
+ // Resize
+ if (mOnResizeListener != null) {
+ int handle = ((Integer)mHandle.getTag()).intValue();
+ int delta =
+ handle == Gravity.LEFT || handle == Gravity.RIGHT
+ ? Math.round(x - mLastTouchX)
+ : Math.round(y - mLastTouchY);
+ mOnResizeListener.onResize(handle, delta);
+ invalidate();
+ }
+ mLastTouchX = x;
+ mLastTouchY = y;
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mHandle != null) {
+ if (mOnResizeListener != null) {
+ mOnResizeListener.onEndResize(((Integer)mHandle.getTag()).intValue());
+ return true;
+ }
+ cancelMotion();
+ break;
+ }
+
+ //$FALL-THROUGH$
+ case MotionEvent.ACTION_CANCEL:
+ cancelMotion();
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * Cancel motions
+ */
+ private void cancelMotion() {
+ mHandle = null;
+ mLastTouchX = 0;
+ mLastTouchY = 0;
+ if (mOnResizeListener != null) {
+ mOnResizeListener.onCancel();
+ }
+ }
+
+ /**
+ * Method that returns the resize handle touch from the the screen coordinates
+ *
+ * @param x The x coordinate
+ * @param y The y coordinate
+ * @return View The handle view or null if no handle touched
+ */
+ private View getHandleFromCoordinates(float x, float y) {
+ final View[] handles = {mLeftHandle, mRightHandle, mTopHandle, mBottomHandle};
+ for (View v : handles) {
+ if ((v.getLeft() - mExtraHandlingSpace) < x && (v.getRight() + mExtraHandlingSpace) > x &&
+ (v.getTop() - mExtraHandlingSpace) < y && (v.getBottom() + mExtraHandlingSpace) > y) {
+ return v;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/VerticalEndlessScroller.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/VerticalEndlessScroller.java
new file mode 100644
index 0000000..67a1f17
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/VerticalEndlessScroller.java
@@ -0,0 +1,114 @@
+/*
+ * 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.widget.ScrollView;
+
+/**
+ * A scroll view that notifies the end of the scroll to create new views
+ * dynamically.
+ */
+public class VerticalEndlessScroller extends ScrollView {
+
+ /**
+ * Interface to communicate end-scroll events
+ */
+ public interface OnEndScrollListener {
+ /**
+ * Called when the scroll reachs the end of the scroll
+ */
+ void onEndScroll();
+ }
+
+ private OnEndScrollListener mCallback;
+ private int mEndPadding = 0;
+ private boolean mSwitch = false;
+
+ /**
+ * Constructor of <code>VerticalEndlessScroller</code>.
+ *
+ * @param context The current context
+ */
+ public VerticalEndlessScroller(Context context) {
+ super(context);
+ }
+
+ /**
+ * Constructor of <code>VerticalEndlessScroller</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public VerticalEndlessScroller(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Constructor of <code>VerticalEndlessScroller</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 VerticalEndlessScroller(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Method that set the callback for notify end-scroll events
+ *
+ * @param callback The callback
+ */
+ public void setCallback(OnEndScrollListener callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Method that set the end padding for fired the event
+ *
+ * @param endPadding The end padding
+ */
+ public void setEndPadding(int endPadding) {
+ this.mEndPadding = endPadding;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ // We take the last child in the scrollview
+ View view = getChildAt(getChildCount() - 1);
+ int diff = (view.getBottom() - (getHeight() + getScrollY()));
+ if ((!mSwitch && diff <= mEndPadding)) {
+ if (mCallback != null) {
+ mCallback.onEndScroll();
+ mSwitch = true;
+ return;
+ }
+ } else if (diff > mEndPadding) {
+ mSwitch = false;
+ }
+ }
+
+}