aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk30
-rw-r--r--AndroidManifest.xml78
-rw-r--r--CleanSpec.mk50
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE190
-rw-r--r--README.md15
-rw-r--r--libs/color-picker-view/CHANGELOG.md4
-rw-r--r--libs/color-picker-view/LICENSE.md202
-rw-r--r--libs/color-picker-view/README.md18
-rw-r--r--libs/color-picker-view/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java129
-rw-r--r--libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorDialogView.java494
-rw-r--r--libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPanelView.java174
-rw-r--r--libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPickerView.java998
-rw-r--r--proguard.flags54
-rw-r--r--res/anim/cards_animation.xml36
-rw-r--r--res/drawable-hdpi/ic_accept.pngbin0 -> 1320 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin0 -> 7888 bytes
-rw-r--r--res/drawable-hdpi/ic_overflow.pngbin0 -> 2837 bytes
-rw-r--r--res/drawable-hdpi/ic_return.pngbin0 -> 1653 bytes
-rw-r--r--res/drawable-mdpi/ic_accept.pngbin0 -> 1197 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin0 -> 3796 bytes
-rw-r--r--res/drawable-mdpi/ic_overflow.pngbin0 -> 1253 bytes
-rw-r--r--res/drawable-mdpi/ic_return.pngbin0 -> 971 bytes
-rw-r--r--res/drawable-nodpi/bg_card.9.pngbin0 -> 307 bytes
-rw-r--r--res/drawable-nodpi/bg_notification.9.pngbin0 -> 84 bytes
-rw-r--r--res/drawable-xhdpi/ic_accept.pngbin0 -> 1546 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin0 -> 13096 bytes
-rw-r--r--res/drawable-xhdpi/ic_overflow.pngbin0 -> 2838 bytes
-rw-r--r--res/drawable-xhdpi/ic_return.pngbin0 -> 2769 bytes
-rw-r--r--res/drawable/card.xml22
-rw-r--r--res/drawable/holo_selector.xml38
-rw-r--r--res/layout/album.xml33
-rw-r--r--res/layout/album_info.xml79
-rw-r--r--res/layout/album_pictures.xml80
-rw-r--r--res/layout/choose_picture_fragment.xml32
-rw-r--r--res/layout/color_picker_pref_item.xml30
-rw-r--r--res/layout/picture_item.xml30
-rw-r--r--res/layout/preference_widget_seekbar.xml85
-rw-r--r--res/layout/preference_widget_seekbar_progress.xml27
-rw-r--r--res/menu/album_actions.xml24
-rw-r--r--res/menu/albums.xml22
-rw-r--r--res/menu/main.xml21
-rw-r--r--res/menu/pictures_actions.xml27
-rw-r--r--res/raw/alpha_fragment_shader.glsl26
-rw-r--r--res/raw/alpha_vertex_shader.glsl30
-rw-r--r--res/raw/color_fragment_shader.glsl23
-rw-r--r--res/raw/color_vertex_shader.glsl27
-rw-r--r--res/raw/default_fragment_shader.glsl24
-rw-r--r--res/raw/default_vertex_shader.glsl27
-rw-r--r--res/raw/translate_fragment_shader.glsl24
-rw-r--r--res/raw/translate_vertex_shader.glsl27
-rw-r--r--res/values-land/dimens.xml19
-rw-r--r--res/values-large/dimens.xml19
-rw-r--r--res/values-sw720dp-w1280dp/dimens.xml19
-rw-r--r--res/values-sw720dp/dimens.xml19
-rw-r--r--res/values/arrays.xml65
-rw-r--r--res/values/colors.xml26
-rw-r--r--res/values/config.xml21
-rw-r--r--res/values/dimens.xml47
-rw-r--r--res/values/strings.xml106
-rw-r--r--res/values/styles.xml107
-rw-r--r--res/xml/preferences_general.xml83
-rw-r--r--res/xml/preferences_headers.xml33
-rw-r--r--res/xml/preferences_layout.xml26
-rw-r--r--res/xml/preferences_media.xml57
-rw-r--r--res/xml/wallpaper.xml20
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java96
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java134
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/Colors.java58
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java117
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java207
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java50
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java64
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java498
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java221
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java239
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java309
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java136
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java447
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java175
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java353
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java70
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureManager.java384
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java32
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/Utils.java41
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java147
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java82
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java53
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java33
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java68
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java34
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java56
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/model/Album.java97
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java337
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java86
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java144
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java44
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java129
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java85
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java342
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java318
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java145
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java168
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java30
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java71
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java137
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java182
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java113
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java202
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java104
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java354
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java281
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java308
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java92
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java253
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java139
116 files changed, 12162 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2fe0643
--- /dev/null
+++ b/Android.mk
@@ -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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES += $(call all-java-files-under, libs/color-picker-view/src)
+LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
+
+LOCAL_PACKAGE_NAME := PhotoPhase
+LOCAL_MODULE_TAGS := optional
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..dba8ecf
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.cyanogenmod.wallpapers.photophase"
+ android:versionCode="1"
+ android:versionName="1.0.0">
+
+ <original-package android:name="org.cyanogenmod.wallpapers.photophase" />
+
+ <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17" />
+
+ <uses-feature android:name="android.software.live_wallpaper" android:required="true" />
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:description="@string/app_description"
+ android:theme="@android:style/Theme.Holo.Light"
+ android:hardwareAccelerated="true"
+ android:largeHeap="false">
+
+ <service
+ android:name="PhotoPhaseWallpaper"
+ android:enabled="true"
+ android:label="@string/app_name"
+ android:description="@string/app_description"
+ android:permission="android.permission.BIND_WALLPAPER">
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ </intent-filter>
+ <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" />
+ </service>
+
+ <activity
+ android:name=".PhotoPhaseActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:description="@string/app_description" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".preferences.PhotoPhasePreferences"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:description="@string/app_description">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".SetPhotoPhaseWallpaperActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:description="@string/app_description">
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..a7c94bf
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2007 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************ \ No newline at end of file
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..e60324c
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2012-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f363028
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+PhotoPhase Live Wallpaper
+=========================
+
+A live wallpaper for Android that displays a subset of your photos on your
+wallpaper, that are rotated in small intervals of time with beautiful transitions
+and effects.
+
+This source was released under the terms of
+[Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license.
+
+Visit [CyanogenMod Github](https://github.com/CyanogenMod/android_packages_wallpapers_PhotoPhase)
+and [CyanogenMod Code Review](http://review.cyanogenmod.com/) to get the source
+and submit patches.
+
+Copyright © 2013 The CyanogenMod Project
diff --git a/libs/color-picker-view/CHANGELOG.md b/libs/color-picker-view/CHANGELOG.md
new file mode 100644
index 0000000..342ba60
--- /dev/null
+++ b/libs/color-picker-view/CHANGELOG.md
@@ -0,0 +1,4 @@
+ChangeLog
+========================
+
+The source was grabbed from version 1.0 (r8)
diff --git a/libs/color-picker-view/LICENSE.md b/libs/color-picker-view/LICENSE.md
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/libs/color-picker-view/LICENSE.md
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/libs/color-picker-view/README.md b/libs/color-picker-view/README.md
new file mode 100644
index 0000000..b26ef5a
--- /dev/null
+++ b/libs/color-picker-view/README.md
@@ -0,0 +1,18 @@
+mColorPicker
+================================
+
+A color picker is something that has always been missing from the standard
+set of components which developers can build their user interface in Android
+with. Although there have been a few color pickers floating around on the
+internet I never found any that I thought was good enough for use in my
+applications so I sat down to write my own. This is the result and I have
+decided to release it as open source application for all you developers
+out there to use, free of charge of course.
+
+Checkout latest sources at http://code.google.com/p/color-picker-view/
+
+This library is released under the [Apache 2.0]
+http://www.apache.org/licenses/LICENSE-2.0.html) license.
+
+Copyright © 2010 Daniel Nilsson
+Copyright © 2013 The CyanogenMod Project
diff --git a/libs/color-picker-view/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java b/libs/color-picker-view/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java
new file mode 100644
index 0000000..e878756
--- /dev/null
+++ b/libs/color-picker-view/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 afzkl.development.mColorPicker.drawables;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This drawable that draws a simple white and gray chessboard pattern.
+ * It's pattern you will often see as a background behind a
+ * partly transparent image in many applications.
+ * @author Daniel Nilsson
+ */
+@SuppressWarnings("all")
+public class AlphaPatternDrawable extends Drawable {
+
+ private int mRectangleSize = 10;
+
+ private final Paint mPaint = new Paint();
+ private final Paint mPaintWhite = new Paint();
+ private final Paint mPaintGray = new Paint();
+
+ private int numRectanglesHorizontal;
+ private int numRectanglesVertical;
+
+ /**
+ * Bitmap in which the pattern will be cahched.
+ */
+ private Bitmap mBitmap;
+
+ public AlphaPatternDrawable(int rectangleSize) {
+ mRectangleSize = rectangleSize;
+ mPaintWhite.setColor(0xffffffff);
+ mPaintGray.setColor(0xffcbcbcb);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ throw new UnsupportedOperationException("Alpha is not supported by this drawwable.");
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable.");
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ int height = bounds.height();
+ int width = bounds.width();
+
+ numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize));
+ numRectanglesVertical = (int) Math.ceil(height / mRectangleSize);
+
+ generatePatternBitmap();
+
+ }
+
+ /**
+ * This will generate a bitmap with the pattern
+ * as big as the rectangle we were allow to draw on.
+ * We do this to chache the bitmap so we don't need to
+ * recreate it each time draw() is called since it
+ * takes a few milliseconds.
+ */
+ private void generatePatternBitmap() {
+
+ if (getBounds().width() <= 0 || getBounds().height() <= 0) {
+ return;
+ }
+
+ mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
+ Canvas canvas = new Canvas(mBitmap);
+
+ Rect r = new Rect();
+ boolean verticalStartWhite = true;
+ for (int i = 0; i <= numRectanglesVertical; i++) {
+
+ boolean isWhite = verticalStartWhite;
+ for (int j = 0; j <= numRectanglesHorizontal; j++) {
+
+ r.top = i * mRectangleSize;
+ r.left = j * mRectangleSize;
+ r.bottom = r.top + mRectangleSize;
+ r.right = r.left + mRectangleSize;
+
+ canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray);
+
+ isWhite = !isWhite;
+ }
+
+ verticalStartWhite = !verticalStartWhite;
+
+ }
+
+ }
+
+}
diff --git a/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorDialogView.java b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorDialogView.java
new file mode 100644
index 0000000..2333002
--- /dev/null
+++ b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorDialogView.java
@@ -0,0 +1,494 @@
+/*
+ * 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 afzkl.development.mColorPicker.views;
+
+import afzkl.development.mColorPicker.views.ColorPickerView.OnColorChangedListener;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+/**
+ * A view use directly into a dialog. It contains a one {@link ColorPickerView}
+ * and two {@link ColorPanelView} (the current color and the new color)
+ */
+public class ColorDialogView extends RelativeLayout
+ implements OnColorChangedListener, TextWatcher {
+
+ private static final int DEFAULT_MARGIN_DP = 16;
+ private static final int DEFAULT_PANEL_HEIGHT_DP = 32;
+ private static final int DEFAULT_TEXT_SIZE_SP = 12;
+ private static final int DEFAULT_LABEL_TEXT_SIZE_SP = 18;
+
+ private ColorPickerView mPickerView;
+ private ColorPanelView mCurrentColorView;
+ private ColorPanelView mNewColorView;
+ private TextView tvCurrent;
+ private TextView tvNew;
+ private TextView tvColorLabel;
+ private EditText etColor;
+
+ private String mCurrentLabelText = "Current:"; //$NON-NLS-1$
+ private String mNewLabelText = "New:"; //$NON-NLS-1$
+
+ private String mColorLabelText = "Color:"; //$NON-NLS-1$
+
+ /**
+ * Constructor of <code>ColorDialogView</code>
+ *
+ * @param context The current context
+ */
+ public ColorDialogView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor of <code>ColorDialogView</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public ColorDialogView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Constructor of <code>ColorDialogView</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 ColorDialogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Method that initializes the view. This method loads all the necessary
+ * information and create an appropriate layout for the view
+ */
+ private void init() {
+ // To fight color branding.
+ ((Activity)getContext()).getWindow().setFormat(PixelFormat.RGBA_8888);
+
+ // Create the scrollview over the dialog
+ final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
+ ScrollView sv = new ScrollView(getContext());
+ sv.setId(generateViewId());
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.setMargins(dlgMarging, 0, dlgMarging, 0);
+ sv.setLayoutParams(lp);
+ sv.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
+
+ // Now the vertical layout
+ LinearLayout ll = new LinearLayout(getContext());
+ ll.setId(generateViewId());
+ lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ ll.setLayoutParams(lp);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ sv.addView(ll);
+
+ // Creates the color input field
+ int id = createColorInput(ll);
+
+ // Creates the color picker
+ id = createColorPicker(ll, id);
+
+ // Creates the current color and new color panels
+ id = createColorsPanel(ll, id);
+
+ // Add the scrollview
+ addView(sv);
+
+ // Sets the input color
+ this.etColor.setText(toHex(this.mNewColorView.getColor()));
+ }
+
+ /**
+ * Method that creates the color input
+ *
+ * @param parent The parent layout
+ */
+ private int createColorInput(ViewGroup parent) {
+ final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
+ LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ lp2.setMargins(0, 0, dlgMarging, 0);
+ this.tvColorLabel = new TextView(getContext());
+ this.tvColorLabel.setText(this.mColorLabelText);
+ this.tvColorLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_LABEL_TEXT_SIZE_SP);
+ this.tvColorLabel.setGravity(Gravity.BOTTOM | Gravity.LEFT);
+ this.tvColorLabel.setLayoutParams(lp2);
+
+ lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ this.etColor = new EditText(getContext());
+ this.etColor.setSingleLine();
+ this.etColor.setGravity(Gravity.TOP | Gravity.LEFT);
+ this.etColor.setCursorVisible(true);
+ this.etColor.setImeOptions(
+ EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+ this.etColor.setInputType(
+ InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ this.etColor.setLayoutParams(lp2);
+ InputFilter[] filters = new InputFilter[2];
+ filters[0] = new InputFilter.LengthFilter(8);
+ filters[1] = new InputFilter() {
+ @Override
+ public CharSequence filter(CharSequence source, int start,
+ int end, Spanned dest, int dstart, int dend) {
+ if (start >= end) return ""; //$NON-NLS-1$
+ String s = source.subSequence(start, end).toString();
+ StringBuilder sb = new StringBuilder();
+ int cc = s.length();
+ for (int i = 0; i < cc; i++) {
+ char c = s.charAt(i);
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F')) {
+ sb.append(c);
+ }
+ }
+ return sb.toString().toUpperCase();
+ }
+ };
+ this.etColor.setFilters(filters);
+ this.etColor.addTextChangedListener(this);
+
+ LinearLayout ll1 = new LinearLayout(getContext());
+ ll1.setId(generateViewId());
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.setMargins(dlgMarging, 0, dlgMarging, 0);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ ll1.setLayoutParams(lp);
+ ll1.addView(this.tvColorLabel);
+ ll1.addView(this.etColor);
+ parent.addView(ll1);
+
+ return ll1.getId();
+ }
+
+ /**
+ * Method that creates the color picker
+ *
+ * @param parent The parent layout
+ * @param belowOf The anchor view
+ * @return id The layout id
+ */
+ private int createColorPicker(ViewGroup parent, int belowOf) {
+ final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
+ this.mPickerView = new ColorPickerView(getContext());
+ this.mPickerView.setId(generateViewId());
+ this.mPickerView.setOnColorChangedListener(this);
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.setMargins(dlgMarging, 0, dlgMarging, 0);
+ lp.addRule(RelativeLayout.BELOW, belowOf);
+ this.mPickerView.setLayoutParams(lp);
+ parent.addView(this.mPickerView);
+ return this.mPickerView.getId();
+ }
+
+ /**
+ * Method that creates the colors panel (current and new)
+ *
+ * @param parent The parent layout
+ * @param belowOf The anchor view
+ * @return id The layout id
+ */
+ private int createColorsPanel(ViewGroup parent, int belowOf) {
+ final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
+ final int panelHeight = (int)convertDpToPixel(DEFAULT_PANEL_HEIGHT_DP);
+ LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ 1);
+
+ // Titles
+ this.tvCurrent = new TextView(getContext());
+ lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1);
+ this.tvCurrent.setLayoutParams(lp2);
+ this.tvCurrent.setText(this.mCurrentLabelText);
+ this.tvCurrent.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
+ this.tvNew = new TextView(getContext());
+ this.tvNew.setLayoutParams(lp2);
+ this.tvNew.setText(this.mNewLabelText);
+ this.tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
+ TextView sep1 = new TextView(getContext());
+ lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 0);
+ lp2.setMargins(dlgMarging, 0, dlgMarging, 0);
+ sep1.setLayoutParams(lp2);
+ sep1.setText(" "); //$NON-NLS-1$
+ sep1.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
+
+ LinearLayout ll1 = new LinearLayout(getContext());
+ ll1.setId(generateViewId());
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2);
+ lp.addRule(RelativeLayout.BELOW, belowOf);
+ ll1.setLayoutParams(lp);
+ ll1.addView(this.tvCurrent);
+ ll1.addView(sep1);
+ ll1.addView(this.tvNew);
+ parent.addView(ll1);
+
+ // Color panels
+ lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1);
+ this.mCurrentColorView = new ColorPanelView(getContext());
+ this.mCurrentColorView.setLayoutParams(lp2);
+ this.mNewColorView = new ColorPanelView(getContext());
+ this.mNewColorView.setLayoutParams(lp2);
+ TextView sep2 = new TextView(getContext());
+ lp2 = new LinearLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ 0);
+ lp2.setMargins(dlgMarging, 0, dlgMarging, 0);
+ sep2.setLayoutParams(lp2);
+ sep2.setText("-"); //$NON-NLS-1$
+ sep2.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
+
+ LinearLayout ll2 = new LinearLayout(getContext());
+ ll2.setId(generateViewId());
+ lp = new RelativeLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT, panelHeight);
+ lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2);
+ lp.addRule(RelativeLayout.BELOW, ll1.getId());
+ ll2.setLayoutParams(lp);
+ ll2.addView(this.mCurrentColorView);
+ ll2.addView(sep2);
+ ll2.addView(this.mNewColorView);
+ parent.addView(ll2);
+
+ return ll2.getId();
+ }
+
+ /**
+ * Method that returns the color of the picker
+ *
+ * @return The ARGB color
+ */
+ public int getColor() {
+ return this.mPickerView.getColor();
+ }
+
+ /**
+ * Method that set the color of the picker
+ *
+ * @param argb The ARGB color
+ */
+ public void setColor(int argb) {
+ setColor(argb, false);
+ }
+
+ /**
+ * Method that set the color of the picker
+ *
+ * @param argb The ARGB color
+ * @param fromEditText If the call comes from the <code>EditText</code>
+ */
+ private void setColor(int argb, boolean fromEditText) {
+ this.mPickerView.setColor(argb, false);
+ this.mCurrentColorView.setColor(argb);
+ this.mNewColorView.setColor(argb);
+ if (!fromEditText) {
+ this.etColor.setText(toHex(this.mNewColorView.getColor()));
+ }
+ }
+
+ /**
+ * Method that display/hide the alpha slider
+ *
+ * @param show If the alpha slider should be shown
+ */
+ public void showAlphaSlider(boolean show) {
+ this.mPickerView.setAlphaSliderVisible(show);
+ }
+
+ /**
+ * Set the text that should be shown in the alpha slider.
+ * Set to null to disable text.
+ *
+ * @param text Text that should be shown.
+ */
+ public void setAlphaSliderText(String text) {
+ this.mPickerView.setAlphaSliderText(text);
+ }
+
+ /**
+ * Set the text that should be shown in the actual color panel.
+ * Set to null to disable text.
+ *
+ * @param text Text that should be shown.
+ */
+ public void setCurrentColorText(String text) {
+ this.mCurrentLabelText = text;
+ this.tvCurrent.setText(this.mCurrentLabelText);
+ }
+
+ /**
+ * Set the text that should be shown in the new color panel.
+ * Set to null to disable text.
+ *
+ * @param text Text that should be shown.
+ */
+ public void setNewColorText(String text) {
+ this.mNewLabelText = text;
+ this.tvNew.setText(this.mNewLabelText);
+ }
+
+ /**
+ * Set the text that should be shown in the label of the color input.
+ * Set to null to disable text.
+ *
+ * @param text Text that should be shown.
+ */
+ public void setColorLabelText(String text) {
+ this.mColorLabelText = text;
+ this.tvColorLabel.setText(this.mColorLabelText);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onColorChanged(int color) {
+ this.mNewColorView.setColor(color);
+ this.etColor.removeTextChangedListener(this);
+ this.etColor.setText(toHex(this.mNewColorView.getColor()));
+ this.etColor.addTextChangedListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() == 8) {
+ try {
+ setColor(toARGB(s.toString()), true);
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+
+ /**
+ * 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
+ */
+ private float convertDpToPixel(float dp) {
+ Resources resources = getContext().getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ return dp * (metrics.densityDpi / 160f);
+ }
+
+ /**
+ * Method that converts an ARGB color to its hex string color representation
+ *
+ * @param argb The ARGB color
+ * @return String The hex string representation of the color
+ */
+ private static String toHex(int argb) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(toHexString((byte)Color.alpha(argb)));
+ sb.append(toHexString((byte)Color.red(argb)));
+ sb.append(toHexString((byte)Color.green(argb)));
+ sb.append(toHexString((byte)Color.blue(argb)));
+ return sb.toString();
+ }
+
+ /**
+ * Method that converts an hex string color representation to an ARGB color
+ *
+ * @param hex The hex string representation of the color
+ * @return int The ARGB color
+ */
+ private static int toARGB(String hex) {
+ return Color.parseColor("#" + hex); //$NON-NLS-1$
+ }
+
+ /**
+ * Method that converts a byte into its hex string representation
+ *
+ * @param v The value to convert
+ * @return String The hex string representation
+ */
+ private static String toHexString(byte v) {
+ String hex = Integer.toHexString(v & 0xff);
+ if (hex.length() == 1) {
+ hex = "0" + hex; //$NON-NLS-1$
+ }
+ return hex.toUpperCase();
+ }
+}
diff --git a/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPanelView.java b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPanelView.java
new file mode 100644
index 0000000..9764ff1
--- /dev/null
+++ b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPanelView.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 afzkl.development.mColorPicker.views;
+
+import afzkl.development.mColorPicker.drawables.AlphaPatternDrawable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This class draws a panel which which will be filled with a color which can be set.
+ * It can be used to show the currently selected color which you will get from
+ * the {@link ColorPickerView}.
+ * @author Daniel Nilsson
+ *
+ */
+@SuppressWarnings("all")
+public class ColorPanelView extends View{
+
+ /**
+ * The width in pixels of the border
+ * surrounding the color panel.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ private static float mDensity = 1f;
+
+ private int mBorderColor = 0xff6E6E6E;
+ private int mColor = 0xff000000;
+
+ private Paint mBorderPaint;
+ private Paint mColorPaint;
+
+ private RectF mDrawingRect;
+ private RectF mColorRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+
+ public ColorPanelView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ mBorderPaint = new Paint();
+ mColorPaint = new Paint();
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ final RectF rect = mColorRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect, mBorderPaint);
+ }
+
+ if (mAlphaPattern != null) {
+ mAlphaPattern.draw(canvas);
+ }
+
+ mColorPaint.setColor(mColor);
+
+ canvas.drawRect(rect, mColorPaint);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = getPaddingLeft();
+ mDrawingRect.right = w - getPaddingRight();
+ mDrawingRect.top = getPaddingTop();
+ mDrawingRect.bottom = h - getPaddingBottom();
+
+ setUpColorRect();
+
+ }
+
+ private void setUpColorRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mColorRect = new RectF(left,top, right, bottom);
+
+ mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity));
+
+ mAlphaPattern.setBounds(Math.round(mColorRect.left),
+ Math.round(mColorRect.top),
+ Math.round(mColorRect.right),
+ Math.round(mColorRect.bottom));
+
+ }
+
+ /**
+ * Set the color that should be shown by this view.
+ * @param color
+ */
+ public void setColor(int color) {
+ mColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color currently show by this view.
+ * @return
+ */
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Set the color of the border surrounding the panel.
+ * @param color
+ */
+ public void setBorderColor(int color) {
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding the panel.
+ */
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+}
diff --git a/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPickerView.java b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPickerView.java
new file mode 100644
index 0000000..fd901c5
--- /dev/null
+++ b/libs/color-picker-view/src/afzkl/development/mColorPicker/views/ColorPickerView.java
@@ -0,0 +1,998 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 afzkl.development.mColorPicker.views;
+
+import afzkl.development.mColorPicker.drawables.AlphaPatternDrawable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+/**
+ * Displays a color picker to the user and allow them
+ * to select a color. A slider for the alpha channel is
+ * also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ * @author Daniel Nilsson
+ */
+@SuppressWarnings("all")
+public class ColorPickerView extends View{
+
+ public interface OnColorChangedListener{
+ public void onColorChanged(int color);
+ }
+
+ private final static int PANEL_SAT_VAL = 0;
+ private final static int PANEL_HUE = 1;
+ private final static int PANEL_ALPHA = 2;
+
+ /**
+ * The width in pixels of the border
+ * surrounding all color panels.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ /**
+ * The width in dp of the hue panel.
+ */
+ private float HUE_PANEL_WIDTH = 30f;
+ /**
+ * The height in dp of the alpha panel
+ */
+ private float ALPHA_PANEL_HEIGHT = 20f;
+ /**
+ * The distance in dp between the different
+ * color panels.
+ */
+ private float PANEL_SPACING = 10f;
+ /**
+ * The radius in dp of the color palette tracker circle.
+ */
+ private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+ /**
+ * The dp which the tracker of the hue or alpha panel
+ * will extend outside of its bounds.
+ */
+ private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+
+ private static float mDensity = 1f;
+
+ private OnColorChangedListener mListener;
+
+ private Paint mSatValPaint;
+ private Paint mSatValTrackerPaint;
+
+ private Paint mHuePaint;
+ private Paint mHueTrackerPaint;
+
+ private Paint mAlphaPaint;
+ private Paint mAlphaTextPaint;
+
+ private Paint mBorderPaint;
+
+ private Shader mValShader;
+ private Shader mSatShader;
+ private Shader mHueShader;
+ private Shader mAlphaShader;
+
+ private int mAlpha = 0xff;
+ private float mHue = 360f;
+ private float mSat = 0f;
+ private float mVal = 0f;
+
+ private String mAlphaSliderText = "Alpha";
+ private int mSliderTrackerColor = 0xff1c1c1c;
+ private int mBorderColor = 0xff6E6E6E;
+ private boolean mShowAlphaPanel = false;
+
+ /*
+ * To remember which panel that has the "focus" when
+ * processing hardware button data.
+ */
+ private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+ /**
+ * Offset from the edge we must have or else
+ * the finger tracker will get clipped when
+ * it is drawn outside of the view.
+ */
+ private float mDrawingOffset;
+
+
+ /*
+ * Distance form the edges of the view
+ * of where we are allowed to draw.
+ */
+ private RectF mDrawingRect;
+
+ private RectF mSatValRect;
+ private RectF mHueRect;
+ private RectF mAlphaRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ private Point mStartTouchPoint = null;
+
+
+ public ColorPickerView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+ RECTANGLE_TRACKER_OFFSET *= mDensity;
+ HUE_PANEL_WIDTH *= mDensity;
+ ALPHA_PANEL_HEIGHT *= mDensity;
+ PANEL_SPACING = PANEL_SPACING * mDensity;
+
+ mDrawingOffset = calculateRequiredOffset();
+
+ initPaintTools();
+
+ //Needed for receiving trackball motion events.
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ }
+
+ private void initPaintTools() {
+
+ mSatValPaint = new Paint();
+ mSatValTrackerPaint = new Paint();
+ mHuePaint = new Paint();
+ mHueTrackerPaint = new Paint();
+ mAlphaPaint = new Paint();
+ mAlphaTextPaint = new Paint();
+ mBorderPaint = new Paint();
+
+
+ mSatValTrackerPaint.setStyle(Style.STROKE);
+ mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+ mSatValTrackerPaint.setAntiAlias(true);
+
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+ mHueTrackerPaint.setStyle(Style.STROKE);
+ mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+ mHueTrackerPaint.setAntiAlias(true);
+
+ mAlphaTextPaint.setColor(0xff1c1c1c);
+ mAlphaTextPaint.setTextSize(14f * mDensity);
+ mAlphaTextPaint.setAntiAlias(true);
+ mAlphaTextPaint.setTextAlign(Align.CENTER);
+ mAlphaTextPaint.setFakeBoldText(true);
+
+
+ }
+
+ private float calculateRequiredOffset() {
+ float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+ offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+ return offset * 1.5f;
+ }
+
+ private int[] buildHueColorArray() {
+
+ int[] hue = new int[361];
+
+ int count = 0;
+ for (int i = hue.length -1; i >= 0; i--, count++) {
+ hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f});
+ }
+
+ return hue;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ checkHardwareAccelerationSupport();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return;
+
+ drawSatValPanel(canvas);
+ drawHuePanel(canvas);
+ drawAlphaPanel(canvas);
+
+ }
+
+ private void drawSatValPanel(Canvas canvas) {
+
+ final RectF rect = mSatValRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+ }
+
+ if (mValShader == null) {
+ mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ 0xffffffff, 0xff000000, TileMode.CLAMP);
+ }
+
+ int rgb = Color.HSVToColor(new float[]{mHue,1f,1f});
+
+ mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ 0xffffffff, rgb, TileMode.CLAMP);
+ ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY);
+ mSatValPaint.setShader(mShader);
+
+ canvas.drawRect(rect, mSatValPaint);
+
+ Point p = satValToPoint(mSat, mVal);
+
+ mSatValTrackerPaint.setColor(0xff000000);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint);
+
+ mSatValTrackerPaint.setColor(0xffdddddd);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+
+ }
+
+ private void drawHuePanel(Canvas canvas) {
+
+ final RectF rect = mHueRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ if (mHueShader == null) {
+ mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP);
+ mHuePaint.setShader(mHueShader);
+ }
+
+ canvas.drawRect(rect, mHuePaint);
+
+ float rectHeight = 4 * mDensity / 2;
+
+ Point p = hueToPoint(mHue);
+
+ RectF r = new RectF();
+ r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+ r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+ r.top = p.y - rectHeight;
+ r.bottom = p.y + rectHeight;
+
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+ private void drawAlphaPanel(Canvas canvas) {
+
+ if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return;
+
+ final RectF rect = mAlphaRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+
+ mAlphaPattern.draw(canvas);
+
+ float[] hsv = new float[]{mHue,mSat,mVal};
+ int color = Color.HSVToColor(hsv);
+ int acolor = Color.HSVToColor(0, hsv);
+
+ mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ color, acolor, TileMode.CLAMP);
+
+
+ mAlphaPaint.setShader(mAlphaShader);
+
+ canvas.drawRect(rect, mAlphaPaint);
+
+ if (mAlphaSliderText != null && mAlphaSliderText!= "") {
+ canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint);
+ }
+
+ float rectWidth = 4 * mDensity / 2;
+
+ Point p = alphaToPoint(mAlpha);
+
+ RectF r = new RectF();
+ r.left = p.x - rectWidth;
+ r.right = p.x + rectWidth;
+ r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+ r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+
+ private Point hueToPoint(float hue) {
+
+ final RectF rect = mHueRect;
+ final float height = rect.height();
+
+ Point p = new Point();
+
+ p.y = (int) (height - (hue * height / 360f) + rect.top);
+ p.x = (int) rect.left;
+
+ return p;
+ }
+
+ private Point satValToPoint(float sat, float val) {
+
+ final RectF rect = mSatValRect;
+ final float height = rect.height();
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (sat * width + rect.left);
+ p.y = (int) ((1f - val) * height + rect.top);
+
+ return p;
+ }
+
+ private Point alphaToPoint(int alpha) {
+
+ final RectF rect = mAlphaRect;
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+ p.y = (int) rect.top;
+
+ return p;
+
+ }
+
+ private float[] pointToSatVal(float x, float y) {
+
+ final RectF rect = mSatValRect;
+ float[] result = new float[2];
+
+ float width = rect.width();
+ float height = rect.height();
+
+ if (x < rect.left) {
+ x = 0f;
+ }
+ else if (x > rect.right) {
+ x = width;
+ }
+ else{
+ x = x - rect.left;
+ }
+
+ if (y < rect.top) {
+ y = 0f;
+ }
+ else if (y > rect.bottom) {
+ y = height;
+ }
+ else{
+ y = y - rect.top;
+ }
+
+
+ result[0] = 1.f / width * x;
+ result[1] = 1.f - (1.f / height * y);
+
+ return result;
+ }
+
+ private float pointToHue(float y) {
+
+ final RectF rect = mHueRect;
+
+ float height = rect.height();
+
+ if (y < rect.top) {
+ y = 0f;
+ }
+ else if (y > rect.bottom) {
+ y = height;
+ }
+ else{
+ y = y - rect.top;
+ }
+
+ return 360f - (y * 360f / height);
+ }
+
+ private int pointToAlpha(int x) {
+
+ final RectF rect = mAlphaRect;
+ final int width = (int) rect.width();
+
+ if (x < rect.left) {
+ x = 0;
+ }
+ else if (x > rect.right) {
+ x = width;
+ }
+ else{
+ x = x - (int)rect.left;
+ }
+
+ return 0xff - (x * 0xff / width);
+
+ }
+
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+
+ float x = event.getX();
+ float y = event.getY();
+
+ boolean update = false;
+
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+
+ switch(mLastTouchedPanel) {
+
+ case PANEL_SAT_VAL:
+
+ float sat, val;
+
+ sat = mSat + x/50f;
+ val = mVal - y/50f;
+
+ if (sat < 0f) {
+ sat = 0f;
+ }
+ else if (sat > 1f) {
+ sat = 1f;
+ }
+
+ if (val < 0f) {
+ val = 0f;
+ }
+ else if (val > 1f) {
+ val = 1f;
+ }
+
+ mSat = sat;
+ mVal = val;
+
+ update = true;
+
+ break;
+
+ case PANEL_HUE:
+
+ float hue = mHue - y * 10f;
+
+ if (hue < 0f) {
+ hue = 0f;
+ }
+ else if (hue > 360f) {
+ hue = 360f;
+ }
+
+ mHue = hue;
+
+ update = true;
+
+ break;
+
+ case PANEL_ALPHA:
+
+ if (!mShowAlphaPanel || mAlphaRect == null) {
+ update = false;
+ }
+ else{
+
+ int alpha = (int) (mAlpha - x*10);
+
+ if (alpha < 0) {
+ alpha = 0;
+ }
+ else if (alpha > 0xff) {
+ alpha = 0xff;
+ }
+
+ mAlpha = alpha;
+
+
+ update = true;
+ }
+
+ break;
+ }
+
+
+ }
+
+
+ if (update) {
+
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ return true;
+ }
+
+
+ return super.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ boolean update = false;
+
+ switch(event.getAction()) {
+
+ case MotionEvent.ACTION_DOWN:
+
+ mStartTouchPoint = new Point((int)event.getX(), (int)event.getY());
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ case MotionEvent.ACTION_UP:
+
+ mStartTouchPoint = null;
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ }
+
+ if (update) {
+
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ return true;
+ }
+
+
+ return super.onTouchEvent(event);
+ }
+
+ private boolean moveTrackersIfNeeded(MotionEvent event) {
+
+ if (mStartTouchPoint == null) return false;
+
+ boolean update = false;
+
+ int startX = mStartTouchPoint.x;
+ int startY = mStartTouchPoint.y;
+
+
+ if (mHueRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_HUE;
+
+ mHue = pointToHue(event.getY());
+
+ update = true;
+ }
+ else if (mSatValRect.contains(startX, startY)) {
+
+ mLastTouchedPanel = PANEL_SAT_VAL;
+
+ float[] result = pointToSatVal(event.getX(), event.getY());
+
+ mSat = result[0];
+ mVal = result[1];
+
+ update = true;
+ }
+ else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) {
+
+ mLastTouchedPanel = PANEL_ALPHA;
+
+ mAlpha = pointToAlpha((int)event.getX());
+
+ update = true;
+ }
+
+
+ return update;
+ }
+
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = 0;
+ int height = 0;
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+ int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+
+ widthAllowed = chooseWidth(widthMode, widthAllowed);
+ heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+
+ if (!mShowAlphaPanel) {
+ height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+ //If calculated height (based on the width) is more than the allowed height.
+ if (height > heightAllowed) {
+ height = heightAllowed;
+ width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+ }
+ else{
+ width = widthAllowed;
+ }
+ }
+ else{
+
+ width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+ if (width > widthAllowed) {
+ width = widthAllowed;
+ height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+ }
+ else{
+ height = heightAllowed;
+ }
+
+
+ }
+
+
+ setMeasuredDimension(width, height);
+ }
+
+ private int chooseWidth(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedWidth();
+ }
+ }
+
+ private int chooseHeight(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPreferedHeight();
+ }
+ }
+
+ private int getPrefferedWidth() {
+
+ int width = getPreferedHeight();
+
+ if (mShowAlphaPanel) {
+ width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+ }
+
+
+ return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+
+ }
+
+ private int getPreferedHeight() {
+
+ int height = (int)(200 * mDensity);
+
+ if (mShowAlphaPanel) {
+ height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ return height;
+ }
+
+
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+ mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+ mDrawingRect.top = mDrawingOffset + getPaddingTop();
+ mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+ setUpSatValRect();
+ setUpHueRect();
+ setUpAlphaRect();
+ }
+
+ private void setUpSatValRect() {
+
+ final RectF dRect = mDrawingRect;
+ float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+ if (mShowAlphaPanel) {
+ panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = top + panelSide;
+ float right = left + panelSide;
+
+ mSatValRect = new RectF(left,top, right, bottom);
+ }
+
+ private void setUpHueRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mHueRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpAlphaRect() {
+
+ if (!mShowAlphaPanel) return;
+
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mAlphaRect = new RectF(left, top, right, bottom);
+
+
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+ mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
+ .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
+ .round(mAlphaRect.bottom));
+
+
+
+ }
+
+
+ /**
+ * Set a OnColorChangedListener to get notified when the color
+ * selected by the user has changed.
+ * @param listener
+ */
+ public void setOnColorChangedListener(OnColorChangedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Set the color of the border surrounding all panels.
+ * @param color
+ */
+ public void setBorderColor(int color) {
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding all panels.
+ */
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+ /**
+ * Get the current color this view is showing.
+ * @return the current color.
+ */
+ public int getColor() {
+ return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal});
+ }
+
+ /**
+ * Set the color the view should show.
+ * @param color The color that should be selected.
+ */
+ public void setColor(int color) {
+ setColor(color, false);
+ }
+
+ /**
+ * Set the color this view should show.
+ * @param color The color that should be selected.
+ * @param callback If you want to get a callback to
+ * your OnColorChangedListener.
+ */
+ public void setColor(int color, boolean callback) {
+
+ int alpha = Color.alpha(color);
+ int red = Color.red(color);
+ int blue = Color.blue(color);
+ int green = Color.green(color);
+
+ float[] hsv = new float[3];
+
+ Color.RGBToHSV(red, green, blue, hsv);
+
+ mAlpha = alpha;
+ mHue = hsv[0];
+ mSat = hsv[1];
+ mVal = hsv[2];
+
+ if (callback && mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Get the drawing offset of the color picker view.
+ * The drawing offset is the distance from the side of
+ * a panel to the side of the view minus the padding.
+ * Useful if you want to have your own panel below showing
+ * the currently selected color and want to align it perfectly.
+ * @return The offset in pixels.
+ */
+ public float getDrawingOffset() {
+ return mDrawingOffset;
+ }
+
+ /**
+ * Set if the user is allowed to adjust the alpha panel. Default is false.
+ * If it is set to false no alpha will be set.
+ * @param visible
+ */
+ public void setAlphaSliderVisible(boolean visible) {
+
+ if (mShowAlphaPanel != visible) {
+ mShowAlphaPanel = visible;
+
+ /*
+ * Reset all shader to force a recreation.
+ * Otherwise they will not look right after
+ * the size of the view has changed.
+ */
+ mValShader = null;
+ mSatShader = null;
+ mHueShader = null;
+ mAlphaShader = null;;
+
+ requestLayout();
+ }
+
+ }
+
+ public void setSliderTrackerColor(int color) {
+ mSliderTrackerColor = color;
+
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+
+ invalidate();
+ }
+
+ public int getSliderTrackerColor() {
+ return mSliderTrackerColor;
+ }
+
+ /**
+ * Set the text that should be shown in the
+ * alpha slider. Set to null to disable text.
+ * @param res string resource id.
+ */
+ public void setAlphaSliderText(int res) {
+ String text = getContext().getString(res);
+ setAlphaSliderText(text);
+ }
+
+ /**
+ * Set the text that should be shown in the
+ * alpha slider. Set to null to disable text.
+ * @param text Text that should be shown.
+ */
+ public void setAlphaSliderText(String text) {
+ mAlphaSliderText = text;
+ invalidate();
+ }
+
+ /**
+ * Get the current value of the text
+ * that will be shown in the alpha
+ * slider.
+ * @return
+ */
+ public String getAlphaSliderText() {
+ return mAlphaSliderText;
+ }
+
+ /**
+ * Method that checks the support for HardwareAcceleration. Check AOSP notice<br/>
+ * <br/>
+ * <pre>
+ * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
+ * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
+ * application is affected by any of these missing features or limitations, you can turn
+ * off hardware acceleration for just the affected portion of your application by calling
+ * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'
+ */
+ private void checkHardwareAccelerationSupport() {
+ // HardwareAcceleration sit is only available since ICS. 14 = ICS_VERSION_CODE
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ try{
+ // We need to use reflection to get that method to avoid compilation errors
+ Method isHardwareAccelerated =
+ getClass().getMethod("isHardwareAccelerated", new Class[]{});
+ Object o = isHardwareAccelerated.invoke(this, new Object[]{});
+ if (null != o && o instanceof Boolean && (Boolean)o) {
+ // HardwareAcceleration is supported. Use SoftwareAcceleration
+ Method setLayerType =
+ getClass().getMethod(
+ "setLayerType", int.class, android.graphics.Paint.class);
+ setLayerType.invoke(this, 1, (android.graphics.Paint)null);
+ }
+ } catch (Exception e) { /** NON BLOCK **/}
+ }
+ }
+
+}
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..38329a5
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,54 @@
+#configuration
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+#keep common classes
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+
+#keep all classes that might be used in XML layouts
+-keep public class * extends android.view.View {
+ public <init>(android.content.Context);
+ public <init>(android.content.Context, android.util.AttributeSet);
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+ public void set*(...);
+}
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+#keep callback methods (onClick, onOption...)
+-keepclassmembers class * extends android.content.Context {
+ public void *(android.view.View);
+ public void *(android.view.MenuItem);
+}
+
+#keep parcelable constructors
+-keepclassmembers class * implements android.os.Parcelable {
+ static android.os.Parcelable$Creator CREATOR;
+}
+
+#keep all resource identifiers
+-keep class **.R$*
+-keepclassmembers class **.R$* {
+ public static <fields>;
+}
+
+#keep preference's classes
+-keep public class * extends android.preference.PreferenceFragment {
+ public <init>(...);
+}
+-keep public class * extends android.preference.Preference {
+ public <init>(...);
+}
+
diff --git a/res/anim/cards_animation.xml b/res/anim/cards_animation.xml
new file mode 100644
index 0000000..8ae21b3
--- /dev/null
+++ b/res/anim/cards_animation.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator" >
+
+ <translate
+ android:duration="800"
+ android:fromYDelta="100%p"
+ android:toYDelta="0" />
+
+ <alpha
+ android:duration="800"
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0" />
+
+ <rotate
+ android:duration="800"
+ android:fromDegrees="-25"
+ android:pivotX="100%"
+ android:pivotY="0"
+ android:toDegrees="0" />
+</set> \ No newline at end of file
diff --git a/res/drawable-hdpi/ic_accept.png b/res/drawable-hdpi/ic_accept.png
new file mode 100644
index 0000000..58bf972
--- /dev/null
+++ b/res/drawable-hdpi/ic_accept.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b60bb7e
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_overflow.png b/res/drawable-hdpi/ic_overflow.png
new file mode 100644
index 0000000..493e1f1
--- /dev/null
+++ b/res/drawable-hdpi/ic_overflow.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_return.png b/res/drawable-hdpi/ic_return.png
new file mode 100644
index 0000000..e487ee8
--- /dev/null
+++ b/res/drawable-hdpi/ic_return.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_accept.png b/res/drawable-mdpi/ic_accept.png
new file mode 100644
index 0000000..cf5fab3
--- /dev/null
+++ b/res/drawable-mdpi/ic_accept.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..cf96da3
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_overflow.png b/res/drawable-mdpi/ic_overflow.png
new file mode 100644
index 0000000..133a5c1
--- /dev/null
+++ b/res/drawable-mdpi/ic_overflow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_return.png b/res/drawable-mdpi/ic_return.png
new file mode 100644
index 0000000..ff100de
--- /dev/null
+++ b/res/drawable-mdpi/ic_return.png
Binary files differ
diff --git a/res/drawable-nodpi/bg_card.9.png b/res/drawable-nodpi/bg_card.9.png
new file mode 100644
index 0000000..d0612b1
--- /dev/null
+++ b/res/drawable-nodpi/bg_card.9.png
Binary files differ
diff --git a/res/drawable-nodpi/bg_notification.9.png b/res/drawable-nodpi/bg_notification.9.png
new file mode 100644
index 0000000..4d60ecb
--- /dev/null
+++ b/res/drawable-nodpi/bg_notification.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_accept.png b/res/drawable-xhdpi/ic_accept.png
new file mode 100644
index 0000000..b891571
--- /dev/null
+++ b/res/drawable-xhdpi/ic_accept.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6088d25
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_overflow.png b/res/drawable-xhdpi/ic_overflow.png
new file mode 100644
index 0000000..0c844f3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_overflow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_return.png b/res/drawable-xhdpi/ic_return.png
new file mode 100644
index 0000000..2f9e9bf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_return.png
Binary files differ
diff --git a/res/drawable/card.xml b/res/drawable/card.xml
new file mode 100644
index 0000000..f7b2b18
--- /dev/null
+++ b/res/drawable/card.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item android:drawable="@drawable/holo_selector" />
+ <item android:drawable="@drawable/bg_card" />
+
+</layer-list>
diff --git a/res/drawable/holo_selector.xml b/res/drawable/holo_selector.xml
new file mode 100644
index 0000000..8f52dd4
--- /dev/null
+++ b/res/drawable/holo_selector.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:exitFadeDuration="@android:integer/config_shortAnimTime">
+
+ <item
+ android:drawable="@android:color/holo_red_dark"
+ android:state_pressed="true" />
+ <item
+ android:drawable="@android:color/holo_red_light"
+ android:state_enabled="true"
+ android:state_focused="true" />
+ <item
+ android:drawable="@android:color/holo_red_light"
+ android:state_enabled="true"
+ android:state_checked="true" />
+ <item
+ android:drawable="@android:color/holo_red_light"
+ android:state_enabled="true"
+ android:state_selected="true" />
+ <item
+ android:drawable="@android:color/transparent" />
+
+</selector>
diff --git a/res/layout/album.xml b/res/layout/album.xml
new file mode 100644
index 0000000..94c87eb
--- /dev/null
+++ b/res/layout/album.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/small_margin"
+ android:orientation="vertical">
+
+ <include android:id="@+id/album_info"
+ layout="@layout/album_info"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true" />
+
+ <include android:id="@+id/album_pictures"
+ layout="@layout/album_pictures"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true" />
+
+</RelativeLayout>
diff --git a/res/layout/album_info.xml b/res/layout/album_info.xml
new file mode 100644
index 0000000..3c4cc70
--- /dev/null
+++ b/res/layout/album_info.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PhotoPhase.Album">
+
+ <ImageView android:id="@+id/album_thumbnail"
+ android:layout_width="@dimen/album_size"
+ android:layout_height="@dimen/album_size"
+ android:scaleType="fitXY"
+ android:layout_marginRight="@dimen/album_margin"
+ android:contentDescription="@null"
+ style="@style/PhotoPhase.Album.Thumbnail" />
+
+ <TextView android:id="@+id/album_selected_items"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignRight="@id/album_thumbnail"
+ android:layout_alignBottom="@id/album_thumbnail"
+ style="PhotoPhase.Notification" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/album_thumbnail"
+ android:layout_alignTop="@id/album_thumbnail"
+ android:layout_alignBottom="@id/album_thumbnail"
+ android:orientation="vertical"
+ style="@style/PhotoPhase.Album.Info">
+
+ <TextView android:id="@+id/album_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingBottom="@dimen/album_info_margin"
+ android:gravity="bottom"
+ android:ellipsize="end"
+ android:singleLine="true"
+ style="@style/PhotoPhase.TextAppearance.Primary" />
+
+ <TextView android:id="@+id/album_items"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="@dimen/album_info_margin"
+ android:gravity="top"
+ android:ellipsize="end"
+ android:singleLine="true"
+ style="@style/PhotoPhase.TextAppearance.Secondary" />
+
+ </LinearLayout>
+
+ <ImageView android:id="@+id/overflow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentBottom="true"
+ android:layout_gravity="top"
+ android:padding="@dimen/small_padding"
+ android:contentDescription="@null"
+ style="@style/PhotoPhase.Album.MenuBar.Overflow" />
+
+</org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo>
diff --git a/res/layout/album_pictures.xml b/res/layout/album_pictures.xml
new file mode 100644
index 0000000..71a1a49
--- /dev/null
+++ b/res/layout/album_pictures.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/card"
+ style="@style/PhotoPhase.Album">
+
+ <TextView android:id="@+id/album_pictures_title"
+ android:layout_width="@dimen/album_size"
+ android:layout_height="@dimen/album_title_height"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ style="@style/PhotoPhase.Album.Title" />
+
+ <LinearLayout android:id="@+id/album_menubar"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/album_size"
+ android:layout_marginLeft="@dimen/small_margin"
+ android:layout_marginRight="@dimen/small_margin"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:orientation="vertical">
+
+ <ImageView android:id="@+id/back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:contentDescription="@null"
+ style="@style/PhotoPhase.Album.MenuBar.Return" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"/>
+
+ <ImageView android:id="@+id/overflow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:contentDescription="@null"
+ style="@style/PhotoPhase.Album.MenuBar.Overflow" />
+ </LinearLayout>
+
+ <org.cyanogenmod.wallpapers.photophase.widgets.PicturesView
+ android:id="@+id/album_pictures_scroller"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="@dimen/album_title_height"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@id/album_menubar"
+ android:fillViewport="true"
+ android:scrollbars="none"
+ android:layout_weight="1">
+
+ <LinearLayout android:id="@+id/album_pictures_holder"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <!-- Pictures go here -->
+
+ </LinearLayout>
+ </org.cyanogenmod.wallpapers.photophase.widgets.PicturesView>
+
+</org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures>
diff --git a/res/layout/choose_picture_fragment.xml b/res/layout/choose_picture_fragment.xml
new file mode 100644
index 0000000..f2c8b1d
--- /dev/null
+++ b/res/layout/choose_picture_fragment.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ android:scrollbars="none">
+
+ <org.cyanogenmod.wallpapers.photophase.widgets.CardLayout
+ android:id="@+id/albums_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/small_margin" />
+
+</ScrollView>
+
diff --git a/res/layout/color_picker_pref_item.xml b/res/layout/color_picker_pref_item.xml
new file mode 100644
index 0000000..a3d4a6c
--- /dev/null
+++ b/res/layout/color_picker_pref_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="48dp"
+ android:layout_height="32dp"
+ android:background="@android:color/darker_gray">
+ <afzkl.development.mColorPicker.views.ColorPanelView
+ android:id="@+android:id/color_picker"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="1dp"
+ android:layout_gravity="center"
+ android:focusable="false"
+ android:clickable="false" />
+</LinearLayout>
+
diff --git a/res/layout/picture_item.xml b/res/layout/picture_item.xml
new file mode 100644
index 0000000..01ebdce
--- /dev/null
+++ b/res/layout/picture_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/picture"
+ android:layout_width="@dimen/picture_size"
+ android:layout_height="@dimen/picture_size"
+ style="@style/PhotoPhase.Album.Picture">
+
+ <ImageView android:id="@+id/picture_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitXY"
+ android:background="@color/album_thumbnail_color"
+ android:contentDescription="@null" />
+
+</FrameLayout>
diff --git a/res/layout/preference_widget_seekbar.xml b/res/layout/preference_widget_seekbar.xml
new file mode 100644
index 0000000..e528abc
--- /dev/null
+++ b/res/layout/preference_widget_seekbar.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:baselineAligned="false"
+ android:paddingEnd="?android:attr/scrollbarSize">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:minWidth="@dimen/preference_icon_minWidth"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:minWidth="48dp"
+ />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <TextView android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView android:id="@+android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="4" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@+android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_below="@android:id/summary"
+ android:layout_alignStart="@android:id/title"
+ android:minWidth="@dimen/preference_widget_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+ <SeekBar android:id="@+id/seekbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/summary"
+ android:layout_toEndOf="@android:id/widget_frame"
+ android:layout_alignParentEnd="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout/preference_widget_seekbar_progress.xml b/res/layout/preference_widget_seekbar_progress.xml
new file mode 100644
index 0000000..b9eead0
--- /dev/null
+++ b/res/layout/preference_widget_seekbar_progress.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:minWidth="50sp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_gravity="center" />
+
diff --git a/res/menu/album_actions.xml b/res/menu/album_actions.xml
new file mode 100644
index 0000000..a01d014
--- /dev/null
+++ b/res/menu/album_actions.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/mnu_select_album"
+ android:title="@string/mnu_select_album"
+ android:showAsAction="never" />
+ <item android:id="@+id/mnu_deselect_album"
+ android:title="@string/mnu_deselect_album"
+ android:showAsAction="never" />
+</menu>
diff --git a/res/menu/albums.xml b/res/menu/albums.xml
new file mode 100644
index 0000000..4a64b62
--- /dev/null
+++ b/res/menu/albums.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/mnu_ok"
+ android:title="@string/mnu_ok"
+ android:icon="@drawable/ic_accept"
+ android:showAsAction="always|withText" />
+</menu>
diff --git a/res/menu/main.xml b/res/menu/main.xml
new file mode 100644
index 0000000..772ec04
--- /dev/null
+++ b/res/menu/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/mnu_settings"
+ android:title="@string/mnu_settings"
+ android:showAsAction="never" />
+</menu>
diff --git a/res/menu/pictures_actions.xml b/res/menu/pictures_actions.xml
new file mode 100644
index 0000000..f1eecc5
--- /dev/null
+++ b/res/menu/pictures_actions.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/mnu_select_all"
+ android:title="@string/mnu_select_all"
+ android:showAsAction="never" />
+ <item android:id="@+id/mnu_deselect_all"
+ android:title="@string/mnu_deselect_all"
+ android:showAsAction="never" />
+ <item android:id="@+id/mnu_invert_selection"
+ android:title="@string/mnu_invert_selection"
+ android:showAsAction="never" />
+</menu>
diff --git a/res/raw/alpha_fragment_shader.glsl b/res/raw/alpha_fragment_shader.glsl
new file mode 100644
index 0000000..777f6a2
--- /dev/null
+++ b/res/raw/alpha_fragment_shader.glsl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+precision mediump float;
+
+uniform sampler2D sTexture;
+
+varying vec2 vTextureCoord;
+varying float vAlpha;
+
+void main() {
+ gl_FragColor = texture2D(sTexture, vTextureCoord);
+}
diff --git a/res/raw/alpha_vertex_shader.glsl b/res/raw/alpha_vertex_shader.glsl
new file mode 100644
index 0000000..d411ad5
--- /dev/null
+++ b/res/raw/alpha_vertex_shader.glsl
@@ -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.
+ */
+
+uniform mat4 uMVPMatrix;
+
+attribute vec4 aPosition;
+attribute vec2 aTextureCoord;
+attribute float aAlpha;
+
+varying vec2 vTextureCoord;
+varying float vAlpha;
+
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vTextureCoord = aTextureCoord;
+ vAlpha = aAlpha;
+}
diff --git a/res/raw/color_fragment_shader.glsl b/res/raw/color_fragment_shader.glsl
new file mode 100644
index 0000000..d1ad102
--- /dev/null
+++ b/res/raw/color_fragment_shader.glsl
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+precision mediump float;
+
+varying vec4 vColor;
+
+void main() {
+ gl_FragColor = vColor;
+}
diff --git a/res/raw/color_vertex_shader.glsl b/res/raw/color_vertex_shader.glsl
new file mode 100644
index 0000000..c1143e5
--- /dev/null
+++ b/res/raw/color_vertex_shader.glsl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+uniform mat4 uMVPMatrix;
+
+attribute vec4 aPosition;
+attribute vec4 aColor;
+
+varying vec4 vColor;
+
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vColor = aColor;
+}
diff --git a/res/raw/default_fragment_shader.glsl b/res/raw/default_fragment_shader.glsl
new file mode 100644
index 0000000..55cbf0b
--- /dev/null
+++ b/res/raw/default_fragment_shader.glsl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+precision mediump float;
+
+varying vec2 vTextureCoord;
+uniform sampler2D sTexture;
+
+void main() {
+ gl_FragColor = texture2D(sTexture, vTextureCoord);
+}
diff --git a/res/raw/default_vertex_shader.glsl b/res/raw/default_vertex_shader.glsl
new file mode 100644
index 0000000..dad4d5a
--- /dev/null
+++ b/res/raw/default_vertex_shader.glsl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+uniform mat4 uMVPMatrix;
+
+attribute vec4 aPosition;
+attribute vec2 aTextureCoord;
+
+varying vec2 vTextureCoord;
+
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vTextureCoord = aTextureCoord;
+}
diff --git a/res/raw/translate_fragment_shader.glsl b/res/raw/translate_fragment_shader.glsl
new file mode 100644
index 0000000..55cbf0b
--- /dev/null
+++ b/res/raw/translate_fragment_shader.glsl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+precision mediump float;
+
+varying vec2 vTextureCoord;
+uniform sampler2D sTexture;
+
+void main() {
+ gl_FragColor = texture2D(sTexture, vTextureCoord);
+}
diff --git a/res/raw/translate_vertex_shader.glsl b/res/raw/translate_vertex_shader.glsl
new file mode 100644
index 0000000..1229d64
--- /dev/null
+++ b/res/raw/translate_vertex_shader.glsl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+uniform mat4 uMVPMatrix;
+
+attribute vec4 aPosition;
+attribute vec2 aTextureCoord;
+
+varying vec2 vTextureCoord;
+
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vTextureCoord = aTextureCoord;
+}
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
new file mode 100644
index 0000000..0973569
--- /dev/null
+++ b/res/values-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="preference_widget_width">72dp</dimen>
+</resources>
diff --git a/res/values-large/dimens.xml b/res/values-large/dimens.xml
new file mode 100644
index 0000000..1c94cab
--- /dev/null
+++ b/res/values-large/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="preference_widget_width">56dp</dimen>
+</resources>
diff --git a/res/values-sw720dp-w1280dp/dimens.xml b/res/values-sw720dp-w1280dp/dimens.xml
new file mode 100644
index 0000000..c0d9720
--- /dev/null
+++ b/res/values-sw720dp-w1280dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="preference_widget_width">64dp</dimen>
+</resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..4868a22
--- /dev/null
+++ b/res/values-sw720dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="preference_icon_minWidth">56dp</dimen>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..d2bb590
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string-array name="refresh_intervals_labels" translatable="false">
+ <item>@string/refresh_intervals_disabled</item>
+ <item>@string/refresh_intervals_1h</item>
+ <item>@string/refresh_intervals_4h</item>
+ <item>@string/refresh_intervals_1d</item>
+ <item>@string/refresh_intervals_2d</item>
+ <item>@string/refresh_intervals_1w</item>
+ </string-array>
+
+ <string-array name="refresh_intervals_values" translatable="false">
+ <item>0</item>
+ <item>3600</item>
+ <item>14400</item>
+ <item>86400</item>
+ <item>172800</item>
+ <item>604800</item>
+ </string-array>
+
+ <string-array name="transitions_labels" translatable="false">
+ <item>@string/transitions_random</item>
+ <item>@string/transitions_swap</item>
+ <item>@string/transitions_fade</item>
+ <item>@string/transitions_translation</item>
+ </string-array>
+
+ <string-array name="transitions_values" translatable="false">
+ <item>0</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ </string-array>
+
+ <string-array name="effects_labels" translatable="false">
+ <item>@string/effects_none</item>
+ <item>@string/effects_random</item>
+ <item>@string/effects_black_and_white</item>
+ <item>@string/effects_sepia</item>
+ </string-array>
+
+ <string-array name="effects_values" translatable="false">
+ <item>1</item>
+ <item>0</item>
+ <item>2</item>
+ <item>3</item>
+ </string-array>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..2df0078
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <color name="wallpaper_background_color">#ff202020</color>
+ <color name="wallpaper_overlay_color">#aa202020</color>
+
+ <color name="album_thumbnail_color">#aa202020</color>
+
+ <color name="text_color">#404040</color>
+ <color name="notification_text_color">#ffffffff</color>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..71d41f7
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources>
+ <!-- Wether preserve EGL context on pause. Disable when the devices doesn't support
+ multiples EGL contexts -->
+ <bool name="config_preserve_egl_context">true</bool>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..e22bc83
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="preference_icon_minWidth">0dp</dimen>
+ <dimen name="preference_widget_width">48dp</dimen>
+
+ <dimen name="primary_text_size">18sp</dimen>
+ <dimen name="secondary_text_size">14sp</dimen>
+ <dimen name="notification_text_size">12sp</dimen>
+
+ <dimen name="album_size">96dp</dimen>
+ <dimen name="album_card_margin_bottom">8dp</dimen>
+ <dimen name="album_margin">16dp</dimen>
+ <dimen name="album_info_margin">2dp</dimen>
+
+ <dimen name="album_title_height">20dp</dimen>
+ <dimen name="album_title_margin_top">38dp</dimen>
+ <dimen name="album_title_margin_left">-38dp</dimen>
+
+ <dimen name="picture_size">80dp</dimen>
+ <dimen name="picture_vertical_margin">8dp</dimen>
+ <dimen name="picture_horizontal_margin">4dp</dimen>
+
+ <dimen name="notification_min_width">32dp</dimen>
+ <dimen name="notification_min_height">32dp</dimen>
+ <dimen name="notification_padding">3dp</dimen>
+
+ <dimen name="toolbutton_padding">5dp</dimen>
+
+ <dimen name="default_margin">16dp</dimen>
+ <dimen name="small_margin">8dp</dimen>
+ <dimen name="small_padding">5dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..1b320b6
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The application name -->
+ <string name="app_name" translatable="false">PhotoPhase</string>
+ <!-- The app description -->
+ <string name="app_description">PhotoPhase Live Wallpaper</string>
+
+ <!-- Menus -->
+ <string name="mnu_ok" translatable="false">@android:string/ok</string>
+ <string name="mnu_settings">Settings</string>
+ <string name="mnu_select_all">Select all</string>
+ <string name="mnu_deselect_all">Deselect all</string>
+ <string name="mnu_invert_selection">Invert selection</string>
+ <string name="mnu_select_album">Select album</string>
+ <string name="mnu_deselect_album">Deselect album</string>
+
+ <!-- Preferences -->
+ <string name="pref_general">General</string>
+ <string name="pref_general_summary">Access to general settings, effects and transitions</string>
+ <string name="pref_general_settings">Settings</string>
+ <string name="pref_general_settings_wallpaper_dim">Wallpaper dim</string>
+ <string name="pref_general_settings_wallpaper_dim_format">%s%%</string>
+ <string name="pref_general_settings_wallpaper_dim_summary">Set the brightness of the wallpaper for a better visualization and battery performance</string>
+ <string name="pref_general_settings_background_color">Background color</string>
+ <string name="pref_general_settings_background_color_summary">Set the background color of the wallpaper</string>
+
+ <string name="pref_general_transitions">Transitions</string>
+ <string name="pref_general_transitions_types">Types</string>
+ <string name="pref_general_transitions_types_summary">Select the types of transition effects to be applied</string>
+ <string name="pref_general_transitions_interval">Interval</string>
+ <string name="pref_general_transitions_interval_summary">Set how often are triggered the picture transitions</string>
+ <string name="pref_general_transitions_interval_format">%s sec.</string>
+ <string name="pref_general_effects">Effects</string>
+ <string name="pref_general_effects_types">Types</string>
+ <string name="pref_general_effects_types_summary">Select the types of image effects to be applied to the pictures</string>
+
+ <string name="pref_media">Media</string>
+ <string name="pref_media_summary">Set the pictures and albums to be displayed, the refresh interval and other media settings</string>
+ <string name="pref_media_settings">Settings</string>
+ <string name="pref_media_settings_refresh_interval">Refresh interval</string>
+ <string name="pref_media_settings_refresh_interval_disable">The search of new pictures is disabled</string>
+ <string name="pref_media_settings_refresh_interval_summary">Search for new pictures every <xliff:g id="interval">%1$s</xliff:g></string>
+ <string name="pref_media_settings_refresh_now">Refresh now</string>
+ <string name="pref_media_settings_refresh_now_summary">Refresh the pictures and albums database right now</string>
+ <string name="pref_media_pictures">Pictures</string>
+ <string name="pref_media_albums">Albums</string>
+ <string name="pref_media_albums_summary">Set the albums and pictures that will be displayed on the wallpaper</string>
+
+ <string name="pref_layout">Layout</string>
+ <string name="pref_layout_summary">Select how pictures are disposed on the screen</string>
+ <string name="pref_layout_disposition">Disposition</string>
+
+ <string name="pref_about">About</string>
+ <string name="pref_about_summary">PhotoPhase v<xliff:g id="version">%1$s</xliff:g>\nCopyright \u00A9 2013 The CyanogenMod Project</string>
+
+ <!-- Plurals -->
+ <plurals name="album_number_of_pictures">
+ <item quantity="zero">0 pictures</item>
+ <item quantity="one">1 picture</item>
+ <item quantity="other"><xliff:g id="number" example="7">%d</xliff:g> pictures</item>
+ </plurals>
+
+ <!-- Refresh intervals -->
+ <string name="refresh_intervals_disabled">Disabled</string>
+ <string name="refresh_intervals_1h">1 hour</string>
+ <string name="refresh_intervals_2h">2 hours</string>
+ <string name="refresh_intervals_4h">4 hours</string>
+ <string name="refresh_intervals_1d">1 day</string>
+ <string name="refresh_intervals_2d">2 days</string>
+ <string name="refresh_intervals_1w">1 week</string>
+
+ <!-- Transitions -->
+ <string name="transitions_random">Random</string>
+ <string name="transitions_swap">Swap</string>
+ <string name="transitions_fade">Fade</string>
+ <string name="transitions_translation">Translation</string>
+
+ <!-- Effects -->
+ <string name="effects_none">None</string>
+ <string name="effects_random">Random</string>
+ <string name="effects_black_and_white">Black &amp; White</string>
+ <string name="effects_sepia">Sepia</string>
+
+ <!-- ColorPickerDialog -->
+ <string name="color_picker_alpha_slider_text">Alpha</string>
+ <string name="color_picker_current_text">Current:</string>
+ <string name="color_picker_new_text">New:</string>
+ <string name="color_picker_color">Color:</string>
+
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..09b2a37
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="PhotoPhase">
+ </style>
+
+ <style name="PhotoPhase.TextAppearance">
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:textColor">@color/text_color</item>
+ </style>
+
+ <style name="PhotoPhase.TextAppearance.Primary">
+ <item name="android:textSize">@dimen/primary_text_size</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+ <style name="PhotoPhase.TextAppearance.Secondary">
+ <item name="android:textSize">@dimen/secondary_text_size</item>
+ <item name="android:textStyle">normal</item>
+ </style>
+
+ <style name="PhotoPhase.Card">
+ <item name="android:background">@drawable/card</item>
+ <item name="android:layout_marginBottom">@dimen/album_card_margin_bottom</item>
+ </style>
+
+ <style name="PhotoPhase.ToolButton">
+ <item name="android:padding">@dimen/toolbutton_padding</item>
+ </style>
+
+ <style name="PhotoPhase.Notification">
+ <item name="android:minWidth">@dimen/notification_min_width</item>
+ <item name="android:minHeight">@dimen/notification_min_height</item>
+ <item name="android:padding">@dimen/notification_padding</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/bg_notification</item>
+ <item name="android:textSize">@dimen/notification_text_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textColor">@color/notification_text_color</item>
+ </style>
+
+ <style name="PhotoPhase.Album" parent="@style/PhotoPhase.Card">
+ </style>
+
+ <style name="PhotoPhase.Album.Title">
+ <item name="android:background">@null</item>
+ <item name="android:textSize">@dimen/secondary_text_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textColor">@color/text_color</item>
+ <item name="android:rotation">-90</item>
+ <item name="android:gravity">center</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:layout_marginTop">@dimen/album_title_margin_top</item>
+ <item name="android:layout_marginLeft">@dimen/album_title_margin_left</item>
+ </style>
+
+ <style name="PhotoPhase.Album.MenuBar">
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="PhotoPhase.Album.MenuBar.Return">
+ <item name="android:src">@drawable/ic_return</item>
+ </style>
+
+ <style name="PhotoPhase.Album.MenuBar.Overflow">
+ <item name="android:src">@drawable/ic_overflow</item>
+ </style>
+
+ <style name="PhotoPhase.Album.Thumbnail">
+ <item name="android:background">@color/album_thumbnail_color</item>
+ </style>
+
+ <style name="PhotoPhase.Album.Info">
+ <item name="android:background">@null</item>
+ <item name="android:layout_marginTop">@dimen/default_margin</item>
+ <item name="android:layout_marginLeft">@dimen/small_margin</item>
+ <item name="android:layout_marginRight">@dimen/default_margin</item>
+ <item name="android:layout_marginBottom">@dimen/default_margin</item>
+ </style>
+
+ <style name="PhotoPhase.Album.Picture">
+ <item name="android:background">@drawable/card</item>
+ <item name="android:layout_marginTop">@dimen/picture_vertical_margin</item>
+ <item name="android:layout_marginBottom">@dimen/picture_vertical_margin</item>
+ <item name="android:layout_marginLeft">@dimen/picture_horizontal_margin</item>
+ <item name="android:layout_marginRight">@dimen/picture_horizontal_margin</item>
+ </style>
+
+</resources>
diff --git a/res/xml/preferences_general.xml b/res/xml/preferences_general.xml
new file mode 100644
index 0000000..5877d52
--- /dev/null
+++ b/res/xml/preferences_general.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Settings -->
+ <PreferenceCategory
+ android:key="category_settings"
+ android:title="@string/pref_general_settings">
+
+ <!-- Dim screen -->
+ <org.cyanogenmod.wallpapers.photophase.preferences.SeekBarProgressPreference
+ android:key="ui_wallpaper_dim"
+ android:title="@string/pref_general_settings_wallpaper_dim"
+ android:summary="@string/pref_general_settings_wallpaper_dim_summary"
+ android:persistent="true"
+ android:defaultValue="0" />
+
+ <org.cyanogenmod.wallpapers.photophase.widgets.ColorPickerPreference
+ android:key="ui_background_color"
+ android:title="@string/pref_general_settings_background_color"
+ android:summary="@string/pref_general_settings_background_color_summary"
+ android:persistent="true"
+ android:defaultValue="@color/wallpaper_background_color" />
+
+ </PreferenceCategory>
+
+ <!-- Transitions -->
+ <PreferenceCategory
+ android:key="category_transitions"
+ android:title="@string/pref_general_transitions">
+
+ <!-- Types -->
+ <ListPreference
+ android:key="ui_transition_types"
+ android:title="@string/pref_general_transitions_types"
+ android:summary="@string/pref_general_transitions_types_summary"
+ android:persistent="true"
+ android:entries="@array/transitions_labels"
+ android:entryValues="@array/transitions_values"
+ android:defaultValue="0" />
+
+ <!-- Transitions Interval -->
+ <org.cyanogenmod.wallpapers.photophase.preferences.SeekBarProgressPreference
+ android:key="ui_transition_interval"
+ android:title="@string/pref_general_transitions_interval"
+ android:summary="@string/pref_general_transitions_interval_summary"
+ android:persistent="true"
+ android:defaultValue="2" />
+
+ </PreferenceCategory>
+
+ <!-- Effects -->
+ <PreferenceCategory
+ android:key="category_effects"
+ android:title="@string/pref_general_effects">
+
+ <!-- Transitions -->
+ <ListPreference
+ android:key="ui_effect_types"
+ android:title="@string/pref_general_effects_types"
+ android:summary="@string/pref_general_effects_types_summary"
+ android:persistent="true"
+ android:entries="@array/effects_labels"
+ android:entryValues="@array/effects_values"
+ android:defaultValue="1" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/preferences_headers.xml b/res/xml/preferences_headers.xml
new file mode 100644
index 0000000..db45326
--- /dev/null
+++ b/res/xml/preferences_headers.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+ <header
+ android:title="@string/pref_general"
+ android:fragment="org.cyanogenmod.wallpapers.photophase.preferences.GeneralPreferenceFragment"
+ android:summary="@string/pref_general_summary" />
+ <header
+ android:title="@string/pref_media"
+ android:fragment="org.cyanogenmod.wallpapers.photophase.preferences.MediaPreferenceFragment"
+ android:summary="@string/pref_media_summary" />
+ <header
+ android:title="@string/pref_layout"
+ android:fragment="org.cyanogenmod.wallpapers.photophase.preferences.LayoutPreferenceFragment"
+ android:summary="@string/pref_layout_summary" />
+ <header
+ android:title="@string/pref_about"
+ android:summary="@null" />
+</preference-headers>
diff --git a/res/xml/preferences_layout.xml b/res/xml/preferences_layout.xml
new file mode 100644
index 0000000..6fdf438
--- /dev/null
+++ b/res/xml/preferences_layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Disposition -->
+ <PreferenceCategory
+ android:key="category_disposition"
+ android:title="@string/pref_layout_disposition">
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/preferences_media.xml b/res/xml/preferences_media.xml
new file mode 100644
index 0000000..99bd1f9
--- /dev/null
+++ b/res/xml/preferences_media.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Settings -->
+ <PreferenceCategory
+ android:key="category_refresh"
+ android:title="@string/pref_media_settings">
+
+ <!-- Refresh Interval -->
+ <ListPreference
+ android:key="ui_media_refresh_interval"
+ android:title="@string/pref_media_settings_refresh_interval"
+ android:persistent="true"
+ android:entries="@array/refresh_intervals_labels"
+ android:entryValues="@array/refresh_intervals_values"
+ android:defaultValue="0" />
+
+ <!-- Refresh now -->
+ <Preference
+ android:key="ui_media_refresh_now"
+ android:title="@string/pref_media_settings_refresh_now"
+ android:summary="@string/pref_media_settings_refresh_now_summary"
+ android:persistent="false" />
+
+ </PreferenceCategory>
+
+ <!-- Pictures -->
+ <PreferenceCategory
+ android:key="category_pictures"
+ android:title="@string/pref_media_pictures">
+
+ <!-- Refresh now -->
+ <Preference
+ android:key="ui_media_albums"
+ android:title="@string/pref_media_albums"
+ android:summary="@string/pref_media_albums_summary"
+ android:fragment="org.cyanogenmod.wallpapers.photophase.preferences.ChoosePicturesFragment"
+ android:persistent="false" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/wallpaper.xml b/res/xml/wallpaper.xml
new file mode 100644
index 0000000..006d9e9
--- /dev/null
+++ b/res/xml/wallpaper.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+ -->
+
+<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
+ android:thumbnail="@drawable/ic_launcher"
+ android:description="@string/app_description"
+ android:settingsActivity="org.cyanogenmod.wallpapers.photophase.preferences.PhotoPhasePreferences" /> \ No newline at end of file
diff --git a/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java b/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java
new file mode 100644
index 0000000..a7bcd59
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/AndroidHelper.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.view.ViewConfiguration;
+
+/**
+ * A helper class with useful methods for deal with android.
+ */
+public final class AndroidHelper {
+
+ /**
+ * Method that returns if the device is a tablet
+ *
+ * @param ctx The current context
+ * @return boolean If device is a table
+ */
+ public static boolean isTablet(Context ctx) {
+ Configuration configuration = ctx.getResources().getConfiguration();
+ return (configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
+ >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ }
+
+ /**
+ * Method that returns if an option menu has to be displayed
+ *
+ * @param ctx The current context
+ * @return boolean If an option menu has to be displayed
+ */
+ public static boolean showOptionsMenu(Context ctx) {
+ // Show overflow button?
+ return !ViewConfiguration.get(ctx).hasPermanentMenuKey();
+ }
+
+ /**
+ * This method converts dp unit to equivalent device specific value in pixels.
+ *
+ * @param ctx The current context
+ * @param dp A value in dp (Device independent pixels) unit
+ * @return float A float value to represent Pixels equivalent to dp according to device
+ */
+ public static float convertDpToPixel(Context ctx, float dp) {
+ Resources resources = ctx.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ return dp * (metrics.densityDpi / 160f);
+ }
+
+ /**
+ * This method converts device specific pixels to device independent pixels.
+ *
+ * @param ctx The current context
+ * @param px A value in px (pixels) unit
+ * @return A float value to represent dp equivalent to px value
+ */
+ public static float convertPixelsToDp(Context ctx, float px) {
+ Resources resources = ctx.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ return px / (metrics.densityDpi / 160f);
+ }
+
+ /**
+ * Calculate the dimension of the status bar
+ *
+ * @param context The current context
+ * @return The height of the status bar
+ */
+ public static int calculateStatusBarHeight(Context context) {
+ int result = 0;
+ if (!(context instanceof Activity)) {
+ int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ result = context.getResources().getDimensionPixelSize(resourceId);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java b/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java
new file mode 100644
index 0000000..efbd45d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/BitmapUtils.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A helper class for deal with Bitmaps
+ */
+public class BitmapUtils {
+
+ /**
+ * Method that decodes a bitmap
+ *
+ * @param bitmap The bitmap buffer to decode
+ * @return Bitmap The decoded bitmap
+ */
+ public static Bitmap decodeBitmap(InputStream bitmap) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferQualityOverSpeed = false;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ return BitmapFactory.decodeStream(bitmap, null, options);
+ }
+
+ /**
+ * Method that decodes a bitmap
+ *
+ * @param file The bitmap file to decode
+ * @param reqWidth The request width
+ * @param reqHeight The request height
+ * @return Bitmap The decoded bitmap
+ */
+ public static Bitmap decodeBitmap(File file, int reqWidth, int reqHeight) {
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ options.inPreferQualityOverSpeed = false;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateBitmapRatio(options, reqWidth, reqHeight);
+
+ // Decode the bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ if (bitmap == null) {
+ return null;
+ }
+
+ //Test if the file has exif format
+ return decodeExifBitmap(file, bitmap);
+ }
+
+ /**
+ * Method that decodes an Exif bitmap
+ *
+ * @param file The file to decode
+ * @param bitmap The bitmap reference
+ * @return Bitmap The decoded bitmap
+ */
+ private static Bitmap decodeExifBitmap(File file, Bitmap bitmap) {
+ try {
+ // Try to load the bitmap as a bitmap file
+ ExifInterface exif = new ExifInterface(file.getAbsolutePath());
+ int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
+ Matrix matrix = new Matrix();
+ if (orientation == 6) {
+ matrix.postRotate(90);
+ } else if (orientation == 3) {
+ matrix.postRotate(180);
+ } else if (orientation == 8) {
+ matrix.postRotate(270);
+ }
+ // Rotate the bitmap
+ return Bitmap.createBitmap(
+ bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ } catch (IOException e) {
+ // Ignore
+ }
+ return bitmap;
+ }
+
+ /**
+ * Method that calculate the bitmap size prior to decode
+ *
+ * @param options The bitmap factory options
+ * @param reqWidth The request width
+ * @param reqHeight The request height
+ * @return int The picture ratio
+ */
+ private static int calculateBitmapRatio(Options options, int reqWidth, int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+ // Calculate ratios of height and width to requested height and width
+ final int heightRatio = Math.round((float) height / (float) reqHeight);
+ final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+ // Choose the smallest ratio as inSampleSize value, this will guarantee
+ // a final image with both dimensions larger than or equal to the
+ // requested height and width.
+ inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+ }
+
+ return inSampleSize;
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/Colors.java b/src/org/cyanogenmod/wallpapers/photophase/Colors.java
new file mode 100644
index 0000000..3c3ffb9
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/Colors.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider;
+
+/**
+ * A class that defines some wallpaper GLColor colors.
+ */
+public class Colors {
+
+ private static GLColor sBackground = new GLColor(0);
+ private static GLColor sOverlay = new GLColor(0);
+
+ /**
+ * This method should be called on initialization for load the preferences color
+ */
+ public static void register(Context ctx) {
+ Resources res = ctx.getResources();
+ sBackground = PreferencesProvider.Preferences.General.getBackgroundColor();
+ sOverlay = new GLColor(res.getColor(R.color.wallpaper_overlay_color));
+ }
+
+ public static GLColor getBackground() {
+ return sBackground;
+ }
+
+ public static void setBackground(GLColor background) {
+ Colors.sBackground = background;
+ }
+
+ public static GLColor getOverlay() {
+ return sOverlay;
+ }
+
+ public static void setOverlay(GLColor overlay) {
+ Colors.sOverlay = overlay;
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java
new file mode 100644
index 0000000..9effe8c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/EGLWallpaperService.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.service.wallpaper.WallpaperService;
+import android.view.SurfaceHolder;
+
+/**
+ * An abstract class for using a {@link GLSurfaceView} inside a {@link WallpaperService}.
+ */
+public abstract class EGLWallpaperService extends WallpaperService {
+
+ /**
+ * A listener interface for the {@link GLESWallpaperService.GLESEngine} engine class.
+ */
+ public interface EGLEngineListener {
+ // No methods
+ }
+
+ /**
+ * An EGL implementation of {@link android.service.wallpaper.WallpaperService.Engine} that
+ * uses {@link GLSurfaceView}.
+ */
+ public class EGLEngine extends Engine {
+ /**
+ * The internal {@link GLSurfaceView}.
+ */
+ class WallpaperGLSurfaceView extends GLSurfaceView {
+
+ /**
+ * @see GLSurfaceView
+ */
+ WallpaperGLSurfaceView(Context context) {
+ super(context);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SurfaceHolder getHolder() {
+ return getSurfaceHolder();
+ }
+
+ /**
+ * Should be called when the {@link GLSurfaceView} is not needed anymore.
+ */
+ public void onDestroy() {
+ super.onDetachedFromWindow();
+ }
+ }
+
+ private WallpaperGLSurfaceView mGlSurfaceView;
+
+ /**
+ * Method that sets the EGL engine listener
+ *
+ * @param listener The EGL engine listener
+ */
+ public void setEGLEngineListener(EGLEngineListener listener) {
+ // No methods
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(SurfaceHolder surfaceHolder) {
+ super.onCreate(surfaceHolder);
+ mGlSurfaceView = createWallpaperGLSurfaceView();
+ }
+
+ /**
+ * Method that returns the internal {@link GLSurfaceView}
+ *
+ * @return GLSurfaceView The internal {@link GLSurfaceView}.
+ */
+ protected GLSurfaceView getGlSurfaceView() {
+ return mGlSurfaceView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mGlSurfaceView.onDestroy();
+ }
+
+ /**
+ * Override this method if a {@link GLSurfaceView} wrapper is needed, for example
+ * if you need implements some eve
+ *
+ * @return WallpaperGLSurfaceView The specialized EGL {@link GLSurfaceView}.
+ */
+ public WallpaperGLSurfaceView createWallpaperGLSurfaceView() {
+ return new WallpaperGLSurfaceView(EGLWallpaperService.this);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java b/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java
new file mode 100644
index 0000000..398785f
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/FixedQueue.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that represent a FIFO queue with a fixed size. When the queue reach the maximum defined
+ * size then extract the next element from the queue.
+ * @param <T> The type of object to hold.
+ */
+@SuppressWarnings("unchecked")
+public class FixedQueue<T> {
+
+ /**
+ * An exception thrown when the queue hasn't more elements
+ */
+ public static class EmptyQueueException extends Exception {
+ private static final long serialVersionUID = 1L;
+ }
+
+ private final Object[] mQueue;
+ private final int mSize;
+ private int mHead;
+ private int mTail;
+
+ /**
+ * Constructor of <code>FixedQueue</code>
+ *
+ * @param size The size of the queue. The limit of objects in queue. Beyond this limits
+ * the older objects are recycled.
+ */
+ public FixedQueue(int size) {
+ super();
+ this.mQueue = new Object[size];
+ this.mSize = size;
+ this.mHead = 0;
+ this.mTail = 0;
+ }
+
+ /**
+ * Method that inserts a new object to the queue.
+ *
+ * @param o The object to insert
+ * @return The object inserted (for concatenation purpose)
+ */
+ public T insert(T o) {
+ synchronized (this.mQueue) {
+ if (o == null) throw new NullPointerException();
+ if (this.mQueue[this.mHead] != null) {
+ try {
+ noSynchronizedRemove();
+ } catch (Throwable ex) {/**NON BLOCK**/}
+ }
+ this.mQueue[this.mHead] = o;
+ this.mHead++;
+ if (this.mHead >= this.mSize) {
+ this.mHead = 0;
+ }
+ return o;
+ }
+ }
+
+ /**
+ * Method that extract the first element in the queue
+ *
+ * @return The item extracted
+ * @throws EmptyQueueException If the queue hasn't element
+ */
+ public T remove() throws EmptyQueueException {
+ synchronized (this.mQueue) {
+ return noSynchronizedRemove();
+ }
+ }
+
+ /**
+ * Method that extract all the items from the queue
+ *
+ * @return The items extracted
+ * @throws EmptyQueueException If the queue hasn't element
+ */
+ public List<T> removeAll() throws EmptyQueueException {
+ synchronized (this.mQueue) {
+ if (isEmpty()) throw new EmptyQueueException();
+ List<T> l = new ArrayList<T>();
+ while (!isEmpty()) {
+ l.add(noSynchronizedRemove());
+ }
+ return l;
+ }
+ }
+
+ /**
+ * Method that retrieves the first element in the queue. This method doesn't remove the item
+ * from queue.
+ *
+ * @return The item retrieved
+ * @throws EmptyQueueException If the queue hasn't element
+ */
+ public T peek() throws EmptyQueueException {
+ synchronized (this.mQueue) {
+ T o = (T)this.mQueue[this.mTail];
+ if (o == null) throw new EmptyQueueException();
+ return o;
+ }
+
+ }
+
+ /**
+ * Method that retrieves all the items from the queue. This method doesn't remove any item
+ * from queue.
+ *
+ * @return The items retrieved
+ * @throws EmptyQueueException If the queue hasn't element
+ */
+ public List<T> peekAll() throws EmptyQueueException {
+ synchronized (this.mQueue) {
+ if (isEmpty()) throw new EmptyQueueException();
+ List<T> l = new ArrayList<T>();
+ int head = this.mHead;
+ int tail = this.mTail;
+ do {
+ l.add((T)this.mQueue[tail]);
+ tail++;
+ if (tail >= this.mSize) {
+ tail = 0;
+ }
+ } while (head != tail);
+ return l;
+ }
+ }
+
+ /**
+ * Method that returns if the queue is empty
+ *
+ * @return boolean If the queue is empty
+ */
+ public boolean isEmpty() {
+ synchronized (this.mQueue) {
+ return this.mQueue[this.mTail] == null;
+ }
+ }
+
+ /**
+ * Method that returns the number of items in the queue
+ *
+ * @return int The number of items
+ */
+ public int items() {
+ int cc = 0;
+ int head = this.mHead;
+ int tail = this.mTail;
+ do {
+ if (this.mQueue[tail] == null) {
+ return cc;
+ }
+ cc++;
+ tail++;
+ if (tail >= this.mSize) {
+ tail = 0;
+ }
+ } while (head != tail);
+ return cc;
+ }
+
+ /**
+ * Method that remove one item without synchronization (for be called from
+ * synchronized method).
+ *
+ * @return The item extracted
+ * @throws EmptyQueueException If the queue hasn't element
+ */
+ private T noSynchronizedRemove() throws EmptyQueueException {
+ T o = (T)this.mQueue[this.mTail];
+ if (o == null) throw new EmptyQueueException();
+ this.mQueue[this.mTail] = null;
+ this.mTail++;
+ if (this.mTail >= this.mSize) {
+ this.mTail = 0;
+ }
+ return o;
+ }
+
+ /**
+ * Method that returns the size of this queue.
+ *
+ * @return The size of this queue
+ */
+ public int size() {
+ return mSize;
+ }
+} \ No newline at end of file
diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java
new file mode 100644
index 0000000..fdd8b5b
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/GLES20WallpaperService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+
+
+
+/**
+ * An abstract implementation of {@link EGLWallpaperService} based on <code>GLES</code>.
+ */
+public abstract class GLES20WallpaperService extends GLESWallpaperService {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Engine onCreateEngine() {
+ return new GLES20Engine();
+ }
+
+ /**
+ * A class that extends the {@link GLESWallpaperService.GLESEngine} to add support for
+ * <code>GLES20</code>.
+ */
+ class GLES20Engine extends GLESWallpaperService.GLESEngine {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void initialize() {
+ // Request an OpenGL ES 2.x compatible context.
+ getGlSurfaceView().setEGLContextClientVersion(2);
+ getGlSurfaceView().setEGLConfigChooser(false);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java b/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java
new file mode 100644
index 0000000..7634a63
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/GLESSurfaceDispatcher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.opengl.GLSurfaceView;
+
+/**
+ * A class responsible of dispatch GLES commands inside the main GLThread.
+ */
+public class GLESSurfaceDispatcher {
+
+ private final GLSurfaceView mSurface;
+
+ /**
+ * Constructor of <code>GLESSurfaceDispatcher</code>
+ *
+ * @param v The associated GLES surface view
+ */
+ public GLESSurfaceDispatcher(GLSurfaceView v) {
+ super();
+ mSurface = v;
+ }
+
+ /**
+ * Method that dispatch a GLES commands inside the main GLThread.
+ *
+ * @param r The runnable that execute the GLES commands
+ */
+ public void dispatch(Runnable r) {
+ this.mSurface.queueEvent(r);
+ }
+
+ /**
+ * Method that set the render mode
+ *
+ * @param mode The GLES render mode
+ */
+ public void setRenderMode(int mode) {
+ if (mSurface.getRenderMode() != mode) {
+ mSurface.setRenderMode(mode);
+ }
+ }
+
+ /**
+ * Method that requests a render to the surface.
+ */
+ public void requestRender() {
+ mSurface.requestRender();
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java b/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java
new file mode 100644
index 0000000..a24e585
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/GLESUtil.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.effects.Effect;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * A helper class with some useful methods for deal with GLES.
+ */
+public final class GLESUtil {
+
+ private static final String TAG = "GLESUtil";
+
+ private static final boolean DEBUG = true;
+
+ /**
+ * A helper class to deal with OpenGL float colors.
+ */
+ public static class GLColor {
+
+ private static final float MAX_COLOR = 255.0f;
+
+ /**
+ * Red
+ */
+ public float r;
+ /**
+ * Green
+ */
+ public float g;
+ /**
+ * Blue
+ */
+ public float b;
+ /**
+ * Alpha
+ */
+ public float a;
+
+ /**
+ * Constructor of <code>GLColor</code> from ARGB
+ *
+ * @param a Alpha
+ * @param r Red
+ * @param g Green
+ * @param b Alpha
+ */
+ public GLColor(int a, int r, int g, int b) {
+ this.a = a / MAX_COLOR;
+ this.r = r / MAX_COLOR;
+ this.g = g / MAX_COLOR;
+ this.b = b / MAX_COLOR;
+ }
+
+ /**
+ * Constructor of <code>GLColor</code> from ARGB.
+ *
+ * @param argb An #AARRGGBB string
+ */
+ public GLColor(String argb) {
+ int color = Color.parseColor(argb);
+ this.a = Color.alpha(color) / MAX_COLOR;
+ this.r = Color.red(color) / MAX_COLOR;
+ this.g = Color.green(color) / MAX_COLOR;
+ this.b = Color.blue(color) / MAX_COLOR;
+ }
+
+ /**
+ * Constructor of <code>GLColor</code> from ARGB.
+ *
+ * @param argb An #AARRGGBB string
+ */
+ public GLColor(int argb) {
+ this.a = Color.alpha(argb) / MAX_COLOR;
+ this.r = Color.red(argb) / MAX_COLOR;
+ this.g = Color.green(argb) / MAX_COLOR;
+ this.b = Color.blue(argb) / MAX_COLOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Float.floatToIntBits(a);
+ result = prime * result + Float.floatToIntBits(b);
+ result = prime * result + Float.floatToIntBits(g);
+ result = prime * result + Float.floatToIntBits(r);
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GLColor other = (GLColor) obj;
+ if (Float.floatToIntBits(a) != Float.floatToIntBits(other.a))
+ return false;
+ if (Float.floatToIntBits(b) != Float.floatToIntBits(other.b))
+ return false;
+ if (Float.floatToIntBits(g) != Float.floatToIntBits(other.g))
+ return false;
+ if (Float.floatToIntBits(r) != Float.floatToIntBits(other.r))
+ return false;
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "#"+Integer.toHexString(Color.argb((int)a, (int)r, (int)g, (int)b));
+ }
+ }
+
+ /**
+ * Class that holds some information about a GLES texture
+ */
+ public static class GLESTextureInfo {
+ /**
+ * Handle of the texture
+ */
+ public int handle = 0;
+ /**
+ * The bitmap reference
+ */
+ public Bitmap bitmap;
+ }
+
+ /**
+ * Method that load a vertex shader and returns its handler identifier.
+ *
+ * @param src The source shader
+ * @return int The handler identifier of the shader
+ */
+ public static int loadVertexShader(String src) {
+ return loadShader(src, GLES20.GL_VERTEX_SHADER);
+ }
+
+ /**
+ * Method that load a fragment shader and returns its handler identifier.
+ *
+ * @param src The source shader
+ * @return int The handler identifier of the shader
+ */
+ public static int loadFragmentShader(String src) {
+ return loadShader(src, GLES20.GL_FRAGMENT_SHADER);
+ }
+
+ /**
+ * Method that load a shader and returns its handler identifier.
+ *
+ * @param src The source shader
+ * @param type The type of shader
+ * @return int The handler identifier of the shader
+ */
+ public static int loadShader(String src, int type) {
+ int[] compiled = new int[1];
+ // Create, load and compile the shader
+ int shader = GLES20.glCreateShader(type);
+ GLESUtil.glesCheckError("glCreateShader");
+ if (shader <= 0) {
+ String msg = "Cannot create a shader";
+ if (DEBUG) Log.e(TAG, msg);
+ return 0;
+ }
+ GLES20.glShaderSource(shader, src);
+ GLESUtil.glesCheckError("glShaderSource");
+ GLES20.glCompileShader(shader);
+ GLESUtil.glesCheckError("glesCheckError");
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ GLESUtil.glesCheckError("glesCheckError");
+ if (compiled[0] <= 0) {
+ String msg = "Shader compilation error trace:\n" + GLES20.glGetShaderInfoLog(shader);
+ Log.e(TAG, msg);
+ return 0;
+ }
+ return shader;
+ }
+
+ /**
+ * Method that create a new program from its shaders (vertex and fragment)
+ *
+ * @param res A resources reference
+ * @param vertexShaderId The vertex shader glsl resource
+ * @param fragmentShaderId The fragment shader glsl resource
+ * @return int The handler identifier of the program
+ */
+ public static int createProgram(Resources res, int vertexShaderId, int fragmentShaderId) {
+ return createProgram(
+ readResource(res, vertexShaderId),
+ readResource(res, fragmentShaderId));
+ }
+
+ /**
+ * Method that create a new program from its shaders (vertex and fragment)
+ *
+ * @param vertexShaderSrc The vertex shader
+ * @param fragmentShaderSrc The fragment shader
+ * @return int The handler identifier of the program.
+ */
+ public static int createProgram(String vertexShaderSrc, String fragmentShaderSrc) {
+ int vshader = 0;
+ int fshader = 0;
+ int progid = 0;
+ int[] link = new int[1];
+
+ try {
+ // Check that we have valid shaders
+ if (vertexShaderSrc == null || fragmentShaderSrc == null) {
+ return 0;
+ }
+
+ // Load the vertex and fragment shaders
+ vshader = loadVertexShader(vertexShaderSrc);
+ fshader = loadFragmentShader(fragmentShaderSrc);
+
+ // Create the programa ref
+ progid = GLES20.glCreateProgram();
+ GLESUtil.glesCheckError("glCreateProgram");
+ if (progid <= 0) {
+ String msg = "Cannot create a program";
+ Log.e(TAG, msg);
+ return 0;
+ }
+
+ // Attach the shaders
+ GLES20.glAttachShader(progid, vshader);
+ GLESUtil.glesCheckError("glAttachShader");
+ GLES20.glAttachShader(progid, fshader);
+ GLESUtil.glesCheckError("glAttachShader");
+
+ // Link the program
+ GLES20.glLinkProgram(progid);
+ GLESUtil.glesCheckError("glLinkProgram");
+
+ GLES20.glGetProgramiv(progid, GLES20.GL_LINK_STATUS, link, 0);
+ GLESUtil.glesCheckError("glGetProgramiv");
+ if (link[0] <= 0) {
+ String msg = "Program compilation error trace:\n" + GLES20.glGetProgramInfoLog(progid);
+ Log.e(TAG, msg);
+ return 0;
+ }
+
+ // Return the program
+ return progid;
+
+ } finally {
+ // Delete the shaders
+ if (vshader != 0) {
+ GLES20.glDeleteShader(vshader);
+ GLESUtil.glesCheckError("glDeleteShader");
+ }
+ if (fshader != 0) {
+ GLES20.glDeleteShader(fshader);
+ GLESUtil.glesCheckError("glDeleteShader");
+ }
+ }
+ }
+
+ /**
+ * Method that loads a texture from a file.
+ *
+ * @param file The image file
+ * @param dimensions The desired dimensions
+ * @param effect The effect to apply to the image
+ * @param recycle If the bitmap should be recycled
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(
+ File file, Rect dimensions, Effect effect, boolean recycle) {
+ Bitmap bitmap = null;
+ try {
+ // Decode and associate the bitmap (invert the desired dimensions)
+ bitmap = BitmapUtils.decodeBitmap(file, dimensions.height(), dimensions.width());
+ if (bitmap == null) {
+ String msg = "Failed to decode the file bitmap";
+ if (DEBUG) Log.e(TAG, msg);
+ return new GLESTextureInfo();
+ }
+ bitmap = effect.apply(bitmap);
+
+ if (DEBUG) Log.d(TAG, "image: " + file.getAbsolutePath());
+ return loadTexture(bitmap);
+
+ } catch (Exception e) {
+ String msg = "Failed to generate a valid texture from file: " + file.getAbsolutePath();
+ if (DEBUG) Log.e(TAG, msg, e);
+ return new GLESTextureInfo();
+
+ } finally {
+ // Recycle the bitmap
+ if (bitmap != null && recycle) {
+ bitmap.recycle();
+ }
+ }
+ }
+
+ /**
+ * Method that loads a texture from a resource context.
+ *
+ * @param ctx The current context
+ * @param resourceId The resource identifier
+ * @param effect The effect to apply to the image
+ * @param recycle If the bitmap should be recycled
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(
+ Context ctx, int resourceId, Effect effect, boolean recycle) {
+ Bitmap bitmap = null;
+ InputStream raw = null;
+ try {
+ // Decode and associate the bitmap
+ raw = ctx.getResources().openRawResource(resourceId);
+ bitmap = BitmapUtils.decodeBitmap(raw);
+ if (bitmap == null) {
+ String msg = "Failed to decode the resource bitmap";
+ if (DEBUG) Log.e(TAG, msg);
+ return new GLESTextureInfo();
+ }
+
+ if (DEBUG) Log.d(TAG, "resourceId: " + resourceId);
+ return loadTexture(bitmap);
+
+ } catch (Exception e) {
+ String msg = "Failed to generate a valid texture from resource: " + resourceId;
+ if (DEBUG) Log.e(TAG, msg, e);
+ return new GLESTextureInfo();
+
+ } finally {
+ // Close the buffer
+ try {
+ if (raw != null) {
+ raw.close();
+ }
+ } catch (IOException e) {
+ // Ignore.
+ }
+ // Recycle the bitmap
+ if (bitmap != null && recycle) {
+ bitmap.recycle();
+ }
+ }
+ }
+
+ /**
+ * Method that loads texture from a bitmap reference.
+ *
+ * @param bitmap The bitmap reference
+ * @return GLESTextureInfo The texture info
+ */
+ public static GLESTextureInfo loadTexture(Bitmap bitmap) {
+ // Check that we have a valid image name reference
+ if (bitmap == null) {
+ return new GLESTextureInfo();
+ }
+
+ int[] textureNames = new int[1];
+ GLES20.glGenTextures(1, textureNames, 0);
+ GLESUtil.glesCheckError("glGenTextures");
+ if (textureNames[0] <= 0) {
+ String msg = "Failed to generate a valid texture";
+ if (DEBUG) Log.e(TAG, msg);
+ return new GLESTextureInfo();
+ }
+
+ // Bind the texture to the name
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureNames[0]);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Set the texture properties
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLESUtil.glesCheckError("glTexParameteri");
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+ GLESUtil.glesCheckError("glTexParameteri");
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLESUtil.glesCheckError("glTexParameteri");
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ GLESUtil.glesCheckError("glTexParameteri");
+
+ // Load the texture
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
+ if (!GLES20.glIsTexture(textureNames[0])) {
+ String msg = "Failed to load a valid texture";
+ if (DEBUG) Log.e(TAG, msg);
+ return new GLESTextureInfo();
+ }
+
+ // Return the texture handle identifier and the associated info
+ GLESTextureInfo ti = new GLESTextureInfo();
+ ti.handle = textureNames[0];
+ ti.bitmap = bitmap;
+ return ti;
+ }
+
+ /**
+ * Method that checks if an GLES error is present
+ *
+ * @param func The GLES function to check
+ * @return boolean If there was an error
+ */
+ public static boolean glesCheckError(String func) {
+ int error = GLES20.glGetError();
+ if (error != 0) {
+ if (DEBUG) {
+ Log.e(TAG, "GLES20 Error (" + glesGetErrorModule() + ") (" + func + "): " +
+ GLUtils.getEGLErrorString(error));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns the line and module that generates the current error
+ *
+ * @return String The line and module
+ */
+ private static String glesGetErrorModule() {
+ try {
+ return String.valueOf(Thread.currentThread().getStackTrace()[4]);
+ } catch (IndexOutOfBoundsException ioobEx) {
+ // Ignore
+ }
+ return "";
+ }
+
+ /**
+ * Method that read a resource.
+ *
+ * @param res The resources reference
+ * @param resId The resource identifier
+ * @return String The shader source
+ * @throws IOException If an error occurs while loading the resource
+ */
+ private static String readResource(Resources res, int resId) {
+ InputStream is = res.openRawResource(resId);
+ try {
+ final int BUFFER = 1024;
+ byte[] data = new byte[BUFFER];
+ int read = 0;
+ StringBuilder sb = new StringBuilder();
+ while ((read = is.read(data, 0, BUFFER)) != -1) {
+ sb.append(new String(data, 0 ,read));
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ String msg = "Failed to read the resource " + resId;
+ if (DEBUG) Log.e(TAG, msg);
+ return null;
+ } finally {
+ try {
+ is.close();
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java
new file mode 100644
index 0000000..5c94519
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/GLESWallpaperService.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.view.SurfaceHolder;
+
+
+/**
+ * An abstract implementation of {@link EGLWallpaperService} based on <code>GLES</code>.
+ */
+public abstract class GLESWallpaperService extends EGLWallpaperService {
+
+ /**
+ * A listener interface for the {@link GLESWallpaperService.GLESEngine} engine class.
+ */
+ public interface GLESEngineListener extends EGLEngineListener {
+ /**
+ * Method invoked when the EGL surface is starting to initialize.
+ *
+ * @param view GLSurfaceView The EGL view
+ */
+ void onInitializeEGLView(GLSurfaceView view);
+
+ /**
+ * Method invoked when the EGL surface is recycled.
+ *
+ * @param view GLSurfaceView The EGL view
+ * @param renderer The renderer associated
+ */
+ void onDestroyEGLView(GLSurfaceView view, Renderer renderer);
+
+ /**
+ * Method invoked when the EGL surface was initialized.
+ *
+ * @param view GLSurfaceView The EGL view
+ */
+ void onEGLViewInitialized(GLSurfaceView view);
+
+ /**
+ * Method invoked when the EGL context is paused
+ *
+ * @param renderer The renderer associated
+ */
+ void onPause(Renderer renderer);
+
+ /**
+ * Method invoked when the EGL context is resumed
+ *
+ * @param renderer The renderer associated
+ */
+ void onResume(Renderer renderer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Engine onCreateEngine() {
+ return new GLESEngine();
+ }
+
+ /**
+ * A class that extends the {@link EGLWallpaperService.EGLEngine} to add support for
+ * <code>GLES</code>.
+ */
+ class GLESEngine extends EGLWallpaperService.EGLEngine {
+
+ private GLESEngineListener mListener = null;
+ private WallpaperGLSurfaceView mWallpaperGLSurfaceView = null;
+ private Renderer mRenderer = null;
+
+ private boolean mRendererHasBeenSet;
+ private boolean mPauseOnPreview;
+
+ /**
+ * Method that sets the EGL engine listener
+ *
+ * @param listener The EGL engine listener
+ */
+ public void setGLESEngineListener(GLESEngineListener listener) {
+ mListener = listener;
+ setEGLEngineListener(listener);
+ }
+
+ /**
+ * Method that sets the {@link GLSurfaceView} to use.
+ *
+ * @param wallpaperGLSurfaceView A {@link GLSurfaceView}
+ */
+ public void setWallpaperGLSurfaceView(WallpaperGLSurfaceView wallpaperGLSurfaceView) {
+ mWallpaperGLSurfaceView = wallpaperGLSurfaceView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(SurfaceHolder surfaceHolder) {
+ super.onCreate(surfaceHolder);
+
+ // Notify initialization
+ if (mListener != null) {
+ mListener.onInitializeEGLView(getGlSurfaceView());
+ }
+
+ // Initialize the GLES context
+ initialize();
+
+ // Set the renderer to our user-defined renderer.
+ mRenderer = getNewRenderer(getGlSurfaceView());
+ getGlSurfaceView().setRenderer(mRenderer);
+ mRendererHasBeenSet = true;
+
+ // Notify that the EGL is initialized
+ if (mListener != null) {
+ mListener.onEGLViewInitialized(getGlSurfaceView());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Notify initialization
+ if (mListener != null) {
+ mListener.onDestroyEGLView(getGlSurfaceView(), mRenderer);
+ }
+ mRenderer = null;
+ }
+
+ /**
+ * Method that initializes
+ */
+ void initialize() {
+ // Request an OpenGL ES 1.x compatible context.
+ getGlSurfaceView().setEGLContextClientVersion(1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WallpaperGLSurfaceView createWallpaperGLSurfaceView() {
+ // Check whether to use a proprietary GLSurfaceView reference or an internal one
+ if (mWallpaperGLSurfaceView != null) {
+ return mWallpaperGLSurfaceView;
+ }
+ return super.createWallpaperGLSurfaceView();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+ if (mRendererHasBeenSet) {
+ if (visible) {
+ getGlSurfaceView().onResume();
+ getGlSurfaceView().setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ mListener.onResume(mRenderer);
+ } else {
+ // Check that the user is not previewing the live wallpaper; if they are, then
+ // if they open up a settings dialog that appears over the preview, we don’t
+ // want to pause rendering
+ boolean preview = isPreview();
+ if (!preview || (preview && !mPauseOnPreview)) {
+ getGlSurfaceView().setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ getGlSurfaceView().onPause();
+ mListener.onPause(mRenderer);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method that determines if the surface view should be paused on preview mode
+ *
+ * @return boolean whether the surface view should be paused on preview mode
+ */
+ public boolean isPauseOnPreview() {
+ return mPauseOnPreview;
+ }
+
+ /**
+ * Method that sets if the surface view should be paused on preview mode
+ *
+ * @param pauseOnPreview whether the surface view should be paused on preview mode
+ */
+ public void setPauseOnPreview(boolean pauseOnPreview) {
+ this.mPauseOnPreview = pauseOnPreview;
+ }
+ }
+
+ /**
+ * Method that return a new EGL renderer.
+ *
+ * @param view The view that will be associated to the renderer
+ * @return Renderer The new EGL renderer.
+ */
+ public abstract Renderer getNewRenderer(GLSurfaceView view);
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java b/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java
new file mode 100644
index 0000000..b1b4d2d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/MediaPictureDiscoverer.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class that load asynchronously the paths of all media stored in the device.
+ * This class only seek at the specified paths
+ */
+public class MediaPictureDiscoverer {
+
+ private static final String TAG = "MediaPictureDiscoverer";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * An interface that is called when new data is ready.
+ */
+ public interface OnMediaPictureDiscoveredListener {
+ /**
+ * Called when the data is ready
+ *
+ * @param mpc The reference to the discoverer
+ * @param images All the images paths found
+ */
+ void onMediaDiscovered(MediaPictureDiscoverer mpc, File[] images);
+ }
+
+ /**
+ * The asynchronous task for query the MediaStore
+ */
+ private class AsyncDiscoverTask extends AsyncTask<Void, Void, List<File> > {
+
+ private final ContentResolver mFinalContentResolver;
+ private final OnMediaPictureDiscoveredListener mFinalCallback;
+ private final Set<String> mFilter;
+
+ /**
+ * Constructor of <code>AsyncDiscoverTask</code>
+ *
+ * @param cr The {@link ContentResolver}
+ * @param filter The filter of pictures and albums to retrieve
+ * @param cb The {@link OnMediaPictureDiscoveredListener} listener
+ */
+ public AsyncDiscoverTask(ContentResolver cr, Set<String> filter,
+ OnMediaPictureDiscoveredListener cb) {
+ super();
+ mFinalContentResolver = cr;
+ mFinalCallback = cb;
+ mFilter = filter;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<File> doInBackground(Void...params) {
+ try {
+ // The columns to read
+ final String[] projection = {MediaStore.MediaColumns.DATA};
+
+ // Query external content
+ List<File> paths =
+ getPictures(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ projection,
+ null,
+ null);
+ if (DEBUG) {
+ int cc = paths.size();
+ Log.v(TAG, "Pictures found (" + cc + "):");
+ for (int i = 0; i < cc; i++) {
+ Log.v(TAG, "\t" + paths.get(i));
+ }
+ }
+ return paths;
+
+ } catch (Exception e) {
+ Log.e(TAG, "AsyncDiscoverTask failed.", e);
+
+ // Return and empty list
+ return new ArrayList<File>();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPostExecute(List<File> result) {
+ if (mFinalCallback != null) {
+ mFinalCallback.onMediaDiscovered(
+ MediaPictureDiscoverer.this, result.toArray(new File[result.size()]));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCancelled(List<File> result) {
+ // Nothing found
+ if (mFinalCallback != null) {
+ mFinalCallback.onMediaDiscovered(
+ MediaPictureDiscoverer.this, new File[]{});
+ }
+ }
+
+ /**
+ * Method that return all the media store pictures for the content uri
+ *
+ * @param uri The content uri where to search
+ * @param projection The field data to return
+ * @param where A filter
+ * @param args The filter arguments
+ * @return List<File> The pictures found
+ */
+ private List<File> getPictures(
+ Uri uri, String[] projection, String where, String[] args) {
+ List<File> paths = new ArrayList<File>();
+ Cursor c = mFinalContentResolver.query(uri, projection, where, args, null);
+ if (c != null) {
+ try {
+ while (c.moveToNext()) {
+ // Only valid files (those i can read)
+ String p = c.getString(0);
+ if (p != null) {
+ File f = new File(p);
+ if (f.isFile() && f.canRead() && matchFilter(f)) {
+ paths.add(f);
+ }
+ }
+ }
+ } finally {
+ try {
+ c.close();
+ } catch (Exception e) {
+ // Ignore: handle exception
+ }
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * Method that checks if the picture match the preferences filter
+ *
+ * @param picture The picture to check
+ * @return boolean whether the picture match the filter
+ */
+ private boolean matchFilter(File picture) {
+ Iterator<String> it = mFilter.iterator();
+ boolean noFilter = true;
+ while (it.hasNext()) {
+ noFilter = false;
+ File filter = new File(it.next());
+ if (filter.isDirectory()) {
+ // Album match
+ if (filter.compareTo(picture.getParentFile()) == 0) {
+ return true;
+ }
+ } else {
+ // Picture match
+ if (filter.compareTo(picture) == 0) {
+ return true;
+ }
+ }
+ }
+ return noFilter;
+ }
+ }
+
+ private final Context mContext;
+ private final OnMediaPictureDiscoveredListener mCallback;
+
+ private AsyncDiscoverTask mTask;
+
+ /**
+ * Constructor of <code>MediaPictureDiscoverer</code>.
+ *
+ * @param ctx The current context
+ * @param callback A callback to returns the data when it gets ready
+ */
+ public MediaPictureDiscoverer(Context ctx, OnMediaPictureDiscoveredListener callback) {
+ super();
+ mContext = ctx;
+ mCallback = callback;
+ }
+
+ /**
+ * Method that request a new reload of the media store picture data.
+ *
+ * @param filter The filter of pictures and albums where to search images
+ */
+ public synchronized void discover(Set<String> filter) {
+ if (mTask != null && !mTask.isCancelled()) {
+ mTask.cancel(true);
+ }
+ mTask = new AsyncDiscoverTask(mContext.getContentResolver(), filter, mCallback);
+ mTask.execute();
+ }
+
+ /**
+ * Method that destroy the references of this class
+ */
+ public void recycle() {
+ if (mTask != null && !mTask.isCancelled()) {
+ mTask.cancel(true);
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java
new file mode 100644
index 0000000..26ba641
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoFrame.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+
+/**
+ * A GLES square geometry that represents one photo frame for show in the wallpaper.
+ */
+public class PhotoFrame implements TextureRequestor {
+
+ /**
+ * @hide
+ */
+ public static final int COORDS_PER_VERTER = 3;
+
+ // The photo frame is always a rectangle, so here applies 2 triangle order
+ private static final short[] VERTEX_ORDER = { 0, 1, 2, 0, 2, 3 };
+
+ // The default texture coordinates (fit to frame)
+ private static final float[] DEFAULT_TEXTURE_COORDS = {
+ 0, 0, // top left
+ 0, 1, // bottom left
+ 1, 1, // bottom right
+ 1, 0 // top right
+ };
+
+ private final TextureManager mTextureManager;
+
+ private final float mFrameWidth, mFrameHeight;
+ private final float mPictureWidth, mPictureHeight;
+ private final float[] mFrameVertex;
+ private final float[] mPictureVertex;
+
+ private FloatBuffer mPictureVertexBuffer;
+ private ShortBuffer mVertexOrderBuffer;
+ private FloatBuffer mTextureBuffer;
+
+ private GLESTextureInfo mTextureInfo;
+
+ private final GLColor mBackgroundColor;
+
+ private boolean mLoaded;
+
+ private final Object mSync = new Object();
+
+ /**
+ * Constructor of <code>PhotoFrame</code>.
+ *
+ * @param ctx The current context
+ * @param textureManager The texture manager
+ * @param frameVertex A 4 dimension array with the coordinates per vertex plus padding
+ * @param pictureVertex A 4 dimension array with the coordinates per vertex
+ * @param color Background color
+ */
+ public PhotoFrame(Context ctx, TextureManager textureManager,
+ float[] frameVertex, float[] pictureVertex, GLColor color) {
+ super();
+ mLoaded = false;
+ mBackgroundColor = color;
+ mTextureManager = textureManager;
+
+ // Save dimensions
+ mFrameVertex = frameVertex;
+ mFrameWidth = frameVertex[9] - frameVertex[0];
+ mFrameHeight = frameVertex[4] - frameVertex[1];
+ mPictureVertex = pictureVertex;
+ mPictureWidth = pictureVertex[9] - pictureVertex[0];
+ mPictureHeight = pictureVertex[4] - pictureVertex[1];
+
+ // Initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(pictureVertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ mPictureVertexBuffer = bb.asFloatBuffer();
+ mPictureVertexBuffer.put(pictureVertex);
+ mPictureVertexBuffer.position(0);
+
+ // Initialize vertex byte buffer for shape coordinates order
+ bb = ByteBuffer.allocateDirect(VERTEX_ORDER.length * 2); // (# of coordinate values * 2 bytes per short)
+ bb.order(ByteOrder.nativeOrder());
+ mVertexOrderBuffer = bb.asShortBuffer();
+ mVertexOrderBuffer.put(VERTEX_ORDER);
+ mVertexOrderBuffer.position(0);
+
+ // Load the texture
+ mTextureInfo = null;
+
+ // Request a new image for this frame
+ textureManager.request(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTextureHandle(GLESTextureInfo ti) {
+ // If the picture is invalid request a new texture
+ if (ti == null || ti.handle <= 0) {
+ mTextureManager.request(this);
+ return;
+ }
+
+ // Full frame picture
+ // TODO Apply some type of ratio correction to the texture coordinates
+ setTextureHandle(ti, DEFAULT_TEXTURE_COORDS);
+ mLoaded = true;
+ }
+
+ /**
+ * Internal method that expose the texture coordinates to set
+ *
+ * @param ti The texture info
+ * @param textureCoords The texture coordinates
+ */
+ private void setTextureHandle(GLESTextureInfo ti, final float[] textureCoords) {
+ // Recycle the previous handle
+ if (mTextureInfo != null && mTextureInfo.handle != 0) {
+ int[] textures = new int[]{mTextureInfo.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+
+ // Initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(textureCoords.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ synchronized (mSync) {
+ // Synchronize buffer swap
+ mTextureBuffer = bb.asFloatBuffer();
+ mTextureBuffer.put(textureCoords);
+ mTextureBuffer.position(0);
+ }
+ mTextureInfo = ti;
+ }
+
+ /**
+ * Method that returns the frame vertex
+ *
+ * @return float[] The frame vertex
+ */
+ public float[] getFrameVertex() {
+ return mFrameVertex;
+ }
+
+ /**
+ * Method that returns the picture vertex
+ *
+ * @return float[] The picture vertex
+ */
+ public float[] getPictureVertex() {
+ return mPictureVertex;
+ }
+
+ /**
+ * Method that returns the picture vertex buffer
+ *
+ * @return FloatBuffer The picture vertex buffer
+ */
+ public FloatBuffer getPictureVertexBuffer() {
+ return mPictureVertexBuffer;
+ }
+
+ /**
+ * Method that returns the vertex order buffer
+ *
+ * @return ShortBuffer The vertex order buffer
+ */
+ public ShortBuffer getVertexOrderBuffer() {
+ return mVertexOrderBuffer;
+ }
+
+ /**
+ * Method that returns the texture buffer
+ *
+ * @return FloatBuffer The texture buffer
+ */
+ public FloatBuffer getTextureBuffer() {
+ synchronized (mSync) {
+ return mTextureBuffer;
+ }
+ }
+
+ /**
+ * Method that returns the texture handle
+ *
+ * @return int The texture handle
+ */
+ public int getTextureHandle() {
+ if (mTextureInfo != null) {
+ return mTextureInfo.handle;
+ }
+ return -1;
+ }
+
+ /**
+ * Method that returns the bitmap handle
+ *
+ * @return int The bitmap handle
+ */
+ public Bitmap getTextureBitmap() {
+ if (mTextureInfo != null) {
+ return mTextureInfo.bitmap;
+ }
+ return null;
+ }
+
+ /**
+ * Method that returns the frame width
+ *
+ * @return float The frame width
+ */
+ public float getFrameWidth() {
+ return mFrameWidth;
+ }
+
+ /**
+ * Method that returns the frame height
+ *
+ * @return float The frame height
+ */
+ public float getFrameHeight() {
+ return mFrameHeight;
+ }
+
+ /**
+ * Method that returns the picture width
+ *
+ * @return float The picture width
+ */
+ public float getPictureWidth() {
+ return mPictureWidth;
+ }
+
+ /**
+ * Method that returns the picture height
+ *
+ * @return float The picture height
+ */
+ public float getPictureHeight() {
+ return mPictureHeight;
+ }
+
+ /**
+ * Method that returns the background color of the frame
+ *
+ * @return GLColor The background color
+ */
+ public GLColor getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ /**
+ * Method that returns if the frame is loaded (has its picture loaded)
+ *
+ * @return boolean If the frame is loaded (has its picture loaded)
+ */
+ public boolean isLoaded() {
+ return mLoaded;
+ }
+
+ /**
+ * Request a recycle of the references of the object
+ */
+ public void recycle() {
+ if (mTextureInfo != null && mTextureInfo.handle != 0) {
+ int[] textures = new int[]{mTextureInfo.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+ mTextureInfo = null;
+
+ if (mPictureVertexBuffer != null) {
+ mPictureVertexBuffer.clear();
+ }
+ if (mVertexOrderBuffer != null) {
+ mVertexOrderBuffer.clear();
+ }
+ if (mTextureBuffer != null) {
+ mTextureBuffer.clear();
+ }
+ mPictureVertexBuffer = null;
+ mVertexOrderBuffer = null;
+ mTextureBuffer = null;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java
new file mode 100644
index 0000000..6ea835c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseActivity.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
+import android.view.WindowManager;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.PhotoPhasePreferences;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider;
+
+/**
+ * A testing activity to simulate the PhotoPhase Live Wallpaper inside an GLES activity.
+ */
+public class PhotoPhaseActivity extends Activity {
+
+ private static final String TAG = "PhotoPhaseActivity";
+
+ private static final boolean DEBUG = true;
+
+ private GLSurfaceView mGLSurfaceView;
+ private PhotoPhaseRenderer mRenderer;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ boolean preserveEglCtx = getResources().getBoolean(R.bool.config_preserve_egl_context);
+
+ // Instance the application
+ PreferencesProvider.reload(this);
+ Colors.register(this);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ // Configure the EGL context
+ mGLSurfaceView = new GLSurfaceView(getApplicationContext());
+ mGLSurfaceView.setEGLContextClientVersion(2);
+ mGLSurfaceView.setEGLConfigChooser(false);
+ mRenderer = new PhotoPhaseRenderer(this, new GLESSurfaceDispatcher(mGLSurfaceView));
+ mGLSurfaceView.setRenderer(mRenderer);
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ mGLSurfaceView.setPreserveEGLContextOnPause(preserveEglCtx);
+ setContentView(mGLSurfaceView);
+
+ mRenderer.onCreate();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ mRenderer.onDestroy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (DEBUG) Log.d(TAG, "onResume");
+ mGLSurfaceView.onResume();
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ if (mRenderer != null) {
+ mRenderer.onResume();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (DEBUG) Log.d(TAG, "onPause");
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ mRenderer.onPause();
+ mGLSurfaceView.onPause();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mnu_settings:
+ Intent settings = new Intent(this, PhotoPhasePreferences.class);
+ startActivity(settings);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java
new file mode 100644
index 0000000..dff9887
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.opengl.GLES20;
+import android.opengl.GLException;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.os.Handler;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider;
+import org.cyanogenmod.wallpapers.photophase.shapes.ColorShape;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transition;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * The EGL renderer of PhotoPhase Live Wallpaper.
+ */
+public class PhotoPhaseRenderer implements GLSurfaceView.Renderer {
+
+ private static final String TAG = "PhotoPhaseRenderer";
+
+ private static final boolean DEBUG = true;
+
+ private final long mInstance;
+ private static long sInstances;
+
+ final Context mContext;
+ private final Handler mHandler;
+ final GLESSurfaceDispatcher mDispatcher;
+ TextureManager mTextureManager;
+
+ PhotoPhaseWallpaperWorld mWorld;
+ ColorShape mOverlay;
+
+ long mLastRunningTransition;
+
+ int mWidth = -1;
+ int mHeight = -1;
+ int mMeasuredHeight = -1;
+
+ private final float[] mMVPMatrix = new float[16];
+ private final float[] mProjMatrix = new float[16];
+ private final float[] mVMatrix = new float[16];
+
+ private final Object mDrawing = new Object();
+
+ final Object mMediaSync = new Object();
+ private PendingIntent mMediaScanIntent;
+
+ private final BroadcastReceiver mSettingsChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Check what flags are been requested
+ boolean recreateWorld = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, false);
+ boolean redraw = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, false);
+ boolean emptyTextureQueue = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, false);
+ boolean mediaReload = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, false);
+ boolean mediaIntervalChanged = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_INTERVAL_CHANGED, false);
+ if (recreateWorld) {
+ // Recreate the wallpaper world
+ try {
+ mWorld.recreateWorld(mWidth, mMeasuredHeight);
+ } catch (GLException e) {
+ Log.e(TAG, "Cannot recreate the wallpaper world.", e);
+ }
+ }
+ if (redraw) {
+ mDispatcher.requestRender();
+ }
+ if (emptyTextureQueue) {
+ mTextureManager.emptyTextureQueue(true);
+ }
+ if (mediaReload) {
+ synchronized (mMediaSync) {
+ mTextureManager.reloadMedia();
+ scheduleOrCancelMediaScan();
+ }
+ }
+ if (mediaIntervalChanged) {
+ scheduleOrCancelMediaScan();
+ }
+ }
+ };
+
+ private final Runnable mTransitionThread = new Runnable() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ // Run in GLES's thread
+ mDispatcher.dispatch(new Runnable() {
+ @Override
+ public void run() {
+ // Select a new transition
+ mWorld.selectTransition();
+ mLastRunningTransition = System.currentTimeMillis();
+
+ // Now force continuously render while transition is applied
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+ });
+ }
+ };
+
+ /**
+ * Constructor of <code>PhotoPhaseRenderer<code>
+ *
+ * @param ctx The current context
+ * @param dispatcher The GLES dispatcher
+ */
+ public PhotoPhaseRenderer(Context ctx, GLESSurfaceDispatcher dispatcher) {
+ super();
+ mContext = ctx;
+ mHandler = new Handler();
+ mDispatcher = dispatcher;
+ mInstance = sInstances;
+ sInstances++;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (mInstance ^ (mInstance >>> 32));
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PhotoPhaseRenderer other = (PhotoPhaseRenderer) obj;
+ if (mInstance != other.mInstance)
+ return false;
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "PhotoPhaseRenderer [instance: " + mInstance + "]";
+ }
+
+ /**
+ * Method called when when renderer is created
+ */
+ public void onCreate() {
+ if (DEBUG) Log.d(TAG, "onCreate [" + mInstance + "]");
+ // Register a receiver to listen for media reload request
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ mContext.registerReceiver(mSettingsChangedReceiver, filter);
+
+ // Check whether the media scan is active
+ int interval = PreferencesProvider.Preferences.Media.getRefreshFrecuency();
+ if (interval != PreferencesProvider.Preferences.Media.MEDIA_RELOAD_DISABLED) {
+ // Schedule a media scan
+ scheduleMediaScan(interval);
+ }
+ }
+
+ /**
+ * Method called when when renderer is destroyed
+ */
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy [" + mInstance + "]");
+ // Register a receiver to listen for media reload request
+ mContext.unregisterReceiver(mSettingsChangedReceiver);
+ recycle();
+ mWidth = -1;
+ mHeight = -1;
+ mMeasuredHeight = -1;
+ }
+
+ /**
+ * Method called when the renderer should be paused
+ */
+ public void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause [" + mInstance + "]");
+ mHandler.removeCallbacks(mTransitionThread);
+ if (mTextureManager != null) {
+ mTextureManager.setPause(true);
+ }
+ }
+
+ /**
+ * Method called when the renderer should be resumed
+ */
+ public void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume [" + mInstance + "]");
+ if (mTextureManager != null) {
+ mTextureManager.setPause(false);
+ }
+ }
+
+ void scheduleOrCancelMediaScan() {
+ int interval = PreferencesProvider.Preferences.Media.getRefreshFrecuency();
+ if (interval != PreferencesProvider.Preferences.Media.MEDIA_RELOAD_DISABLED) {
+ scheduleMediaScan(interval);
+ } else {
+ cancelMediaScan();
+ }
+ }
+
+ /**
+ * Method that schedules a new media scan
+ *
+ * @param interval The new interval
+ */
+ private void scheduleMediaScan(int interval) {
+ AlarmManager am = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+
+ Intent i = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ i.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE);
+ mMediaScanIntent = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ long milliseconds = PreferencesProvider.Preferences.Media.getRefreshFrecuency() * 1000L;
+ am.set(AlarmManager.RTC, System.currentTimeMillis() + milliseconds, mMediaScanIntent);
+ }
+
+ /**
+ * Method that cancels a pending media scan
+ */
+ private void cancelMediaScan() {
+ if (mMediaScanIntent != null) {
+ AlarmManager am = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ am.cancel(mMediaScanIntent);
+ mMediaScanIntent = null;
+ }
+ }
+
+ /**
+ * Method that destroy all the internal references
+ */
+ private void recycle() {
+ if (DEBUG) Log.d(TAG, "recycle [" + mInstance + "]");
+ synchronized (mDrawing) {
+ // Remove any pending handle
+ if (mHandler != null && mTransitionThread != null) {
+ mHandler.removeCallbacks(mTransitionThread);
+ }
+
+ // Delete the world
+ if (mWorld != null) mWorld.recycle();
+ if (mTextureManager != null) mTextureManager.recycle();
+ if (mOverlay != null) mOverlay.recycle();
+ mWorld = null;
+ mTextureManager = null;
+ mOverlay = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ if (DEBUG) Log.d(TAG, "onSurfaceCreated [" + mInstance + "]");
+
+ mWidth = -1;
+ mHeight = -1;
+ mMeasuredHeight = -1;
+
+ // We have a 2d (fake) scenario, disable all unnecessary tests. Deep are
+ // necessary for some 3d effects
+ GLES20.glDisable(GL10.GL_DITHER);
+ GLESUtil.glesCheckError("glDisable");
+ GLES20.glDisable(GL10.GL_CULL_FACE);
+ GLESUtil.glesCheckError("glDisable");
+ GLES20.glEnable(GL10.GL_DEPTH_TEST);
+ GLESUtil.glesCheckError("glEnable");
+ GLES20.glDepthMask(false);
+ GLESUtil.glesCheckError("glDepthMask");
+ GLES20.glDepthFunc(GLES20.GL_LEQUAL);
+ GLESUtil.glesCheckError("glDepthFunc");
+
+ // Create the texture manager and recycle the old one
+ if (mTextureManager == null) {
+ // Precalculate the window size for the TextureManager. In onSurfaceChanged
+ // the best fixed size will be set. The disposition size is simple for a better
+ // performance of the internal arrays
+ final Configuration conf = mContext.getResources().getConfiguration();
+ int w = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp);
+ int h = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp);
+ Rect dimensions = new Rect(0, 0, w / 2, h / 2);
+ int cc = PreferencesProvider.Preferences.Layout.getDisposition().size();
+
+ recycle();
+ mTextureManager = new TextureManager(mContext, mDispatcher, cc, dimensions);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ if (DEBUG) Log.d(TAG, "onSurfaceChanged [" + mInstance + "," + width + "x" + height + "]");
+
+ // Check if the size was changed
+ if (mWidth == width && mHeight == height) {
+ return;
+ }
+
+ // Save the width and height to avoid recreate the world
+ mWidth = width;
+ mHeight = height;
+ mMeasuredHeight = mHeight - AndroidHelper.calculateStatusBarHeight(mContext);
+
+ // Calculate a better fixed size for the pictures
+ Rect dimensions = new Rect(0, 0, width / 2, mMeasuredHeight / 2);
+ mTextureManager.setDimensions(dimensions);
+ mTextureManager.setPause(false);
+
+ // Create the wallpaper
+ mWorld = new PhotoPhaseWallpaperWorld(mContext, mTextureManager);
+
+ // Create all other shapes
+ final float[] vertex = {
+ -1.0f, 1.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f
+ };
+ mOverlay = new ColorShape(mContext, vertex, Colors.getOverlay());
+
+ // Set the viewport and the fustrum to use
+ GLES20.glViewport(0, 0, width, mMeasuredHeight);
+ GLESUtil.glesCheckError("glViewport");
+ Matrix.frustumM(mProjMatrix, 0, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 2.0f);
+
+ // Recreate the wallpaper world
+ try {
+ mWorld.recreateWorld(width, mMeasuredHeight);
+ } catch (GLException e) {
+ Log.e(TAG, "Cannot recreate the wallpaper world.", e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDrawFrame(GL10 glUnused) {
+ synchronized (mDrawing) {
+ // Draw the background
+ drawBackground();
+
+ if (mWorld != null) {
+ // Set the projection, view and model
+ Matrix.setLookAtM(mVMatrix, 0, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
+ Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
+
+ // Now draw the world (all the photo frames with effects)
+ mWorld.draw(mMVPMatrix);
+
+ // Check if we have some pending transition or transition has exceed its timeout
+ if (!mWorld.hasRunningTransition() || isTransitionTimeout()) {
+ mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+
+ // Now start a delayed thread to generate the next effect
+ mHandler.removeCallbacks(mTransitionThread);
+ mWorld.deselectTransition(mMVPMatrix);
+ mLastRunningTransition = 0;
+ mHandler.postDelayed(mTransitionThread,
+ PreferencesProvider.Preferences.General.Transitions.getTransitionInterval());
+ }
+ }
+
+ // Draw the overlay
+ drawOverlay();
+ }
+ }
+
+ /**
+ * Check whether the transition has exceed the timeout
+ *
+ * @return boolean if the transition has exceed the timeout
+ */
+ private boolean isTransitionTimeout() {
+ long now = System.currentTimeMillis();
+ long diff = now - mLastRunningTransition;
+ return mLastRunningTransition != 0 && diff > Transition.MAX_TRANSTION_TIME;
+ }
+
+ /**
+ * Method that draws the background of the wallpaper
+ */
+ private static void drawBackground() {
+ GLColor bg = Colors.getBackground();
+ GLES20.glClearColor(bg.r, bg.g, bg.b, bg.a);
+ GLESUtil.glesCheckError("glClearColor");
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
+ GLESUtil.glesCheckError("glClear");
+ }
+
+ /**
+ * Method that draws the overlay of the wallpaper
+ */
+ private void drawOverlay() {
+ if (mOverlay != null) {
+ mOverlay.setAlpha(PreferencesProvider.Preferences.General.getWallpaperDim() / 100.0f);
+ mOverlay.draw(mMVPMatrix);
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java
new file mode 100644
index 0000000..01f81c7
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.GLESWallpaperService.GLESEngineListener;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The PhotoPhase Live Wallpaper service.
+ */
+public class PhotoPhaseWallpaper
+ extends GLES20WallpaperService implements GLESEngineListener {
+
+ private static final String TAG = "PhotoPhaseWallpaper";
+
+ private static final boolean DEBUG = true;
+
+ private List<PhotoPhaseRenderer> mRenderers;
+ private PhotoPhaseWallpaperEngine mEngine;
+
+ private boolean mPreserveEGLContext;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate();
+
+ // Load the configuration
+ mPreserveEGLContext = getResources().getBoolean(R.bool.config_preserve_egl_context);
+ mRenderers = new ArrayList<PhotoPhaseRenderer>();
+
+ // Instance the application
+ PreferencesProvider.reload(this);
+ Colors.register(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ for (PhotoPhaseRenderer renderer : mRenderers) {
+ renderer.onDestroy();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Engine onCreateEngine() {
+ mEngine = new PhotoPhaseWallpaperEngine(this);
+ return mEngine;
+ }
+
+ /**
+ * A wallpaper engine implementation using GLES.
+ */
+ class PhotoPhaseWallpaperEngine extends GLES20WallpaperService.GLES20Engine {
+ /**
+ * Constructor of <code>PhotoPhaseWallpaperEngine<code>
+ *
+ * @param wallpaper The wallpaper service reference
+ */
+ PhotoPhaseWallpaperEngine(PhotoPhaseWallpaper wallpaper) {
+ super();
+ setOffsetNotificationsEnabled(true);
+ setTouchEventsEnabled(false);
+ setGLESEngineListener(wallpaper);
+ setWallpaperGLSurfaceView(new PhotoPhaseWallpaperGLSurfaceView(wallpaper));
+ setPauseOnPreview(true);
+ }
+
+ /**
+ * Out custom GLSurfaceView class to let us access all events stuff.
+ */
+ class PhotoPhaseWallpaperGLSurfaceView extends WallpaperGLSurfaceView {
+ /**
+ * The constructor of <code>PhotoPhaseWallpaperGLSurfaceView</code>.
+ *
+ * @param context The current context
+ */
+ public PhotoPhaseWallpaperGLSurfaceView(Context context) {
+ super(context);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onInitializeEGLView(GLSurfaceView view) {
+ if (DEBUG) Log.d(TAG, "onInitializeEGLView");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroyEGLView(GLSurfaceView view, Renderer renderer) {
+ if (DEBUG) Log.d(TAG, "onDestroyEGLView" + renderer);
+ mRenderers.remove(renderer);
+ ((PhotoPhaseRenderer)renderer).onPause();
+ ((PhotoPhaseRenderer)renderer).onDestroy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onEGLViewInitialized(GLSurfaceView view) {
+ view.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ view.setPreserveEGLContextOnPause(mPreserveEGLContext);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPause(Renderer renderer) {
+ if (DEBUG) Log.d(TAG, "onPause: " + renderer);
+ ((PhotoPhaseRenderer)renderer).onPause();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onResume(Renderer renderer) {
+ if (DEBUG) Log.d(TAG, "onResume: " + renderer);
+ ((PhotoPhaseRenderer)renderer).onResume();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Renderer getNewRenderer(GLSurfaceView view) {
+ if (DEBUG) Log.d(TAG, "getNewRenderer()");
+ PhotoPhaseRenderer renderer = new PhotoPhaseRenderer(this, new GLESSurfaceDispatcher(view));
+ renderer.onCreate();
+ mRenderers.add(renderer);
+ if (DEBUG) Log.d(TAG, "renderer" + renderer);
+ return renderer;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java
new file mode 100644
index 0000000..395cdbb
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseWallpaperWorld.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.Disposition;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transition;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that represents the wallpapers with all its photo frames.
+ */
+public class PhotoPhaseWallpaperWorld {
+
+ private static final String TAG = "PhotoPhaseWallpaperWorld";
+
+ private static final boolean DEBUG = false;
+
+ // The frame padding
+ private static final int PHOTO_FRAME_PADDING = 2;
+
+ private final Context mContext;
+ private final TextureManager mTextureManager;
+
+ private List<PhotoFrame> mPhotoFrames;
+ private List<Transition> mTransitions;
+ private final List<Transition> mUnusedTransitions;
+
+ private List<Integer> mTransitionsQueue;
+ private List<Integer> mUsedTransitionsQueue;
+ private int mCurrent;
+
+ private boolean mRecycled;
+
+ /**
+ * Constructor <code>PhotoPhaseWallpaperWorld</code>
+ *
+ * @param ctx The current context
+ * @param textureManager The texture manager
+ */
+ public PhotoPhaseWallpaperWorld(
+ Context ctx, TextureManager textureManager) {
+ super();
+ mContext = ctx;
+ mTextureManager = textureManager;
+ mCurrent = -1;
+ mUnusedTransitions = new ArrayList<Transition>();
+ mRecycled = false;
+ }
+
+ /**
+ * Method that returns an unused transition for the type of transition
+ *
+ * @param type The type of transition
+ * @return Transition The unused transition
+ */
+ private Transition getUnusedTransition(TRANSITIONS type) {
+ for (Transition transition : mUnusedTransitions) {
+ if (transition.getType().compareTo(type) == 0) {
+ mUnusedTransitions.remove(transition);
+ return transition;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method that returns or creates a transition for the type of transition
+ *
+ * @param type The type of transition
+ * @param frame The frame which the effect will be applied to
+ * @return Transition The unused transition
+ */
+ private Transition getOrCreateTransition(TRANSITIONS type, PhotoFrame frame) {
+ Transition transition = getUnusedTransition(type);
+ if (transition == null) {
+ transition = Transitions.createTransition(mContext, mTextureManager, type, frame);
+ }
+ transition.reset();
+ return transition;
+ }
+
+ /**
+ * Method that selects a transition and assign it to a photo frame.
+ */
+ public void selectTransition() {
+ // Ensure queue
+ if (mTransitionsQueue.isEmpty()) {
+ mTransitionsQueue.addAll(mUsedTransitionsQueue);
+ mUsedTransitionsQueue.clear();
+ }
+
+ // Get a random frame to which apply the transition
+ int r = 0 + (int)(Math.random() * (((mTransitionsQueue.size()-1) - 0) + 1));
+ int pos = mTransitionsQueue.remove(r).intValue();
+ mUsedTransitionsQueue.add(Integer.valueOf(pos));
+
+ // Create or use a transition
+ PhotoFrame frame = mPhotoFrames.get(pos);
+ Transition transition = null;
+ boolean isSelectable = false;
+ while (transition == null || !isSelectable) {
+ boolean isRandom = Preferences.General.Transitions.getTransitionTypes() == TRANSITIONS.RANDOM.ordinal();
+ TRANSITIONS type = Transitions.getNextTypeOfTransition(frame);
+ transition = getOrCreateTransition(type, frame);
+ isSelectable = transition.isSelectable(frame);
+ if (!isSelectable) {
+ mUnusedTransitions.add(transition);
+ if (!isRandom) {
+ // If is not possible to select a valid transition then select a swap
+ // transition (this one doesn't relies on any selection)
+ transition = getOrCreateTransition(TRANSITIONS.SWAP, frame);
+ isSelectable = true;
+ }
+ }
+ }
+ mTransitions.set(pos, transition);
+ transition.select(frame);
+ mCurrent = pos;
+ }
+
+ /**
+ * Method that deselect the current transition.
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ public void deselectTransition(float[] matrix) {
+ if (mCurrent != -1) {
+ // Retrieve the finally target
+ Transition currentTransition = mTransitions.get(mCurrent);
+ PhotoFrame currentTarget = currentTransition.getTarget();
+ PhotoFrame finalTarget = currentTransition.getTransitionTarget();
+ mUnusedTransitions.add(currentTransition);
+
+ if (finalTarget != null) {
+ Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION, finalTarget);
+ mTransitions.set(mCurrent, transition);
+
+ currentTarget.recycle();
+ mPhotoFrames.set(mCurrent, finalTarget);
+ transition.select(finalTarget);
+
+ // Draw the transition once
+ transition.apply(matrix);
+ }
+ mCurrent = -1;
+ }
+ }
+
+ /**
+ * Method that removes all internal references.
+ */
+ public void recycle() {
+ // Destroy the previous world
+ if (mTransitions != null) {
+ int cc = mTransitions.size()-1;
+ for (int i = cc; i >= 0; i--) {
+ Transition transition = mTransitions.get(i);
+ transition.recycle();
+ mTransitions.remove(i);
+ }
+ }
+ if (mUnusedTransitions != null) {
+ int cc = mUnusedTransitions.size()-1;
+ for (int i = cc; i >= 0; i--) {
+ Transition transition = mUnusedTransitions.get(i);
+ transition.recycle();
+ mUnusedTransitions.remove(i);
+ }
+ }
+ if (mPhotoFrames != null) {
+ int cc = mPhotoFrames.size()-1;
+ for (int i = cc; i >= 0; i--) {
+ PhotoFrame frame = mPhotoFrames.get(i);
+ mTextureManager.releaseBitmap(frame.getTextureBitmap());
+ frame.recycle();
+ mPhotoFrames.remove(i);
+ }
+ }
+ mTransitionsQueue.clear();
+ mUsedTransitionsQueue.clear();
+ mRecycled = true;
+ }
+
+ /**
+ * Method that returns if there are any transition running in the world.
+ *
+ * @return boolean If there are any transition running in the world
+ */
+ public boolean hasRunningTransition() {
+ if (mTransitions != null) {
+ for (Transition transition : mTransitions) {
+ if (transition.isRunning()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that creates and fills the world with {@link PhotoFrame} objects.
+ *
+ * @param w The new width dimension
+ * @param h The new height dimension
+ */
+ public synchronized void recreateWorld(int w, int h) {
+ if (DEBUG) Log.d(TAG, "Recreating the world. New surface: " + w + "x" + h);
+
+ // Destroy the previous world
+ if (mRecycled) {
+ recycle();
+ mRecycled = false;
+ }
+
+ // Calculate the new world
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ boolean portrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+ int cols = portrait ? Preferences.Layout.getCols() : Preferences.Layout.getRows();
+ int rows = portrait ? Preferences.Layout.getRows() : Preferences.Layout.getCols();
+ float cellw = 2.0f / cols;
+ float cellh = 2.0f / rows;
+ List<Disposition> dispositions = Preferences.Layout.getDisposition();
+ if (DEBUG) Log.d(TAG,
+ "Dispositions: " + dispositions.size() + " | " + String.valueOf(dispositions));
+ mPhotoFrames = new ArrayList<PhotoFrame>(dispositions.size());
+ mTransitions = new ArrayList<Transition>(dispositions.size());
+ mTransitionsQueue = new ArrayList<Integer>(dispositions.size());
+ mUsedTransitionsQueue = new ArrayList<Integer>(dispositions.size());
+ int i = 0;
+ for (Disposition disposition : dispositions) {
+ // Create the photo frame
+ float[] frameVertices = getVerticesFromDisposition(disposition, portrait, cellw, cellh);
+ float[] pictureVertices = getFramePadding(frameVertices, portrait ? w : h, portrait ? h : w);
+ PhotoFrame frame =
+ new PhotoFrame(
+ mContext,
+ mTextureManager,
+ frameVertices,
+ pictureVertices,
+ Colors.getBackground());
+ mPhotoFrames.add(frame);
+
+ // Assign a null transition to the photo frame
+ Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION, frame);
+ transition.select(frame);
+ mTransitions.add(transition);
+
+ mTransitionsQueue.add(Integer.valueOf(i));
+ i++;
+ }
+ }
+
+ /**
+ * Method that draws all the photo frames.
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ public void draw(float[] matrix) {
+ // Apply every transition
+ if (mTransitions != null) {
+ // First draw active transitions; then the not running transitions
+ for (Transition transition : mTransitions) {
+ transition.apply(matrix);
+ }
+ }
+ }
+
+ /**
+ * Method that returns a coordinates per vertex array from a disposition
+ *
+ * @param disposition The source disposition
+ * @param portrait If the device is in portrait mode
+ * @param cellw The cell width based on the surface
+ * @param cellh The cell height based on the surface
+ * @return float[] The coordinates per vertex array
+ */
+ private static float[] getVerticesFromDisposition(
+ Disposition disposition, boolean portrait, float cellw, float cellh) {
+ int x = portrait ? disposition.x : disposition.y;
+ int y = portrait ? disposition.y : disposition.x;
+ int w = portrait ? disposition.w : disposition.h;
+ int h = portrait ? disposition.h : disposition.w;
+ return new float[]
+ {
+ // top left
+ -1.0f + (x * cellw),
+ 1.0f - (y * cellh),
+ 0.0f,
+
+ // bottom left
+ -1.0f + (x * cellw),
+ 1.0f - ((y * cellh) + (h * cellh)),
+ 0.0f,
+
+ // bottom right
+ -1.0f + ((x * cellw) + (w * cellw)),
+ 1.0f - ((y * cellh) + (h * cellh)),
+ 0.0f,
+
+ // top right
+ -1.0f + ((x * cellw) + (w * cellw)),
+ 1.0f - (y * cellh),
+ 0.0f
+ };
+ }
+
+ /**
+ * Method that applies a padding to the frame
+ *
+ * @param texCoords The source coordinates
+ * @param screenWidth The screen width
+ * @param screenHeight The screen height
+ * @return float[] The new coordinates
+ */
+ private static float[] getFramePadding(float[] coords, int screenWidth, int screenHeight) {
+ float[] paddingCoords = new float[coords.length];
+ System.arraycopy(coords, 0, paddingCoords, 0, coords.length);
+ final float pxw = (1 / (float)screenWidth) * PHOTO_FRAME_PADDING;
+ final float pxh = (1 / (float)screenHeight) * PHOTO_FRAME_PADDING;
+ paddingCoords[0] += pxw;
+ paddingCoords[1] -= pxh;
+ paddingCoords[3] += pxw;
+ paddingCoords[4] += pxh;
+ paddingCoords[6] -= pxw;
+ paddingCoords[7] += pxh;
+ paddingCoords[9] -= pxw;
+ paddingCoords[10] -= pxh;
+ return paddingCoords;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java b/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java
new file mode 100644
index 0000000..9d3663f
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/StorageHelper.java
@@ -0,0 +1,70 @@
+/**
+ *
+ */
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author jruesga
+ *
+ */
+public final class StorageHelper {
+
+ private static final String TAG = "StorageHelper";
+
+ private static final String EXTERNAL_REGEXP = "(?i).*vold.*(fuse|vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
+
+ /**
+ * Method that returns all the external mounts
+ *
+ * @return List<String> All the external mounts
+ */
+ public static List<String> getExternalMounts() {
+ final List<String> out = new ArrayList<String>();
+
+ // Execute the mount command to list mounts
+ final StringBuilder sb = new StringBuilder();
+ try {
+ final Process process =
+ new ProcessBuilder().command("mount")
+ .redirectErrorStream(true).start();
+ process.waitFor();
+ final InputStream is = process.getInputStream();
+ byte[] buffer = new byte[1024];
+ int read = 0;
+ while ((read = is.read(buffer, 0, 1024)) != -1) {
+ sb.append(new String(buffer, 0, read));
+ }
+ is.close();
+ } catch (IOException ioex) {
+ Log.e(TAG, "Failed to list external mounts", ioex);
+ } catch (InterruptedException iex) {
+ Log.e(TAG, "Failed to list external mounts", iex);
+ }
+
+ // Parse the output
+ final String[] lines = sb.toString().split("\n");
+ for (String line : lines) {
+ if (!line.toLowerCase(Locale.US).contains("asec")) {
+ if (line.matches(EXTERNAL_REGEXP)) {
+ String[] parts = line.split(" ");
+ for (String part : parts) {
+ if (part.startsWith("/")) {
+ if (!part.toLowerCase(Locale.US).contains("vold")) {
+ out.add(part);
+ }
+ }
+ }
+ }
+ }
+ }
+ return out;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
new file mode 100644
index 0000000..bd42af9
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.FixedQueue.EmptyQueueException;
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo;
+import org.cyanogenmod.wallpapers.photophase.MediaPictureDiscoverer.OnMediaPictureDiscoveredListener;
+import org.cyanogenmod.wallpapers.photophase.effects.Effects;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class that manages the acquisition of new textures.
+ */
+public class TextureManager implements OnMediaPictureDiscoveredListener {
+
+ private static final String TAG = "TextureManager";
+
+ private static final int QUEUE_SIZE = 1;
+
+ static final List<Bitmap> sRecycledBitmaps = new ArrayList<Bitmap>();
+
+ final Context mContext;
+ final Object mSync;
+ final List<TextureRequestor> mPendingRequests;
+ final FixedQueue<GLESTextureInfo> mQueue = new FixedQueue<GLESTextureInfo>(QUEUE_SIZE);
+ BackgroundPictureLoaderThread mBackgroundTask;
+ private final MediaPictureDiscoverer mPictureDiscoverer;
+
+
+
+ /*package*/ Rect mDimensions;
+
+ final GLESSurfaceDispatcher mDispatcher;
+
+ /**
+ * A private runnable that will run in the GLThread
+ */
+ /*package*/ class PictureDispatcher implements Runnable {
+ File mImage;
+ GLESTextureInfo ti;
+ final Object mWait = new Object();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ // If we have bitmap to reused then pick up from the recycled list
+ if (sRecycledBitmaps.size() > 0) {
+ // Bind to the GLES context
+ ti = GLESUtil.loadTexture(sRecycledBitmaps.remove(0));
+ } else {
+ // Load and bind to the GLES context
+ ti = GLESUtil.loadTexture(mImage, mDimensions, Effects.getNextEffect(), false);
+ }
+
+ synchronized (mSync) {
+ // Notify the new images to all pending frames
+ if (mPendingRequests.size() > 0) {
+ // Invalid textures are also reported, so requestor can handle it
+ mPendingRequests.remove(0).setTextureHandle(ti);
+ } else {
+ // Add to the queue (only valid textures)
+ if (ti.handle > 0) {
+ mQueue.insert(ti);
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "Something was wrong loading the texture: " + mImage.getAbsolutePath(), e);
+
+ } finally {
+ // Notify that we have a new image
+ synchronized (mWait) {
+ mWait.notify();
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor of <code>TextureManager</code>
+ *
+ * @param ctx The current context
+ * @param dispatcher The GLES dispatcher
+ * @param requestors The number of requestors
+ * @param dimensions The desired dimensions for the decoded bitmaps
+ */
+ public TextureManager(final Context ctx, GLESSurfaceDispatcher dispatcher, int requestors, Rect dimensions) {
+ super();
+ mContext = ctx;
+ mDispatcher = dispatcher;
+ mDimensions = dimensions;
+ mSync = new Object();
+ mPendingRequests = new ArrayList<TextureRequestor>(requestors);
+ mPictureDiscoverer = new MediaPictureDiscoverer(mContext, this);
+
+ // Run the media discovery thread
+ mBackgroundTask = new BackgroundPictureLoaderThread();
+ mBackgroundTask.mTaskPaused = false;
+ reloadMedia();
+ }
+
+ /**
+ * Method that allow to change the preferred dimensions of the bitmaps loaded
+ *
+ * @param dimensions The new dimensions
+ */
+ public void setDimensions(Rect dimensions) {
+ mDimensions = dimensions;
+ }
+
+ /**
+ * Method that returns if the texture manager is paused
+ *
+ * @return boolean whether the texture manager is paused
+ */
+ public boolean isPaused() {
+ return mBackgroundTask != null && mBackgroundTask.mTaskPaused;
+ }
+
+ /**
+ * Method that pauses the internal threads
+ *
+ * @param pause If the thread is paused (true) or resumed (false)
+ */
+ public synchronized void setPause(boolean pause) {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mTaskPaused = pause;
+ if (!mBackgroundTask.mTaskPaused) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ }
+ }
+
+ /**
+ * Method that reload the references of media pictures
+ */
+ void reloadMedia() {
+ Log.d(TAG, "Reload media picture data");
+ // Discover new media
+ mPictureDiscoverer.discover(Preferences.Media.getSelectedAlbums());
+ }
+
+ /**
+ * Method that returns a bitmap to be reused
+ *
+ * @param bitmap The bitmap to release
+ */
+ @SuppressWarnings("static-method")
+ public void releaseBitmap(Bitmap bitmap) {
+ if (bitmap != null) {
+ sRecycledBitmaps.add(0, bitmap);
+ }
+ }
+
+ /**
+ * Method that request a new picture for the {@link TextureRequestor}
+ *
+ * @param requestor The requestor of the texture
+ */
+ public void request(TextureRequestor requestor) {
+ synchronized (mSync) {
+ try {
+ requestor.setTextureHandle(mQueue.remove());
+ } catch (EmptyQueueException eqex) {
+ // Add to queue of pending request to be notified when
+ // we have a new bitmap in the queue
+ mPendingRequests.add(requestor);
+ }
+ }
+
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ }
+
+ /**
+ * Method that removes all the textures from the queue
+ *
+ * @param reload Forces a reload of the queue
+ */
+ public void emptyTextureQueue(boolean reload) {
+ synchronized (mSync) {
+ try {
+ List<GLESTextureInfo> all = mQueue.removeAll();
+ for (GLESTextureInfo info : all) {
+ if (GLES20.glIsTexture(info.handle)) {
+ int[] textures = new int[] {info.handle};
+ GLES20.glDeleteTextures(1, textures, 0);
+ GLESUtil.glesCheckError("glDeleteTextures");
+ }
+ // Return the bitmap
+ sRecycledBitmaps.add(info.bitmap);
+ }
+ } catch (EmptyQueueException eqex) {
+ // Ignore
+ }
+
+ // Reload the queue
+ if (reload) {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ }
+ }
+ }
+
+ /**
+ * Method that cancels a request did it previously.
+ *
+ * @param requestor The requestor of the texture
+ */
+ public void cancelRequest(TextureRequestor requestor) {
+ synchronized (mSync) {
+ if (mPendingRequests.contains(requestor)) {
+ mPendingRequests.remove(requestor);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onMediaDiscovered(MediaPictureDiscoverer mpc, File[] images) {
+ // Now we have the paths of the images to use. Start a image loader
+ // thread to load pictures in background
+ mBackgroundTask.setAvailableImages(images);
+ if (!mBackgroundTask.mRun) {
+ mBackgroundTask.start();
+ } else {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.mLoadSync.notify();
+ }
+ }
+ int found = images == null ? 0 : images.length;
+ Log.d(TAG, "Media picture data reloaded: " + found + " images found.");
+ }
+
+ /**
+ * Method that destroy the references of this class
+ */
+ public void recycle() {
+ // Destroy the media discovery task
+ mPictureDiscoverer.recycle();
+
+ // Destroy the background task
+ if (mBackgroundTask != null) {
+ mBackgroundTask.mRun = false;
+ try {
+ synchronized (mBackgroundTask.mLoadSync) {
+ mBackgroundTask.interrupt();
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ mBackgroundTask = null;
+
+ // Recycle the textures of the queue
+ emptyTextureQueue(false);
+ }
+
+ /**
+ * An internal thread to load pictures in background
+ */
+ private class BackgroundPictureLoaderThread extends Thread {
+
+ final Object mLoadSync = new Object();
+ boolean mRun;
+ boolean mTaskPaused;
+
+ private final List<File> mNewImages;
+ private final List<File> mUsedImages;
+
+ /**
+ * Constructor of <code>BackgroundPictureLoaderThread</code>.
+ */
+ public BackgroundPictureLoaderThread() {
+ super();
+ mNewImages = new ArrayList<File>();
+ mUsedImages = new ArrayList<File>();
+ }
+
+ /**
+ * Method that sets the current available images.
+ *
+ * @param images The current images
+ */
+ public void setAvailableImages(File[] images) {
+ synchronized (mLoadSync) {
+ mNewImages.addAll(Arrays.asList(images));
+ mUsedImages.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ mRun = true;
+ while (mRun) {
+ // Check if we need to load more images
+ while (!mTaskPaused && TextureManager.this.mQueue.items() < TextureManager.this.mQueue.size()) {
+ File image = null;
+ synchronized (mLoadSync) {
+ // Swap arrays if needed
+ if (mNewImages.size() == 0) {
+ mNewImages.addAll(mUsedImages);
+ mUsedImages.clear();
+ }
+ if (mNewImages.size() == 0) {
+ reloadMedia();
+ break;
+ }
+
+ // Extract a random image
+ int low = 0;
+ int hight = mNewImages.size()-1;
+ int index = low + (int)(Math.random() * ((hight - low) + 1));
+ image = mNewImages.remove(index);
+ }
+
+ // Run commands in the GLThread
+ if (!mRun) break;
+ PictureDispatcher pd = new PictureDispatcher();
+ pd.mImage = image;
+ mDispatcher.dispatch(pd);
+
+ // Wait until the texture is loaded
+ try {
+ synchronized (pd.mWait) {
+ pd.mWait.wait();
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+
+ // Add to used images
+ mUsedImages.add(image);
+ }
+
+ // Wait for new request
+ synchronized (mLoadSync) {
+ try {
+ mLoadSync.wait();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java b/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java
new file mode 100644
index 0000000..59e8593
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/TextureRequestor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLESTextureInfo;
+
+/**
+ * An interface that defines an object as able to request textures.
+ */
+public interface TextureRequestor {
+
+ /**
+ * Method that set the texture handle requested.
+ *
+ * @param ti The texture information
+ */
+ void setTextureHandle(GLESTextureInfo ti);
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/Utils.java b/src/org/cyanogenmod/wallpapers/photophase/Utils.java
new file mode 100644
index 0000000..d8331a0
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/Utils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+/**
+ * A helper class with utilities
+ */
+public class Utils {
+
+ /**
+ * This method converts dp unit to equivalent device specific value in pixels.
+ *
+ * @param ctx The current context
+ * @param dp A value in dp (Device independent pixels) unit
+ * @return float A float value to represent Pixels equivalent to dp according to device
+ */
+ public static float convertDpToPixel(Context ctx, float dp) {
+ Resources resources = ctx.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ return dp * (metrics.densityDpi / 160f);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java b/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java
new file mode 100644
index 0000000..44101f4
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/animations/AlbumsFlip3dAnimationController.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.cyanogenmod.wallpapers.photophase.animations;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+
+import org.cyanogenmod.wallpapers.photophase.model.Album;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures.CallbacksListener;
+
+/**
+ * A class that manages a flip 3d effect of an album
+ */
+public class AlbumsFlip3dAnimationController {
+
+ private static final int DURATION = 200;
+
+ View mFront;
+ View mBack;
+ boolean mFrontFace;
+
+ /**
+ * Constructor of <code>AlbumsFlip3dAnimationController</code>
+ *
+ * @param front The front view
+ * @param back The back view
+ */
+ public AlbumsFlip3dAnimationController(AlbumInfo front, AlbumPictures back) {
+ super();
+ mFront = front;
+ mBack = back;
+ mBack.setVisibility(View.GONE);
+ mFrontFace = true;
+ }
+
+ /**
+ * Method that register the controller
+ */
+ public void register() {
+ getFrontView().setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getBackView().setVisibility(View.INVISIBLE);
+ applyAnimation(false);
+ }
+ });
+ ((AlbumPictures)getBackView()).addCallBackListener(new CallbacksListener() {
+ @Override
+ public void onBackButtonClick(View v) {
+ getBackView().setVisibility(View.INVISIBLE);
+ applyAnimation(true);
+ }
+
+ @Override
+ public void onSelectionChanged(Album album) {
+ // Ignore
+ }
+ });
+ }
+
+ /**
+ * Method that applies the animation over the views
+ *
+ * @param inverse Applies the inverse animation
+ */
+ /*package*/ void applyAnimation(boolean inverse) {
+ applyTransformation(getFrontView(), 0, 90 * (inverse ? -1 : 1), true);
+ }
+
+ /*package*/ void applyTransformation(final View v, float start, float end, final boolean step1) {
+ final float centerX = v.getWidth() / 2.0f;
+ final float centerY = v.getHeight() / 2.0f;
+
+ final Flip3dAnimation anim = new Flip3dAnimation(start, end, centerX, centerY);
+ anim.setDuration(DURATION);
+ anim.setFillAfter(true);
+ anim.setInterpolator(new AccelerateInterpolator());
+
+ anim.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ if (!step1) {
+ getBackView().setVisibility(View.VISIBLE);
+ }
+ getFrontView().setOnClickListener(null);
+ getBackView().setOnClickListener(null);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ // Ignore
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ getFrontView().setAnimation(null);
+ getBackView().setAnimation(null);
+ if (step1) {
+ getFrontView().setVisibility(View.INVISIBLE);
+ applyTransformation(getBackView(), -90 * (!mFrontFace ? -1 : 1), 0, false);
+ } else {
+ mFrontFace = !mFrontFace;
+ getBackView().setVisibility(View.GONE);
+ if (mFrontFace) {
+ getFrontView().setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ getBackView().setVisibility(View.INVISIBLE);
+ applyAnimation(false);
+ }
+ });
+ } else {
+ ((AlbumPictures)getFrontView()).onShow();
+ }
+ }
+ }
+ });
+ v.startAnimation(anim);
+ }
+
+ /*package*/ View getFrontView() {
+ return mFrontFace ? mFront : mBack;
+ }
+
+ /*package*/ View getBackView() {
+ return !mFrontFace ? mFront : mBack;
+ }
+}
+
diff --git a/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java b/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java
new file mode 100644
index 0000000..0196dc5
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/animations/Flip3dAnimation.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.cyanogenmod.wallpapers.photophase.animations;
+
+import android.graphics.Camera;
+import android.graphics.Matrix;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+/**
+ * A 3d flit animation
+ */
+public class Flip3dAnimation extends Animation {
+
+ private final float mFromDegrees;
+ private final float mToDegrees;
+ private final float mCenterX;
+ private final float mCenterY;
+ private Camera mCamera;
+
+ /**
+ * Constructor of <code>Flip3dAnimation</code>
+ *
+ * @param fromDegrees From origin degrees
+ * @param toDegrees To destination degrees
+ * @param centerX The center horizontal position
+ * @param centerY The center vertical position
+ */
+ public Flip3dAnimation(float fromDegrees, float toDegrees, float centerX, float centerY) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ mCamera = new Camera();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ final float fromDegrees = mFromDegrees;
+ float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
+
+ final float centerX = mCenterX;
+ final float centerY = mCenterY;
+ final Camera camera = mCamera;
+
+ final Matrix matrix = t.getMatrix();
+
+ camera.save();
+ camera.rotateY(degrees);
+ camera.getMatrix(matrix);
+ camera.restore();
+
+ matrix.preTranslate(-centerX, -centerY);
+ matrix.postTranslate(centerX, centerY);
+ }
+
+}
+
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java
new file mode 100644
index 0000000..dbda330
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/BlackAndWhiteEffect.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+
+/**
+ * This effect converts the source image to black and white color scheme.
+ */
+public class BlackAndWhiteEffect extends Effect {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Bitmap apply(Bitmap src) {
+ try {
+ final int height = src.getHeight();
+ final int width = src.getWidth();
+
+ Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas c = new Canvas(dst);
+ final Paint paint = new Paint();
+ final ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0);
+ final ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
+ paint.setColorFilter(f);
+ c.drawBitmap(src, 0, 0, paint);
+ return dst;
+ } finally {
+ src.recycle();
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java
new file mode 100644
index 0000000..4ab4695
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/Effect.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.graphics.Bitmap;
+
+/**
+ * The base class for all image effects.
+ */
+public abstract class Effect {
+
+ /**
+ * Method that applies the effect
+ *
+ * @param src The source bitmap
+ * @return Bitmap The bitmap with the effect applied
+ */
+ public abstract Bitmap apply(Bitmap src);
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java b/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java
new file mode 100644
index 0000000..f8fd90d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/Effects.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+/**
+ * A class that manages all the supported effects
+ */
+public class Effects {
+
+ /**
+ * Enumeration of the supported effects
+ */
+ public enum EFFECTS {
+ /**
+ * A random combination of all supported effects
+ */
+ RANDOM,
+ /**
+ * @see NullEffect
+ */
+ NO_EFFECT,
+ /**
+ * @see BlackAndWhiteEffect
+ */
+ BLACK_AND_WHITE,
+ /**
+ * @see SepiaEffect
+ */
+ SEPIA;
+ }
+
+ /**
+ * Method that return the next effect to use with the picture.
+ *
+ * @return Effect The next effect to use
+ */
+ public static Effect getNextEffect() {
+ int effect = Preferences.General.Effects.getEffectTypes();
+ if (effect == EFFECTS.RANDOM.ordinal()) {
+ int low = EFFECTS.NO_EFFECT.ordinal();
+ int hight = EFFECTS.values().length - 1;
+ effect = low + (int)(Math.random() * ((hight - low) + 1));
+ }
+ if (effect == EFFECTS.BLACK_AND_WHITE.ordinal()) {
+ return new BlackAndWhiteEffect();
+ }
+ if (effect == EFFECTS.SEPIA.ordinal()) {
+ return new SepiaEffect();
+ }
+ return new NullEffect();
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java
new file mode 100644
index 0000000..0cd5e10
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/NullEffect.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.graphics.Bitmap;
+
+/**
+ * A <code>null</code> effect. This class doesn't apply any filter to the bitmap
+ */
+public class NullEffect extends Effect {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Bitmap apply(Bitmap bitmap) {
+ return bitmap;
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java
new file mode 100644
index 0000000..15aa894
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/SepiaEffect.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.effects;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+
+/**
+ * This effect converts the source image to sepia color scheme.
+ */
+public class SepiaEffect extends Effect {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Bitmap apply(Bitmap src) {
+ try {
+ final int height = src.getHeight();
+ final int width = src.getWidth();
+
+ Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas c = new Canvas(dst);
+ final Paint paint = new Paint();
+ final ColorMatrix cmA = new ColorMatrix();
+ cmA.setSaturation(0);
+ final ColorMatrix cmB = new ColorMatrix();
+ cmB.setScale(1f, .95f, .82f, 1.0f);
+ cmA.setConcat(cmB, cmA);
+ final ColorMatrixColorFilter f = new ColorMatrixColorFilter(cmA);
+ paint.setColorFilter(f);
+ c.drawBitmap(src, 0, 0, paint);
+ return dst;
+ } finally {
+ src.recycle();
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/model/Album.java b/src/org/cyanogenmod/wallpapers/photophase/model/Album.java
new file mode 100644
index 0000000..6329a69
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/model/Album.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.model;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.List;
+
+/**
+ * A class that represents an album
+ */
+public class Album implements Comparable<Album> {
+
+ private Drawable mIcon;
+ private String mPath;
+ private String mName;
+ private String mDate;
+ private boolean selected;
+ private List<String> mItems;
+ private List<String> mSelectedItems;
+
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ public void setIcon(Drawable icon) {
+ this.mIcon = icon;
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+
+ public void setPath(String path) {
+ this.mPath = path;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ this.mName = name;
+ }
+
+ public String getDate() {
+ return mDate;
+ }
+
+ public void setDate(String date) {
+ this.mDate = date;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ public List<String> getItems() {
+ return mItems;
+ }
+
+ public void setItems(List<String> items) {
+ this.mItems = items;
+ }
+
+ public List<String> getSelectedItems() {
+ return mSelectedItems;
+ }
+
+ public void setSelectedItems(List<String> selectedItems) {
+ this.mSelectedItems = selectedItems;
+ }
+
+ @Override
+ public int compareTo(Album another) {
+ return mPath.compareTo(another.mPath);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java
new file mode 100644
index 0000000..f0f3522
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/ChoosePicturesFragment.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.AsyncTask.Status;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.provider.MediaStore;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.animations.AlbumsFlip3dAnimationController;
+import org.cyanogenmod.wallpapers.photophase.model.Album;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumInfo;
+import org.cyanogenmod.wallpapers.photophase.widgets.AlbumPictures;
+import org.cyanogenmod.wallpapers.photophase.widgets.CardLayout;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A fragment class for select the picture that will be displayed on the wallpaper
+ */
+public class ChoosePicturesFragment extends PreferenceFragment {
+
+ private final AsyncTask<Void, Album, Void> mAlbumsLoaderTask = new AsyncTask<Void, Album, Void>() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Void doInBackground(Void... params) {
+ // Query all the external content and classify the pictures in albums and load the cards
+ DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ Album album = null;
+ mAlbums.clear();
+ Cursor c = mContentResolver.query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[]{ MediaStore.MediaColumns.DATA },
+ null,
+ null,
+ MediaStore.MediaColumns.DATA);
+ if (c != null) {
+ try {
+ while (c.moveToNext()) {
+ // Only valid files (those i can read)
+ String p = c.getString(0);
+ if (p != null) {
+ File f = new File(p);
+ if (f.exists() && f.isFile() && f.canRead()) {
+ File path = f.getParentFile();
+ String name = path.getName();
+ if (album == null || album.getPath().compareTo(path.getAbsolutePath()) != 0) {
+ if (album != null) {
+ mAlbums.add(album);
+ this.publishProgress(album);
+ try {
+ Thread.sleep(50L);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ album = new Album();
+ album.setPath(path.getAbsolutePath());
+ album.setName(name);
+ album.setDate(df.format(new Date(path.lastModified())));
+ album.setSelected(isSelectedItem(album.getPath()));
+ album.setItems(new ArrayList<String>());
+ album.setSelectedItems(new ArrayList<String>());
+ }
+ album.getItems().add(f.getAbsolutePath());
+ if (isSelectedItem(f.getAbsolutePath())) {
+ album.getSelectedItems().add(f.getAbsolutePath());
+ }
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onProgressUpdate(Album... values) {
+ for (Album album : values) {
+ addAlbum(album);
+ }
+ }
+ };
+
+
+
+ List<Album> mAlbums;
+ ContentResolver mContentResolver;
+
+ private CardLayout mAlbumsPanel;
+
+ /*package*/ Set<String> mSelectedAlbums;
+
+ /*package*/ boolean mSelectionChanged;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContentResolver = getActivity().getContentResolver();
+
+ // Create an empty album
+ mAlbums = new ArrayList<Album>();
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ // Load the albums user selection
+ mSelectedAlbums = PreferencesProvider.Preferences.Media.getSelectedAlbums();
+ mSelectionChanged = false;
+
+ setHasOptionsMenu(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mAlbumsLoaderTask.getStatus().compareTo(Status.PENDING) == 0) {
+ mAlbumsLoaderTask.cancel(true);
+ }
+ unbindDrawables(mAlbumsPanel);
+
+ // Notify that the settings was changed
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ if (mSelectionChanged) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE);
+ }
+ getActivity().sendBroadcast(intent);
+ }
+
+ /**
+ * Method that unbind all the drawables for a view
+ *
+ * @param view The root view
+ */
+ private void unbindDrawables(View view) {
+ if (view.getBackground() != null) {
+ view.getBackground().setCallback(null);
+ }
+ if (view instanceof ViewGroup) {
+ for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
+ unbindDrawables(((ViewGroup) view).getChildAt(i));
+ }
+ ((ViewGroup) view).removeAllViews();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View v = inflater.inflate(R.layout.choose_picture_fragment, container, false);
+ mAlbumsPanel = (CardLayout) v.findViewById(R.id.albums_panel);
+
+ // Load the albums
+ mAlbumsLoaderTask.execute();
+
+ return v;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.albums, menu);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mnu_ok:
+ getActivity().finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Method that adds a new album to the card layout
+ *
+ * @param album The album to add
+ */
+ void addAlbum(Album album) {
+ LayoutInflater li = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final View albumView = li.inflate(R.layout.album, mAlbumsPanel, false);
+ final AlbumInfo albumInfo = (AlbumInfo)albumView.findViewById(R.id.album_info);
+ final AlbumPictures albumPictures = (AlbumPictures)albumView.findViewById(R.id.album_pictures);
+
+ // Load the album info
+ albumInfo.updateView(album);
+ if (album.isSelected()) {
+ albumInfo.setSelected(true);
+ }
+ albumInfo.addCallBackListener(new AlbumInfo.CallbacksListener() {
+ @Override
+ public void onAlbumSelected(Album ref) {
+ // Remove all pictures of the album and add the album reference
+ removeAlbumItems(ref);
+ mSelectedAlbums.add(ref.getPath());
+ ref.setSelected(true);
+ albumPictures.updateView(ref);
+
+ PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+
+ @Override
+ public void onAlbumDeselected(Album ref) {
+ // Remove all pictures of the album
+ removeAlbumItems(ref);
+ ref.setSelected(false);
+ albumPictures.updateView(ref);
+
+ PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+
+
+ });
+
+ // Load the album picture data
+ albumPictures.updateView(album);
+ albumPictures.addCallBackListener(new AlbumPictures.CallbacksListener() {
+ @Override
+ public void onBackButtonClick(View v) {
+ // Ignored
+ }
+
+ @Override
+ public void onSelectionChanged(Album ref) {
+ // Remove, add, and persist the selection
+ removeAlbumItems(ref);
+ mSelectedAlbums.addAll(ref.getSelectedItems());
+ ref.setSelected(false);
+ albumInfo.updateView(ref);
+
+ PreferencesProvider.Preferences.Media.setSelectedAlbums(getActivity(), mSelectedAlbums);
+ mSelectionChanged = true;
+ }
+ });
+
+ // Register the animation controller
+ AlbumsFlip3dAnimationController controller = new AlbumsFlip3dAnimationController(albumInfo, albumPictures);
+ controller.register();
+
+ // Add to the panel of cards
+ mAlbumsPanel.addCard(albumView);
+ }
+
+ /**
+ * Method that checks if an item is selected
+ *
+ * @param item The item
+ * @return boolean if an item is selected
+ */
+ /*package*/ boolean isSelectedItem(String item) {
+ Iterator<String> it = mSelectedAlbums.iterator();
+ while (it.hasNext()) {
+ String albumPath = it.next();
+ if (item.compareTo(albumPath) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that removes the reference to all the items and itself
+ *
+ * @param ref The album
+ */
+ /*package*/ void removeAlbumItems(Album ref) {
+ Iterator<String> it = mSelectedAlbums.iterator();
+ while (it.hasNext()) {
+ String item = it.next();
+ String parent = new File(item).getParent();
+ if (parent.compareTo(ref.getPath()) == 0) {
+ it.remove();
+ } else if (item.compareTo(ref.getPath()) == 0) {
+ it.remove();
+ }
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java
new file mode 100644
index 0000000..008522a
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/Disposition.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+
+/**
+ * A class that holds a {@link PhotoFrame} disposition.
+ */
+public class Disposition {
+ /**
+ * Column
+ */
+ public int x;
+ /**
+ * Row
+ */
+ public int y;
+ /**
+ * Columns width
+ */
+ public int w;
+ /**
+ * Rows height
+ */
+ public int h;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + h;
+ result = prime * result + w;
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Disposition other = (Disposition) obj;
+ if (h != other.h)
+ return false;
+ if (w != other.w)
+ return false;
+ if (x != other.x)
+ return false;
+ if (y != other.y)
+ return false;
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "Disposition [x=" + x + ", y=" + y + ", w=" + w + ", h=" + h + "]";
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java
new file mode 100644
index 0000000..069d648
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/GeneralPreferenceFragment.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.Colors;
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.preferences.SeekBarProgressPreference.OnDisplayProgress;
+import org.cyanogenmod.wallpapers.photophase.widgets.ColorPickerPreference;
+
+import java.text.DecimalFormat;
+
+/**
+ * A fragment class with all the general settings
+ */
+public class GeneralPreferenceFragment extends PreferenceFragment {
+
+ private static final String TAG = "GeneralPreferenceFragment";
+
+ private static final boolean DEBUG = true;
+
+ private SeekBarProgressPreference mWallpaperDim;
+ private ColorPickerPreference mBackgroundColor;
+ private ListPreference mTransitionsTypes;
+ private SeekBarProgressPreference mTransitionsInterval;
+ private ListPreference mEffectsTypes;
+
+ boolean mRedrawFlag;
+ boolean mEmptyTextureQueueFlag;
+
+ private final OnPreferenceChangeListener mOnChangeListener = new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (DEBUG) Log.d(TAG, "Preference changed: " + key + "=" + newValue);
+ if (key.compareTo("ui_wallpaper_dim") == 0) {
+ mRedrawFlag = true;
+ } else if (key.compareTo("ui_background_color") == 0) {
+ mRedrawFlag = true;
+ int color = ((Integer)newValue).intValue();
+ Colors.setBackground(new GLColor(color));
+ } else if (key.compareTo("ui_transition_types") == 0) {
+ mRedrawFlag = true;
+ } else if (key.compareTo("ui_transition_interval") == 0) {
+ mRedrawFlag = true;
+ } else if (key.compareTo("ui_effect_types") == 0) {
+ mRedrawFlag = true;
+ mEmptyTextureQueueFlag = true;
+ }
+ return true;
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Reload the settings
+ PreferencesProvider.reload(getActivity());
+
+ // Notify that the settings was changed
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ if (mRedrawFlag) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE);
+ }
+ if (mEmptyTextureQueueFlag) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, Boolean.TRUE);
+ }
+ getActivity().sendBroadcast(intent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ final DecimalFormat df = new DecimalFormat();
+ df.setMinimumFractionDigits(0);
+ df.setMaximumIntegerDigits(1);
+
+ // Add the preferences
+ addPreferencesFromResource(R.xml.preferences_general);
+
+ mWallpaperDim = (SeekBarProgressPreference)findPreference("ui_wallpaper_dim");
+ mWallpaperDim.setFormat(getString(R.string.pref_general_settings_wallpaper_dim_format));
+ mWallpaperDim.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mBackgroundColor = (ColorPickerPreference)findPreference("ui_background_color");
+ mBackgroundColor.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mTransitionsTypes = (ListPreference)findPreference("ui_transition_types");
+ mTransitionsTypes.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mTransitionsInterval = (SeekBarProgressPreference)findPreference("ui_transition_interval");
+ mTransitionsInterval.setFormat(getString(R.string.pref_general_transitions_interval_format));
+ int max = PreferencesProvider.Preferences.General.Transitions.MAX_TRANSITION_INTERVAL;
+ int min = PreferencesProvider.Preferences.General.Transitions.MIN_TRANSITION_INTERVAL;
+ final int MAX = ((max - min) / 1000) * 2;
+ mTransitionsInterval.setMax(MAX);
+ mTransitionsInterval.setOnDisplayProgress(new OnDisplayProgress() {
+ @Override
+ public String onDisplayProgress(int progress) {
+ return df.format((progress * 0.5) + 1);
+ }
+ });
+ mTransitionsInterval.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mEffectsTypes = (ListPreference)findPreference("ui_effect_types");
+ mEffectsTypes.setOnPreferenceChangeListener(mOnChangeListener);
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java
new file mode 100644
index 0000000..564c425
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/LayoutPreferenceFragment.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A fragment class with the layout disposition
+ */
+public class LayoutPreferenceFragment extends PreferenceFragment {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ // Add the preferences
+ addPreferencesFromResource(R.xml.preferences_layout);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java
new file mode 100644
index 0000000..8dd942d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/MediaPreferenceFragment.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A fragment class with all the media settings
+ */
+public class MediaPreferenceFragment extends PreferenceFragment {
+
+ private static final String TAG = "MediaPreferenceFragment";
+
+ private static final boolean DEBUG = true;
+
+ ListPreference mRefreshInterval;
+ Preference mRefreshNow;
+
+ boolean mMediaIntevalChangedFlag;
+
+ private final OnPreferenceChangeListener mOnChangeListener = new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (DEBUG) Log.d(TAG, "Preference changed: " + key + "=" + newValue);
+ if (key.compareTo("ui_media_refresh_interval") == 0) {
+ setRefreshIntervalSummary(Integer.valueOf(String.valueOf(newValue)).intValue());
+ mMediaIntevalChangedFlag = true;
+ }
+ return true;
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Reload the settings
+ PreferencesProvider.reload(getActivity());
+
+ // Notify that the settings was changed
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ if (mMediaIntevalChangedFlag) {
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_INTERVAL_CHANGED, Boolean.TRUE);
+ }
+ getActivity().sendBroadcast(intent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(PreferencesProvider.PREFERENCES_FILE);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ // Add the preferences
+ addPreferencesFromResource(R.xml.preferences_media);
+
+ mRefreshInterval = (ListPreference)findPreference("ui_media_refresh_interval");
+ setRefreshIntervalSummary(PreferencesProvider.Preferences.Media.getRefreshFrecuency());
+ mRefreshInterval.setOnPreferenceChangeListener(mOnChangeListener);
+
+ mRefreshNow = findPreference("ui_media_refresh_now");
+ mRefreshNow.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ // Request a refresh of the media data
+ Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
+ intent.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE);
+ getActivity().sendBroadcast(intent);
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Method that set the refresh interval summary
+ *
+ * @param interval The interval value
+ */
+ void setRefreshIntervalSummary(int interval) {
+ String v = String.valueOf(interval);
+ String[] labels = getResources().getStringArray(R.array.refresh_intervals_labels);
+ String[] values = getResources().getStringArray(R.array.refresh_intervals_values);
+ int cc = values.length;
+ for (int i = 0; i < cc; i++) {
+ if (values[i].compareTo(String.valueOf(v)) == 0) {
+ v = labels[i];
+ break;
+ }
+ }
+ String summary =
+ (interval == 0)
+ ? getString(R.string.pref_media_settings_refresh_interval_disable)
+ : getString(R.string.pref_media_settings_refresh_interval_summary, v);
+ mRefreshInterval.setSummary(summary);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java
new file mode 100644
index 0000000..3733418
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PhotoPhasePreferences.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.MenuItem;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+import java.util.List;
+
+/**
+ * The PhotoPhase Live Wallpaper preferences.
+ */
+public class PhotoPhasePreferences extends PreferenceActivity {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ //Initialize action bars
+ initTitleActionBar();
+ }
+
+ /**
+ * Method that initializes the titlebar of the activity.
+ */
+ private void initTitleActionBar() {
+ //Configure the action bar options
+ getActionBar().setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onBuildHeaders(List<Header> target) {
+ loadHeadersFromResource(R.xml.preferences_headers, target);
+
+ // Retrieve the about header
+ Header aboutHeader = target.get(target.size()-1);
+ try {
+ String appver =
+ this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName;
+ aboutHeader.summary = getString(R.string.pref_about_summary, appver);
+ } catch (Exception e) {
+ aboutHeader.summary = getString(R.string.pref_about_summary, ""); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java
new file mode 100644
index 0000000..8d04258
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PreferencesProvider.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.effects.Effects.EFFECTS;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that holds all the preferences of the wallpaper
+ */
+@SuppressWarnings("boxing")
+public final class PreferencesProvider {
+
+ /**
+ * Internal broadcast action to communicate that some setting was changed
+ * @see #EXTRA_FLAG_REDRAW
+ * {@hide}
+ */
+ public static final String ACTION_SETTINGS_CHANGED = "org.cyanogenmod.wallpapers.photophase.actions.SETTINGS_CHANGED";
+
+ /**
+ * An extra setting that indicates that the changed setting request a whole recreation of the wallpaper world
+ * {@hide}
+ */
+ public static final String EXTRA_FLAG_RECREATE_WORLD = "flag_recreate_world";
+
+ /**
+ * An extra setting that indicates that the changed setting request a redraw of the wallpaper
+ * {@hide}
+ */
+ public static final String EXTRA_FLAG_REDRAW = "flag_redraw";
+
+ /**
+ * An extra setting that indicates that the changed setting request to empty the texture queue
+ * {@hide}
+ */
+ public static final String EXTRA_FLAG_EMPTY_TEXTURE_QUEUE = "flag_empty_texture_queue";
+
+ /**
+ * An extra setting that indicates that the changed setting request a reload of the media data
+ * {@hide}
+ */
+ public static final String EXTRA_FLAG_MEDIA_RELOAD = "flag_media_reload";
+
+ /**
+ * An extra setting that indicates that the changed setting notifies that the media
+ * interval was changed
+ * {@hide}
+ */
+ public static final String EXTRA_FLAG_MEDIA_INTERVAL_CHANGED = "flag_media_interval_changed";
+
+ /**
+ * The shared preferences file
+ */
+ public static final String PREFERENCES_FILE = "org.cyanogenmod.wallpapers.photophase";
+
+ private static Map<String, ?> mPreferences = new HashMap<String, Object>();
+
+ /**
+ * Method that loads the all the preferences of the application
+ *
+ * @param context The current context
+ */
+ public static void reload(Context context) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ mPreferences = preferences.getAll();
+ }
+
+ /**
+ * Method that returns a integer property value.
+ *
+ * @param key The preference key
+ * @param def The default value
+ * @return int The integer property value
+ */
+ static int getInt(String key, int def) {
+ return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Integer ?
+ (Integer) mPreferences.get(key) : def;
+ }
+
+ /**
+ * Method that returns a long property value.
+ *
+ * @param key The preference key
+ * @param def The default value
+ * @return long The long property value
+ */
+ static long getLong(String key, long def) {
+ return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Long ?
+ (Long) mPreferences.get(key) : def;
+ }
+
+ /**
+ * Method that returns a boolean property value.
+ *
+ * @param key The preference key
+ * @param def The default value
+ * @return boolean The boolean property value
+ */
+ static boolean getBoolean(String key, boolean def) {
+ return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Boolean ?
+ (Boolean) mPreferences.get(key) : def;
+ }
+
+ /**
+ * Method that returns a string property value.
+ *
+ * @param key The preference key
+ * @param def The default value
+ * @return String The string property value
+ */
+ static String getString(String key, String def) {
+ return mPreferences.containsKey(key) && mPreferences.get(key) instanceof String ?
+ (String) mPreferences.get(key) : def;
+ }
+
+ /**
+ * Method that returns a string set property value.
+ *
+ * @param key The preference key
+ * @param def The default value
+ * @return Set<String> The string property value
+ */
+ @SuppressWarnings("unchecked")
+ static Set<String> getStringSet(String key, Set<String> def) {
+ return mPreferences.containsKey(key) && mPreferences.get(key) instanceof Set<?> ?
+ (Set<String>) mPreferences.get(key) : def;
+ }
+
+ /**
+ * A class for access to the preferences of the application
+ */
+ public static class Preferences {
+ /**
+ * General preferences
+ */
+ public static class General {
+ private static final GLColor DEFAULT_BACKGROUND_COLOR = new GLColor("#ff202020");
+
+ /**
+ * Method that returns the wallpaper dimmed value.
+ *
+ * @return float If the wallpaper dimmed value (0-black, 100-black)
+ */
+ public static float getWallpaperDim() {
+ return getInt("ui_wallpaper_dim", 0);
+ }
+
+ /**
+ * Method that returns the background color
+ *
+ * @return GLColor The background color
+ */
+ public static GLColor getBackgroundColor() {
+ int color = getInt("ui_background_color", 0);
+ if (color == 0) {
+ return DEFAULT_BACKGROUND_COLOR;
+ }
+ return new GLColor(color);
+ }
+
+ /**
+ * Transitions preferences
+ */
+ public static class Transitions {
+ /**
+ * The default transition interval
+ */
+ public static final int DEFAULT_TRANSITION_INTERVAL = 2000;
+ /**
+ * The minimum transition interval
+ */
+ public static final int MIN_TRANSITION_INTERVAL = 1000;
+ /**
+ * The maximum transition interval
+ */
+ public static final int MAX_TRANSITION_INTERVAL = 8000;
+
+ /**
+ * Return the current user preference about the transition to apply to
+ * the pictures of the wallpaper.
+ *
+ * @return int The transition to apply to the wallpaper's pictures
+ */
+ public static int getTransitionTypes() {
+ return Integer.valueOf(getString("ui_transition_types", String.valueOf(TRANSITIONS.RANDOM.ordinal())));
+ }
+
+ /**
+ * Method that returns how often the transitions are triggered.
+ *
+ * @return int The milliseconds in which the next transition will be triggered
+ */
+ public static int getTransitionInterval() {
+ int def = (DEFAULT_TRANSITION_INTERVAL / 500) - 2;
+ int interval = getInt("ui_transition_interval", def);
+ return (interval * 500) + 1000;
+ }
+ }
+
+ /**
+ * Effects preferences
+ */
+ public static class Effects {
+ /**
+ * Return the current user preference about the effect to apply to
+ * the pictures of the wallpaper.
+ *
+ * @return int The effect to apply to the wallpaper's pictures
+ */
+ public static int getEffectTypes() {
+ return Integer.valueOf(getString("ui_effect_types", String.valueOf(EFFECTS.NO_EFFECT.ordinal())));
+ }
+ }
+ }
+
+ /**
+ * Media preferences
+ */
+ public static class Media {
+ /**
+ * Constant that indicates that the media reload is disabled
+ */
+ public static final int MEDIA_RELOAD_DISABLED = 0;
+
+ /**
+ * Method that returns the frequency with which the media is updated.
+ *
+ * @return int The interval in seconds between updates. 0 means that updates are disabled
+ */
+ public static int getRefreshFrecuency() {
+ return Integer.valueOf(getString("ui_media_refresh_interval", String.valueOf(MEDIA_RELOAD_DISABLED)));
+ }
+
+ /**
+ * Method that returns the list of albums and pictures to be displayed
+ *
+ * @return Set<String> The list of albums and pictures to be displayed
+ */
+ public static Set<String> getSelectedAlbums() {
+ return getStringSet("ui_media_selected_albums", new HashSet<String>());
+ }
+
+ /**
+ * Method that returns the list of albums and pictures to be displayed
+ *
+ * @param context The current context
+ * @param selection The new list of albums and pictures to be displayed
+ */
+ public static synchronized void setSelectedAlbums(Context context, Set<String> selection) {
+ SharedPreferences preferences =
+ context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ Editor editor = preferences.edit();
+ editor.remove("ui_media_selected_albums");
+ editor.putStringSet("ui_media_selected_albums", selection);
+ editor.commit();
+ reload(context);
+ }
+ }
+
+ /**
+ * Layout preferences
+ */
+ public static class Layout {
+
+ private static final int DEFAULT_COLS = 8;
+ private static final int DEFAULT_ROWS = 14;
+ private static final String DEFAULT_DISPOSITION = "0x0:5x4|5x0:3x2|5x2:3x2|0x4:4x4|4x4:4x4|0x8:8x6";
+
+ /**
+ * Method that returns the rows of the wallpaper.
+ *
+ * @return int The rows of the wallpaper
+ */
+ public static int getRows() {
+ return getInt("ui_layout_rows", DEFAULT_ROWS);
+ }
+
+ /**
+ * Method that returns the columns of the wallpaper.
+ *
+ * @return int The columns of the wallpaper
+ */
+ public static int getCols() {
+ return getInt("ui_layout_cols", DEFAULT_COLS);
+ }
+
+ /**
+ * Returns the disposition of the photo frames in the wallpaper. The setting is
+ * stored as <code>0x0:1x2|2x2:3x4|...</code>, which it means (position x=0, y=0,
+ * 1 cells width, 2 cells height, ...).
+ *
+ * @return List<Disposition> The photo frames dispositions
+ */
+ public static List<Disposition> getDisposition() {
+ String setting = getString("ui_layout_disposition", DEFAULT_DISPOSITION);
+ String[] v = setting.split("\\|");
+ List<Disposition> dispositions = new ArrayList<Disposition>(v.length);
+ for (String s : v) {
+ String[] s1 = s.split(":");
+ String[] s2 = s1[0].split("x");
+ String[] s3 = s1[1].split("x");
+ Disposition disposition = new Disposition();
+ disposition.x = Integer.parseInt(s2[0]);
+ disposition.y = Integer.parseInt(s2[1]);
+ disposition.w = Integer.parseInt(s3[0]);
+ disposition.h = Integer.parseInt(s3[1]);
+ dispositions.add(disposition);
+ }
+ return dispositions;
+ }
+ }
+
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java
new file mode 100644
index 0000000..e8b6d2e
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarPreference.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A preference with a seekbar widget
+ */
+public class SeekBarPreference extends Preference implements OnSeekBarChangeListener {
+
+ private int mProgress;
+ private int mMax;
+ private boolean mTrackingTouch;
+
+ /**
+ * Constructor of <code>SeekBarPreference</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the view
+ * @param defStyle The resource with the style
+ */
+ public SeekBarPreference(
+ Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setMax(100);
+ setLayoutResource(R.layout.preference_widget_seekbar);
+ }
+
+ /**
+ * Constructor of <code>SeekBarPreference</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the view
+ */
+ public SeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Constructor of <code>SeekBarPreference</code>
+ *
+ * @param context The current context
+ */
+ public SeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar);
+ seekBar.setOnSeekBarChangeListener(this);
+ seekBar.setMax(mMax);
+ seekBar.setProgress(mProgress);
+ seekBar.setEnabled(isEnabled());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("boxing")
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setProgress(restoreValue ? getPersistedInt(mProgress) : (Integer) defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("boxing")
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, 0);
+ }
+
+ /**
+ * Allows a Preference to intercept key events without having focus.
+ * For example, SeekBarPreference uses this to intercept +/- to adjust
+ * the progress.
+ *
+ * @param v The view
+ * @param keyCode The key code
+ * @param event The key event
+ * @return True if the Preference handled the key. Returns false by default.
+ */
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_UP) {
+ if (keyCode == KeyEvent.KEYCODE_PLUS
+ || keyCode == KeyEvent.KEYCODE_EQUALS) {
+ setProgress(getProgress() + 1);
+ return true;
+ }
+ if (keyCode == KeyEvent.KEYCODE_MINUS) {
+ setProgress(getProgress() - 1);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that set the maximum progress
+ *
+ * @param max The maximum progress
+ */
+ public void setMax(int max) {
+ if (max != mMax) {
+ mMax = max;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Method that set the actual progress
+ *
+ * @param progress The actual progress
+ */
+ public void setProgress(int progress) {
+ setProgress(progress, true);
+ }
+
+ /**
+ * Method that set the actual progress
+ *
+ * @param progress The actual progress
+ * @param notifyChanged Whether notify if the progress was changed
+ */
+ protected void setProgress(int progress, boolean notifyChanged) {
+ int p = progress;
+ if (p > mMax) {
+ p = mMax;
+ }
+ if (p < 0) {
+ p = 0;
+ }
+ if (p != mProgress) {
+ mProgress = p;
+ persistInt(p);
+ if (notifyChanged) {
+ notifyChanged();
+ }
+ }
+ }
+
+ /**
+ * Method that returns the current progress
+ *
+ * @return int The current progress
+ */
+ public int getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Persist the seekBar's progress value if callChangeListener
+ *
+ * returns boolean True, otherwise set the seekBar's progress to the stored value
+ */
+ @SuppressWarnings("boxing")
+ void syncProgress(SeekBar seekBar) {
+ int progress = seekBar.getProgress();
+ if (progress != mProgress) {
+ if (callChangeListener(progress)) {
+ setProgress(progress, false);
+ } else {
+ seekBar.setProgress(mProgress);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser && !mTrackingTouch) {
+ syncProgress(seekBar);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = false;
+ if (seekBar.getProgress() != mProgress) {
+ syncProgress(seekBar);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ /*
+ * Suppose a client uses this preference type without persisting. We
+ * must save the instance state so it is able to, for example, survive
+ * orientation changes.
+ */
+
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ // Save the instance state
+ final SavedState myState = new SavedState(superState);
+ myState.progress = mProgress;
+ myState.max = mMax;
+ return myState;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ // Restore the instance state
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ mProgress = myState.progress;
+ mMax = myState.max;
+ notifyChanged();
+ }
+
+ /**
+ * SavedState, a subclass of {@link BaseSavedState}, will store the state
+ * of MyPreference, a subclass of Preference.
+ * <p>
+ * It is important to always call through to super methods.
+ */
+ private static class SavedState extends BaseSavedState {
+ int progress;
+ int max;
+
+ public SavedState(Parcel source) {
+ super(source);
+
+ // Restore the click counter
+ progress = source.readInt();
+ max = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+
+ // Save the click counter
+ dest.writeInt(progress);
+ dest.writeInt(max);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @SuppressWarnings({"unused", "hiding"})
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java
new file mode 100644
index 0000000..64b9b90
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/SeekBarProgressPreference.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.preferences;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A preference with a seekbar widget that display the progress
+ */
+public class SeekBarProgressPreference extends SeekBarPreference {
+
+ /**
+ * Interface to intercept the progress value to display on screen
+ */
+ public interface OnDisplayProgress {
+ /**
+ * Method invoked when a progress value is going to display on screen
+ *
+ * @param progress The real progress
+ * @return The progress to display
+ */
+ String onDisplayProgress(int progress);
+ }
+
+ private String mFormat;
+ private OnDisplayProgress mOnDisplayProgress;
+
+ TextView mTextView;
+
+ /**
+ * Constructor of <code>SeekBarProgressPreference</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the view
+ * @param defStyle The resource with the style
+ */
+ public SeekBarProgressPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Constructor of <code>SeekBarProgressPreference</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the view
+ */
+ public SeekBarProgressPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>SeekBarProgressPreference</code>
+ *
+ * @param context The current context
+ */
+ public SeekBarProgressPreference(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Method that initializes the preference
+ */
+ void init() {
+ mFormat = "%s";
+ mOnDisplayProgress = null;
+ setWidgetLayoutResource(R.layout.preference_widget_seekbar_progress);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mTextView = (TextView) view.findViewById(R.id.text);
+ setText();
+ }
+
+ /**
+ * Method that set the actual progress
+ *
+ * @param progress The actual progress
+ * @param notifyChanged Whether notify if the progress was changed
+ */
+ @Override
+ protected void setProgress(int progress, boolean notifyChanged) {
+ super.setProgress(progress, notifyChanged);
+ setText();
+ }
+
+ /**
+ * Method that displays the progress value
+ */
+ private void setText() {
+ if (mTextView != null) {
+ String value = String.valueOf(getProgress());
+ if (mOnDisplayProgress != null) {
+ value = mOnDisplayProgress.onDisplayProgress(getProgress());
+ }
+ mTextView.setText(String.format(mFormat, value));
+ }
+ }
+
+ /**
+ * Method that sets the callback to intercept the progress value before it will be
+ * displayed on screen.
+ *
+ * @param onDisplayProgress The callback
+ */
+ public void setOnDisplayProgress(OnDisplayProgress onDisplayProgress) {
+ this.mOnDisplayProgress = onDisplayProgress;
+ }
+
+ /**
+ * Method that set the format of the progress
+ *
+ * @param format The format of the string progress
+ */
+ public void setFormat(String format) {
+ mFormat = format;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java
new file mode 100644
index 0000000..027d089
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/ColorShape.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.shapes;
+
+import android.content.Context;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.GLESUtil.GLColor;
+import org.cyanogenmod.wallpapers.photophase.R;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A shape plus color.
+ */
+public class ColorShape implements DrawableShape {
+
+ private int mProgramHandler;
+ private int mPositionHandler;
+ private int mColorHandler;
+ private int mMatrixHandler;
+ private FloatBuffer mVertexBuffer;
+ private ShortBuffer mVertexOrderBuffer;
+
+ private final GLColor mColor;
+
+ /**
+ * Constructor of <code>ColorShape</code>.
+ *
+ * @param ctx The current context
+ * @param vertex The vertext data
+ * @param color The color
+ */
+ public ColorShape(Context ctx, float[] vertex, GLColor color) {
+ super();
+ mColor = color;
+
+ mProgramHandler =
+ GLESUtil.createProgram(
+ ctx.getResources(),
+ R.raw.color_vertex_shader,
+ R.raw.color_fragment_shader);
+ mPositionHandler = GLES20.glGetAttribLocation(mProgramHandler, "aPosition");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mColorHandler = GLES20.glGetAttribLocation(mProgramHandler, "aColor");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mMatrixHandler = GLES20.glGetUniformLocation(mProgramHandler, "uMVPMatrix");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+
+ // Initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(vertex.length * 4); // (# of coordinate values * 4 bytes per float)
+ bb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = bb.asFloatBuffer();
+ mVertexBuffer.put(vertex);
+ mVertexBuffer.position(0);
+
+ // Initialize vertex byte buffer for shape coordinates order
+ final short[] order = { 0, 1, 2, 0, 2, 3 };
+ ByteBuffer dlb = ByteBuffer.allocateDirect(order.length * 2); // (# of coordinate values * 2 bytes per short)
+ dlb.order(ByteOrder.nativeOrder());
+ mVertexOrderBuffer = dlb.asShortBuffer();
+ mVertexOrderBuffer.put(order);
+ mVertexOrderBuffer.position(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void draw(float[] matrix) {
+ // Enable properties
+ if (mColor.a != 1.0f) {
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ // Set the program and its attributes
+ GLES20.glUseProgram(mProgramHandler);
+ GLESUtil.glesCheckError("glUseProgram");
+
+ // Position
+ mVertexBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mPositionHandler,
+ 3,
+ GLES20.GL_FLOAT,
+ false,
+ 3 * 4,
+ mVertexBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandler);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Color
+ GLES20.glVertexAttrib4f(mColorHandler, mColor.r, mColor.g, mColor.b, mColor.a);
+ GLESUtil.glesCheckError("glVertexAttrib4f");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw the photo frame
+ mVertexOrderBuffer.position(0);
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLE_FAN,
+ 6,
+ GLES20.GL_UNSIGNED_SHORT,
+ mVertexOrderBuffer);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandler);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mColorHandler);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+
+ // Disable properties
+ if (mColor.a != 1.0f) {
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLESUtil.glesCheckError("glDisable");
+ }
+ }
+
+ /**
+ * Method that sets the alpha color of the shape
+ *
+ * @param value The new alpha color of the shape
+ */
+ public void setAlpha(float value) {
+ mColor.a = value;
+ }
+
+ /**
+ * Method that destroy all the internal references
+ */
+ public void recycle() {
+ if (GLES20.glIsProgram(mProgramHandler)) {
+ GLES20.glDeleteProgram(mProgramHandler);
+ GLESUtil.glesCheckError("glDeleteProgram");
+ }
+ mProgramHandler = 0;
+ mPositionHandler = 0;
+ mColorHandler = 0;
+ mMatrixHandler = 0;
+ mVertexBuffer.clear();
+ mVertexOrderBuffer.clear();
+ mVertexBuffer = null;
+ mVertexOrderBuffer = null;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java
new file mode 100644
index 0000000..7866dcc
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/DrawableShape.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.shapes;
+
+/**
+ * An interface that defines an object as a drawable shape.
+ */
+public interface DrawableShape {
+
+ /**
+ * Method that request redraw of the shape.
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ void draw(float[] matrix);
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java b/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java
new file mode 100644
index 0000000..9668d22
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/tasks/AsyncPictureLoaderTask.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.tasks;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.widget.ImageView;
+
+import org.cyanogenmod.wallpapers.photophase.BitmapUtils;
+
+import java.io.File;
+
+/**
+ * A class for load images associated to a ImageView in background.
+ */
+public class AsyncPictureLoaderTask extends AsyncTask<File, Void, Drawable> {
+
+ private final Context mContext;
+ private final ImageView mView;
+
+ /**
+ * Constructor of <code>AsyncPictureLoaderTask</code>
+ *
+ * @param context The current context
+ * @param v The associated view
+ */
+ public AsyncPictureLoaderTask(Context context, ImageView v) {
+ super();
+ mContext = context;
+ mView = v;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Drawable doInBackground(File... params) {
+ int width = mView.getMeasuredWidth();
+ int height = mView.getMeasuredHeight();
+ Bitmap bitmap = BitmapUtils.decodeBitmap(params[0], width, height);
+ if (bitmap != null) {
+ return new BitmapDrawable(mContext.getResources(), bitmap);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPostExecute(Drawable result) {
+ mView.setImageDrawable(result);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java
new file mode 100644
index 0000000..d88aafc
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/FadeTransition.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+import android.opengl.GLException;
+import android.os.SystemClock;
+
+import org.cyanogenmod.wallpapers.photophase.Colors;
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.shapes.ColorShape;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+/**
+ * A transition that applies a fade transition to the picture.
+ */
+public class FadeTransition extends NullTransition {
+
+ private static final float TRANSITION_TIME = 800.0f;
+
+ private boolean mRunning;
+ private long mTime;
+
+ ColorShape mOverlay;
+
+ /**
+ * Constructor of <code>FadeTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public FadeTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.FADE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reset() {
+ super.reset();
+ mTime = -1;
+ mRunning = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+ mOverlay = new ColorShape(mContext, target.getPictureVertex(), Colors.getBackground());
+ mOverlay.setAlpha(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void apply(float[] matrix) throws GLException {
+ // Check internal vars
+ if (mTarget == null ||
+ mTarget.getPictureVertexBuffer() == null ||
+ mTarget.getTextureBuffer() == null ||
+ mTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPictureVertexBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == null ||
+ mTransitionTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+
+ // Set the time the first time
+ if (mTime == -1) {
+ mTime = SystemClock.uptimeMillis();
+ }
+
+ final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME;
+ if (delta <= 0.5) {
+ // Draw the src target
+ draw(mTarget, matrix);
+ mOverlay.setAlpha(delta * 2.0f);
+ } else {
+ // Draw the dst target
+ draw(mTransitionTarget, matrix);
+ mOverlay.setAlpha((1 - delta) * 2.0f);
+ }
+ mOverlay.draw(matrix);
+
+ // Transition ended
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java
new file mode 100644
index 0000000..66f89d1
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/NullTransition.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLException;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A special transition that does nothing other than draw the {@link PhotoFrame}
+ * on the screen continually. No transition is done.
+ */
+public class NullTransition extends Transition {
+
+ private static final int[] VERTEX_SHADER = {R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.default_fragment_shader};
+
+ /**
+ * Constructor of <code>NullTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public NullTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.NO_TRANSITION;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mTarget == null || !mTarget.isLoaded();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reset() {
+ // Nothing to do
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void apply(float[] matrix) throws GLException {
+ // Check internal vars
+ if (mTarget == null ||
+ mTarget.getPictureVertexBuffer() == null ||
+ mTarget.getTextureBuffer() == null ||
+ mTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+
+ // Draw the current target
+ draw(mTarget, matrix);
+ }
+
+ /**
+ * Method that draws the picture texture
+ *
+ * @param target
+ * @param matrix The model-view-projection matrix
+ */
+ protected void draw(PhotoFrame target, float[] matrix) {
+ // Set the program
+ useProgram(0);
+
+ // Bind the texture
+ int textureHandle = target.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Position
+ FloatBuffer vertexBuffer = target.getPictureVertexBuffer();
+ vertexBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mPositionHandlers[0],
+ PhotoFrame.COORDS_PER_VERTER,
+ GLES20.GL_FLOAT,
+ false,
+ PhotoFrame.COORDS_PER_VERTER * 4,
+ vertexBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Texture
+ FloatBuffer textureBuffer = target.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mTextureCoordHandlers[0],
+ 2,
+ GLES20.GL_FLOAT,
+ false,
+ 2 * 4,
+ textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Apply the projection and view transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, matrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw the photo frame
+ ShortBuffer vertexOrderBuffer = target.getVertexOrderBuffer();
+ vertexOrderBuffer.position(0);
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLE_FAN,
+ 6,
+ GLES20.GL_UNSIGNED_SHORT,
+ vertexOrderBuffer);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java
new file mode 100644
index 0000000..d100110
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/SwapTransition.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+import android.opengl.GLException;
+import android.os.SystemClock;
+
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+/**
+ * A simple transition that swap an image after the transition time is ended.
+ */
+public class SwapTransition extends NullTransition {
+
+ private static final float TRANSITION_TIME = 250.0f;
+
+ private boolean mRunning;
+ private long mTime;
+
+ /**
+ * Constructor of <code>SwapTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public SwapTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.SWAP;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reset() {
+ super.reset();
+ mTime = -1;
+ mRunning = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void apply(float[] matrix) throws GLException {
+ // Check internal vars
+ if (mTarget == null ||
+ mTarget.getPictureVertexBuffer() == null ||
+ mTarget.getTextureBuffer() == null ||
+ mTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPictureVertexBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == null ||
+ mTransitionTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+
+ // Set the time the first time
+ if (mTime == -1) {
+ mTime = SystemClock.uptimeMillis();
+ }
+
+ // Calculate the delta time
+ final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME;
+
+ // Apply the transition
+ boolean ended = delta == 1;
+ draw(ended ? mTransitionTarget : mTarget, matrix);
+ mRunning = !ended;
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java
new file mode 100644
index 0000000..315a9f8
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transition.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+import android.opengl.GLES20;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+/**
+ * The base class of all transitions that can be applied to the {@link PhotoFrame} classes.
+ */
+public abstract class Transition {
+
+ public static final long MAX_TRANSTION_TIME = 1500L;
+
+ protected final Context mContext;
+ private final TextureManager mTextureManager;
+
+ protected int[] mProgramHandlers;
+ protected int[] mPositionHandlers;
+ protected int[] mTextureCoordHandlers;
+ protected int[] mMVPMatrixHandlers;
+
+ protected PhotoFrame mTarget;
+ protected PhotoFrame mTransitionTarget;
+
+ private final int[] mVertexShader;
+ private final int[] mFragmentShader;
+
+ /**
+ * Constructor of <code>Transition</code>
+ *
+ * @param ctx The current context
+ * @param tm The current texture manager
+ * @param vertexShader The vertex shaders of the programs
+ * @param fragmentShader The fragment shaders of the programs
+ */
+ public Transition(Context ctx, TextureManager tm, int[] vertexShader, int[] fragmentShader) {
+ super();
+ mContext = ctx;
+ mTextureManager = tm;
+ mVertexShader = vertexShader;
+ mFragmentShader = fragmentShader;
+
+ // Compile every program
+ assert mVertexShader.length != mFragmentShader.length;
+ int cc = mVertexShader.length;
+ mProgramHandlers = new int[cc];
+ mPositionHandlers = new int[cc];
+ mTextureCoordHandlers = new int[cc];
+ mMVPMatrixHandlers = new int[cc];
+ for (int i = 0; i < cc; i++) {
+ createProgram(i);
+ }
+ }
+
+ /**
+ * Method that requests to apply this transition.
+ *
+ * @param target The target photo frame
+ */
+ public void select(PhotoFrame target) {
+ mTarget = target;
+ if (hasTransitionTarget()) {
+ // Load the transition frame and request a picture for it
+ mTransitionTarget =
+ new PhotoFrame(
+ mContext,
+ mTextureManager,
+ mTarget.getFrameVertex(),
+ mTarget.getPictureVertex(),
+ mTarget.getBackgroundColor());
+ }
+ }
+
+ /**
+ * Method that returns the target of the transition.
+ *
+ * @return PhotoFrame The target of the transition
+ */
+ public PhotoFrame getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Method that returns the transition target of the transition.
+ *
+ * @return PhotoFrame The transition target of the transition
+ */
+ public PhotoFrame getTransitionTarget() {
+ return mTransitionTarget;
+ }
+
+ /**
+ * Method that returns if the transition is selectable for the passed frame.
+ *
+ * @param frame The frame which the transition should be applied to
+ * @return boolean If the transition is selectable for the passed frame
+ */
+ public abstract boolean isSelectable(PhotoFrame frame);
+
+ /**
+ * Method that resets the current status of the transition.
+ */
+ public abstract void reset();
+
+ /**
+ * Method that returns the type of transition.
+ *
+ * @return TRANSITIONS The type of transition
+ */
+ public abstract TRANSITIONS getType();
+
+ /**
+ * Method that requests to apply this transition.
+ *
+ * @param matrix The model-view-projection matrix
+ */
+ public abstract void apply(float[] matrix);
+
+ /**
+ * Method that returns if the transition is being transition.
+ *
+ * @return boolean If the transition is being transition.
+ */
+ public abstract boolean isRunning();
+
+ /**
+ * Method that return if the transition has a secondary target
+ *
+ * @return boolean If the transition has a secondary target
+ */
+ public abstract boolean hasTransitionTarget();
+
+ /**
+ * Method that creates the program
+ */
+ protected void createProgram(int index) {
+ mProgramHandlers[index] =
+ GLESUtil.createProgram(
+ mContext.getResources(), mVertexShader[index], mFragmentShader[index]);
+ mPositionHandlers[index] =
+ GLES20.glGetAttribLocation(mProgramHandlers[index], "aPosition");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mTextureCoordHandlers[index] =
+ GLES20.glGetAttribLocation(mProgramHandlers[index], "aTextureCoord");
+ GLESUtil.glesCheckError("glGetAttribLocation");
+ mMVPMatrixHandlers[index] =
+ GLES20.glGetUniformLocation(mProgramHandlers[index], "uMVPMatrix");
+ GLESUtil.glesCheckError("glGetUniformLocation");
+ }
+
+ /**
+ * Method that set the program to use
+ *
+ * @param index The index of the program to use
+ */
+ protected void useProgram(int index) {
+ if (!GLES20.glIsProgram(mProgramHandlers[index])) {
+ createProgram(index);
+ }
+ GLES20.glUseProgram(mProgramHandlers[index]);
+ GLESUtil.glesCheckError("glUseProgram(" + index + ")");
+ }
+
+ /**
+ * Method that requests to the transition to remove its internal references and resources.
+ */
+ public void recycle() {
+ int cc = mProgramHandlers.length;
+ for (int i = 0; i < cc; i++) {
+ if (GLES20.glIsProgram(mProgramHandlers[i])) {
+ GLES20.glDeleteProgram(mProgramHandlers[i]);
+ GLESUtil.glesCheckError("glDeleteProgram");
+ }
+ mProgramHandlers[i] = 0;
+ mPositionHandlers[i] = 0;
+ mTextureCoordHandlers[i] = 0;
+ mMVPMatrixHandlers[i] = 0;
+ }
+ mTransitionTarget = null;
+ mTarget = null;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java
new file mode 100644
index 0000000..e29bfce
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/Transitions.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
+
+
+/**
+ * A class that manages all the supported transitions
+ */
+public class Transitions {
+
+ /**
+ * Enumeration of the supported transitions
+ */
+ public enum TRANSITIONS {
+ /**
+ * A random combination of all supported transitions
+ */
+ RANDOM,
+ /**
+ * @see NullTransition
+ */
+ NO_TRANSITION,
+ /**
+ * @see SwapTransition
+ */
+ SWAP,
+ /**
+ * @see FadeTransition
+ */
+ FADE,
+ /**
+ * @see TranslateTransition
+ */
+ TRANSLATION;
+ }
+
+ /**
+ * Method that return the next type of transition to apply the picture.
+ *
+ * @param frame The frame which the effect will be applied to
+ * @return TRANSITIONS The next type of transition to apply
+ */
+ public static TRANSITIONS getNextTypeOfTransition(PhotoFrame frame) {
+ int transition = Preferences.General.Transitions.getTransitionTypes();
+ if (transition == TRANSITIONS.RANDOM.ordinal()) {
+ int low = TRANSITIONS.SWAP.ordinal();
+ int hight = TRANSITIONS.values().length - 1;
+ transition = low + (int)(Math.random() * ((hight - low) + 1));
+ }
+ if (transition == TRANSITIONS.SWAP.ordinal()) {
+ return TRANSITIONS.SWAP;
+ }
+ if (transition == TRANSITIONS.FADE.ordinal()) {
+ return TRANSITIONS.FADE;
+ }
+ if (transition == TRANSITIONS.TRANSLATION.ordinal()) {
+ return TRANSITIONS.TRANSLATION;
+ }
+ return TRANSITIONS.NO_TRANSITION;
+ }
+
+ /**
+ * Method that creates a new transition.
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ * @param type The type of transition
+ * @param frame The frame which the effect will be applied to
+ * @return Transition The next transition to apply
+ */
+ public static Transition createTransition(
+ Context ctx, TextureManager tm, TRANSITIONS type, PhotoFrame frame) {
+ if (type.compareTo(TRANSITIONS.SWAP) == 0) {
+ return new SwapTransition(ctx, tm);
+ }
+ if (type.compareTo(TRANSITIONS.FADE) == 0) {
+ return new FadeTransition(ctx, tm);
+ }
+ if (type.compareTo(TRANSITIONS.TRANSLATION) == 0) {
+ return new TranslateTransition(ctx, tm);
+ }
+ return new NullTransition(ctx, tm);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java b/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java
new file mode 100644
index 0000000..4e32a4a
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/transitions/TranslateTransition.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.transitions;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLException;
+import android.opengl.Matrix;
+import android.os.SystemClock;
+
+import org.cyanogenmod.wallpapers.photophase.GLESUtil;
+import org.cyanogenmod.wallpapers.photophase.PhotoFrame;
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.TextureManager;
+import org.cyanogenmod.wallpapers.photophase.transitions.Transitions.TRANSITIONS;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A transition that applies a translation transition to the picture.
+ */
+public class TranslateTransition extends Transition {
+
+ /**
+ * The enumeration of all possibles translations movements
+ */
+ public enum TRANSLATE_MODES {
+ /**
+ * Translate the picture from left to right
+ */
+ LEFT_TO_RIGHT,
+ /**
+ * Translate the picture from right to left
+ */
+ RIGHT_TO_LEFT,
+ /**
+ * Translate the picture from up to down
+ */
+ UP_TO_DOWN,
+ /**
+ * Translate the picture from down to up
+ */
+ DOWN_TO_UP
+ }
+
+ private static final int[] VERTEX_SHADER = {R.raw.translate_vertex_shader, R.raw.default_vertex_shader};
+ private static final int[] FRAGMENT_SHADER = {R.raw.translate_fragment_shader, R.raw.default_fragment_shader};
+
+ private static final float TRANSITION_TIME = 800.0f;
+
+ private TRANSLATE_MODES mMode;
+
+ private boolean mRunning;
+ private long mTime;
+
+ /**
+ * Constructor of <code>TranslateTransition</code>
+ *
+ * @param ctx The current context
+ * @param tm The texture manager
+ */
+ public TranslateTransition(Context ctx, TextureManager tm) {
+ super(ctx, tm, VERTEX_SHADER, FRAGMENT_SHADER);
+
+ // Initialized
+ reset();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TRANSITIONS getType() {
+ return TRANSITIONS.TRANSLATION;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTransitionTarget() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void select(PhotoFrame target) {
+ super.select(target);
+
+ // Discard all non-supported modes
+ List<TRANSLATE_MODES> modes =
+ new ArrayList<TranslateTransition.TRANSLATE_MODES>(
+ Arrays.asList(TRANSLATE_MODES.values()));
+ float[] vertex = target.getFrameVertex();
+ if (vertex[0] != -1.0f) {
+ modes.remove(TRANSLATE_MODES.RIGHT_TO_LEFT);
+ }
+ if (vertex[9] != 1.0f) {
+ modes.remove(TRANSLATE_MODES.LEFT_TO_RIGHT);
+ }
+ if (vertex[1] != 1.0f) {
+ modes.remove(TRANSLATE_MODES.DOWN_TO_UP);
+ }
+ if (vertex[4] != -1.0f) {
+ modes.remove(TRANSLATE_MODES.UP_TO_DOWN);
+ }
+
+ // Random mode
+ int low = 0;
+ int hight = modes.size() - 1;
+ mMode = modes.get(low + (int)(Math.random() * ((hight - low) + 1)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSelectable(PhotoFrame frame) {
+ float[] vertex = frame.getFrameVertex();
+ if (vertex[0] == -1.0f || vertex[9] == 1.0f ||
+ vertex[1] == 1.0f || vertex[4] == -1.0f) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reset() {
+ mTime = -1;
+ mRunning = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void apply(float[] matrix) throws GLException {
+ // Check internal vars
+ if (mTarget == null ||
+ mTarget.getPictureVertexBuffer() == null ||
+ mTarget.getTextureBuffer() == null ||
+ mTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+ if (mTransitionTarget == null ||
+ mTransitionTarget.getPictureVertexBuffer() == null ||
+ mTransitionTarget.getTextureBuffer() == null ||
+ mTransitionTarget.getVertexOrderBuffer() == null) {
+ return;
+ }
+
+ // Set the time the first time
+ if (mTime == -1) {
+ mTime = SystemClock.uptimeMillis();
+ }
+
+ // Calculate the delta time
+ final float delta = Math.min(SystemClock.uptimeMillis() - mTime, TRANSITION_TIME) / TRANSITION_TIME;
+
+ // Apply the transition
+ applyTransitionToTransitionTarget(delta, matrix);
+ if (delta < 1) {
+ applyTransitionToTarget(delta, matrix);
+ }
+
+ // Transition ending
+ if (delta == 1) {
+ mRunning = false;
+ }
+ }
+
+ /**
+ * Apply the transition to the passed frame
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyTransitionToTarget(float delta, float[] matrix) {
+ // Set the program
+ useProgram(0);
+
+ // Bind the texture
+ int textureHandle = mTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Position
+ FloatBuffer vertexBuffer = mTarget.getPictureVertexBuffer();
+ vertexBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mPositionHandlers[0],
+ PhotoFrame.COORDS_PER_VERTER,
+ GLES20.GL_FLOAT,
+ false,
+ PhotoFrame.COORDS_PER_VERTER * 4,
+ vertexBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Texture
+ FloatBuffer textureBuffer = mTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mTextureCoordHandlers[0],
+ 2,
+ GLES20.GL_FLOAT,
+ false,
+ 2 * 4,
+ textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Calculate the delta distance
+ float distance =
+ (mMode.compareTo(TRANSLATE_MODES.LEFT_TO_RIGHT) == 0 || mMode.compareTo(TRANSLATE_MODES.RIGHT_TO_LEFT) == 0)
+ ? mTarget.getPictureWidth()
+ : mTarget.getPictureHeight();
+ if (mMode.compareTo(TRANSLATE_MODES.RIGHT_TO_LEFT) == 0 || mMode.compareTo(TRANSLATE_MODES.DOWN_TO_UP) == 0) {
+ distance *= -1;
+ }
+ distance *= delta;
+ boolean vertical = (mMode.compareTo(TRANSLATE_MODES.UP_TO_DOWN) == 0 || mMode.compareTo(TRANSLATE_MODES.DOWN_TO_UP) == 0);
+
+ // Apply the projection and view transformation
+ float[] translationMatrix = new float[16];
+ if (vertical) {
+ Matrix.translateM(translationMatrix, 0, matrix, 0, 0.0f, distance, 0.0f);
+ } else {
+ Matrix.translateM(translationMatrix, 0, matrix, 0, distance, 0.0f, 0.0f);
+ }
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[0], 1, false, translationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw the photo frame
+ ShortBuffer vertexOrderBuffer = mTarget.getVertexOrderBuffer();
+ vertexOrderBuffer.position(0);
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLE_FAN,
+ 6,
+ GLES20.GL_UNSIGNED_SHORT,
+ vertexOrderBuffer);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[0]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+ /**
+ * Apply the transition to the passed frame
+ *
+ * @param delta The delta time
+ * @param matrix The model-view-projection matrix
+ */
+ private void applyTransitionToTransitionTarget(float delta, float[] matrix) {
+ // Set the program
+ useProgram(1);
+
+ // Bind the texture
+ int textureHandle = mTransitionTarget.getTextureHandle();
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLESUtil.glesCheckError("glActiveTexture");
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
+ GLESUtil.glesCheckError("glBindTexture");
+
+ // Position
+ FloatBuffer vertexBuffer = mTransitionTarget.getPictureVertexBuffer();
+ vertexBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mPositionHandlers[1],
+ PhotoFrame.COORDS_PER_VERTER,
+ GLES20.GL_FLOAT,
+ false,
+ PhotoFrame.COORDS_PER_VERTER * 4,
+ vertexBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Texture
+ FloatBuffer textureBuffer = mTransitionTarget.getTextureBuffer();
+ textureBuffer.position(0);
+ GLES20.glVertexAttribPointer(
+ mTextureCoordHandlers[1],
+ 2,
+ GLES20.GL_FLOAT,
+ false,
+ 2 * 4,
+ textureBuffer);
+ GLESUtil.glesCheckError("glVertexAttribPointer");
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glEnableVertexAttribArray");
+
+ // Apply the projection and view transformation
+ float[] translationMatrix = new float[16];
+ Matrix.translateM(translationMatrix, 0, matrix, 0, 0.0f, 0.0f, 0.0f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandlers[1], 1, false, translationMatrix, 0);
+ GLESUtil.glesCheckError("glUniformMatrix4fv");
+
+ // Draw the photo frame
+ ShortBuffer vertexOrderBuffer = mTransitionTarget.getVertexOrderBuffer();
+ vertexOrderBuffer.position(0);
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLE_FAN,
+ 6,
+ GLES20.GL_UNSIGNED_SHORT,
+ vertexOrderBuffer);
+ GLESUtil.glesCheckError("glDrawElements");
+
+ // Disable attributes
+ GLES20.glDisableVertexAttribArray(mPositionHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandlers[1]);
+ GLESUtil.glesCheckError("glDisableVertexAttribArray");
+ }
+
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java
new file mode 100644
index 0000000..0711297
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumInfo.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.widgets;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.AsyncTask.Status;
+import android.util.AttributeSet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.model.Album;
+import org.cyanogenmod.wallpapers.photophase.tasks.AsyncPictureLoaderTask;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A view that contains the info about an album
+ */
+public class AlbumInfo extends RelativeLayout
+ implements OnClickListener, OnMenuItemClickListener {
+
+ /**
+ * A convenient listener for receive events of the AlbumPictures class
+ *
+ */
+ public interface CallbacksListener {
+ /**
+ * Invoked when an album was selected
+ *
+ * @param album The album
+ */
+ void onAlbumSelected(Album album);
+
+ /**
+ * Invoked when an album was deselected
+ *
+ * @param album The album
+ */
+ void onAlbumDeselected(Album album);
+ }
+
+ private List<CallbacksListener> mCallbacks;
+
+ /*package*/ Album mAlbum;
+
+ /*package*/ AsyncPictureLoaderTask mTask;
+
+ /*package*/ ImageView mIcon;
+ private TextView mSelectedItems;
+ private TextView mName;
+ private TextView mItems;
+ private View mOverflowButton;
+
+ /**
+ * Constructor of <code>AlbumInfo</code>.
+ *
+ * @param context The current context
+ */
+ public AlbumInfo(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Constructor of <code>AlbumInfo</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public AlbumInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>AlbumInfo</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view. If 0, no style
+ * will be applied (beyond what is included in the theme). This may
+ * either be an attribute resource, whose value will be retrieved
+ * from the current theme, or an explicit style resource.
+ */
+ public AlbumInfo(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Method that initializes the internal references
+ */
+ private void init() {
+ mCallbacks = new ArrayList<AlbumInfo.CallbacksListener>();
+ }
+
+ /**
+ * Method that adds the class that will be listen for events of this class
+ *
+ * @param callback The callback class
+ */
+ public void addCallBackListener(CallbacksListener callback) {
+ this.mCallbacks.add(callback);
+ }
+
+ /**
+ * Method that removes the class from the current callbacks
+ *
+ * @param callback The callback class
+ */
+ public void removeCallBackListener(CallbacksListener callback) {
+ this.mCallbacks.remove(callback);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mIcon = (ImageView)findViewById(R.id.album_thumbnail);
+ mSelectedItems = (TextView)findViewById(R.id.album_selected_items);
+ mName = (TextView)findViewById(R.id.album_name);
+ mItems = (TextView)findViewById(R.id.album_items);
+ mOverflowButton = findViewById(R.id.overflow);
+ mOverflowButton.setOnClickListener(this);
+
+ updateView(mAlbum);
+
+ post(new Runnable() {
+ @Override
+ public void run() {
+ // Show as icon, the first picture
+ mTask = new AsyncPictureLoaderTask(getContext(), mIcon);
+ mTask.execute(new File(mAlbum.getItems().get(0)));
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // Cancel pending tasks
+ if (mTask.getStatus().compareTo(Status.PENDING) == 0) {
+ mTask.cancel(true);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onClick(View v) {
+ if (v.equals(mOverflowButton)) {
+ PopupMenu popup = new PopupMenu(getContext(), v);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.album_actions, popup.getMenu());
+ onPreparePopupMenu(popup.getMenu());
+ popup.setOnMenuItemClickListener(this);
+ popup.show();
+ return;
+ }
+ }
+
+ /**
+ * Method called prior to show the popup menu
+ *
+ * @param popup The popup menu
+ */
+ public void onPreparePopupMenu(Menu popup) {
+ if (isSelected()) {
+ popup.findItem(R.id.mnu_select_album).setVisible(false);
+ } else {
+ popup.findItem(R.id.mnu_deselect_album).setVisible(false);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mnu_select_album:
+ doSelection(true);
+ break;
+
+ case R.id.mnu_deselect_album:
+ doSelection(false);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Method that select/deselect the album
+ *
+ * @param selected whether the album is selected
+ */
+ public void doSelection(boolean selected) {
+ setSelected(selected);
+ mAlbum.setSelected(selected);
+ mAlbum.setSelectedItems(new ArrayList<String>());
+ updateView(mAlbum);
+ notifySelectionChanged();
+ }
+
+ /**
+ * Method that notifies to all the registered callbacks that the selection
+ * was changed
+ */
+ private void notifySelectionChanged() {
+ for (CallbacksListener callback : mCallbacks) {
+ if (mAlbum.isSelected()) {
+ callback.onAlbumSelected(mAlbum);
+ } else {
+ callback.onAlbumDeselected(mAlbum);
+ }
+ }
+ }
+
+ /**
+ * Method that updates the view
+ *
+ * @param album The album data
+ */
+ @SuppressWarnings("boxing")
+ public void updateView(Album album) {
+ mAlbum = album;
+
+ if (mIcon != null) {
+ Resources res = getContext().getResources();
+
+ String count = String.valueOf(mAlbum.getSelectedItems().size());
+ if (mAlbum.getItems().size() > 99) {
+ count += "+";
+ }
+ mSelectedItems.setText(count);
+ mSelectedItems.setVisibility(mAlbum.isSelected() ? View.INVISIBLE : View.VISIBLE);
+ mName.setText(mAlbum.getName());
+ int items = mAlbum.getItems().size();
+ mItems.setText(String.format(res.getQuantityText(
+ R.plurals.album_number_of_pictures, items).toString(), items));
+ setSelected(album.isSelected());
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java
new file mode 100644
index 0000000..324fe15
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/AlbumPictures.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.model.Album;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A view that contains the pictures of an album
+ */
+public class AlbumPictures extends RelativeLayout
+ implements OnClickListener, OnMenuItemClickListener {
+
+ private static final int SELECTION_SELECT_ALL = 1;
+ private static final int SELECTION_DESELECT_ALL = 2;
+ private static final int SELECTION_INVERT = 3;
+
+ /**
+ * A convenient listener for receive events of the AlbumPictures class
+ *
+ */
+ public interface CallbacksListener {
+ /**
+ * Invoked when the user pressed the back button
+ */
+ void onBackButtonClick(View v);
+
+ /**
+ * Invoked when the selection was changed
+ *
+ * @param album The album
+ */
+ void onSelectionChanged(Album album);
+ }
+
+ private List<CallbacksListener> mCallbacks;
+
+ private PicturesView mScroller;
+ private LinearLayout mHolder;
+ private View mBackButton;
+ private View mOverflowButton;
+
+ private Album mAlbum;
+
+ /**
+ * Constructor of <code>AlbumPictures</code>.
+ *
+ * @param context The current context
+ */
+ public AlbumPictures(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Constructor of <code>AlbumPictures</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public AlbumPictures(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>AlbumPictures</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view. If 0, no style
+ * will be applied (beyond what is included in the theme). This may
+ * either be an attribute resource, whose value will be retrieved
+ * from the current theme, or an explicit style resource.
+ */
+ public AlbumPictures(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Method that initializes the internal references
+ */
+ private void init() {
+ mCallbacks = new ArrayList<AlbumPictures.CallbacksListener>();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mScroller = (PicturesView)findViewById(R.id.album_pictures_scroller);
+ mHolder = (LinearLayout)findViewById(R.id.album_pictures_holder);
+ mBackButton = findViewById(R.id.back);
+ mBackButton.setOnClickListener(this);
+ mOverflowButton = findViewById(R.id.overflow);
+ mOverflowButton.setOnClickListener(this);
+ TextView title = (TextView)findViewById(R.id.album_pictures_title);
+ title.setText(mAlbum.getName());
+
+ updateView(mAlbum);
+ }
+
+ /**
+ * Method that adds the class that will be listen for events of this class
+ *
+ * @param callback The callback class
+ */
+ public void addCallBackListener(CallbacksListener callback) {
+ this.mCallbacks.add(callback);
+ }
+
+ /**
+ * Method that removes the class from the current callbacks
+ *
+ * @param callback The callback class
+ */
+ public void removeCallBackListener(CallbacksListener callback) {
+ this.mCallbacks.remove(callback);
+ }
+
+ /**
+ * Method that set the data of the view
+ *
+ * @param album The album data
+ */
+ public void updateView(Album album) {
+ mAlbum = album;
+
+ if (mHolder != null) {
+ // Create the pictures
+ final LayoutInflater inflater = (LayoutInflater) getContext().
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mScroller.cancelTasks();
+ mHolder.removeAllViews();
+ for (final String picture : mAlbum.getItems()) {
+ View v = createPicture(inflater, picture, isPictureSelected(picture));
+ mHolder.addView(v);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onClick(View v) {
+ // Check which is the view pressed
+ if (v.equals(mBackButton)) {
+ for (CallbacksListener callback : mCallbacks) {
+ callback.onBackButtonClick(v);
+ }
+ return;
+ }
+ if (v.equals(mOverflowButton)) {
+ PopupMenu popup = new PopupMenu(getContext(), v);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.pictures_actions, popup.getMenu());
+ popup.setOnMenuItemClickListener(this);
+ popup.show();
+ return;
+ }
+
+ // A picture view
+ v.setSelected(!v.isSelected());
+ notifySelectionChanged();
+ }
+
+ /**
+ * Method that notifies to all the registered callbacks that the selection
+ * was changed
+ */
+ private void notifySelectionChanged() {
+ List<String> selection = new ArrayList<String>();
+ int count = mHolder.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = mHolder.getChildAt(i);
+ if (v.isSelected()) {
+ selection.add((String)v.getTag());
+ }
+ }
+ mAlbum.setSelectedItems(selection);
+ mAlbum.setSelected(false);
+
+ for (CallbacksListener callback : mCallbacks) {
+ callback.onSelectionChanged(mAlbum);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mnu_select_all:
+ doSelection(SELECTION_SELECT_ALL);
+ break;
+
+ case R.id.mnu_deselect_all:
+ doSelection(SELECTION_DESELECT_ALL);
+ break;
+
+ case R.id.mnu_invert_selection:
+ doSelection(SELECTION_INVERT);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Operate over the selection of the pictures of this album.
+ *
+ * @param action Takes the next values:
+ * <ul>
+ * <li>SELECTION_SELECT_ALL: select all</li>
+ * <li>SELECTION_DESELECT_ALL: deselect all</li>
+ * <li>SELECTION_INVERT: invert selection</li>
+ * </ul>
+ */
+ private void doSelection(int action) {
+ int count = mHolder.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = mHolder.getChildAt(i);
+
+ boolean selected = true;
+ if (action == SELECTION_DESELECT_ALL) {
+ selected = false;
+ } else if (action == SELECTION_INVERT) {
+ selected = !v.isSelected();
+ }
+ v.setSelected(selected);
+ }
+ notifySelectionChanged();
+ }
+
+ /**
+ * Method invoked when the view is displayed
+ */
+ public void onShow() {
+ mScroller.requestLoadOfPendingPictures();
+ }
+
+ /**
+ * Method that creates a new picture view
+ *
+ * @param inflater The inflater of the parent view
+ * @param picture The path of the picture
+ * @param selected If the picture is selected
+ */
+ private View createPicture(LayoutInflater inflater, String picture, boolean selected) {
+ final View v = inflater.inflate(R.layout.picture_item, mHolder, false);
+ v.setTag(picture);
+ v.setSelected(selected);
+ v.setOnClickListener(this);
+ return v;
+ }
+
+ /**
+ * Method that check if a picture is selected
+ *
+ * @param picture The picture to check
+ * @return boolean whether the picture is selected
+ */
+ private boolean isPictureSelected(String picture) {
+ for (String item : mAlbum.getSelectedItems()) {
+ if (item.compareTo(picture) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java
new file mode 100644
index 0000000..386ffbe
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/CardLayout.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A "Google Now Card Layout" like layout
+ */
+public class CardLayout extends LinearLayout {
+
+ boolean inverted = false;
+
+ /**
+ * Constructor of <code>CardLayout</code>.
+ *
+ * @param context The current context
+ */
+ public CardLayout(Context context) {
+ super(context);
+ }
+
+ /**
+ * Constructor of <code>CardLayout</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public CardLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Constructor of <code>CardLayout</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view. If 0, no style
+ * will be applied (beyond what is included in the theme). This may
+ * either be an attribute resource, whose value will be retrieved
+ * from the current theme, or an explicit style resource.
+ */
+ public CardLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (isHardwareAccelerated()) {
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ /**
+ * Add a new card to the layout
+ *
+ * @param card The card view to add
+ */
+ public void addCard(final View card) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ addView(card);
+ card.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.cards_animation));
+ }
+ });
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java
new file mode 100644
index 0000000..42e701d
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/ColorPickerPreference.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.widgets;
+
+import afzkl.development.mColorPicker.views.ColorDialogView;
+import afzkl.development.mColorPicker.views.ColorPanelView;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+
+/**
+ * A {@link Preference} that allow to select/pick a color in a new window dialog.
+ */
+public class ColorPickerPreference extends DialogPreference {
+
+ private ColorPanelView mColorPicker;
+ private int mColor;
+
+ private ColorDialogView mColorDlg;
+
+ /**
+ * Constructor of <code>ColorPickerPreference</code>
+ *
+ * @param context The current context
+ */
+ public ColorPickerPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor of <code>ColorPickerPreference</code>
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the preference.
+ */
+ public ColorPickerPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.color_picker_pref_item);
+ }
+
+ /**
+ * Returns the color of the picker.
+ *
+ * @return The color of the picker.
+ */
+ public int getColor() {
+ return this.mColor;
+ }
+
+ /**
+ * Sets the color of the picker and saves it to the {@link SharedPreferences}.
+ *
+ * @param color The new color.
+ */
+ public void setColor(int color) {
+ // Always persist/notify the first time; don't assume the field's default of false.
+ final boolean changed = this.mColor != color;
+ if (changed) {
+ this.mColor = color;
+ // when called from onSetInitialValue the view is still not set
+ if (this.mColorPicker != null) {
+ this.mColorPicker.setColor(color);
+ }
+ persistInt(color);
+ if (changed) {
+ notifyDependencyChange(shouldDisableDependents());
+ notifyChanged();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return Integer.valueOf(a.getColor(index, 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setColor(restoreValue ? getPersistedInt(0) : ((Integer)defaultValue).intValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+
+ // Configure the dialog
+ this.mColorDlg = new ColorDialogView(getContext());
+ this.mColorDlg.setColor(this.mColor);
+ this.mColorDlg.showAlphaSlider(true);
+ this.mColorDlg.setAlphaSliderText(
+ getContext().getString(R.string.color_picker_alpha_slider_text));
+ this.mColorDlg.setCurrentColorText(
+ getContext().getString(R.string.color_picker_current_text));
+ this.mColorDlg.setNewColorText(
+ getContext().getString(R.string.color_picker_new_text));
+ this.mColorDlg.setColorLabelText(
+ getContext().getString(R.string.color_picker_color));
+ builder.setView(this.mColorDlg);
+
+ // The color is selected by the user and confirmed by clicking ok
+ builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void onClick(DialogInterface dialog, int which) {
+ int color = ColorPickerPreference.this.mColorDlg.getColor();
+ if (callChangeListener(Integer.valueOf(color))) {
+ setColor(color);
+ }
+ dialog.dismiss();
+ }
+ });
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ View v = view.findViewById(R.id.color_picker);
+ if (v != null && v instanceof ColorPanelView) {
+ this.mColorPicker = (ColorPanelView)v;
+ this.mColorPicker.setColor(this.mColor);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.color = getColor();
+ return myState;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setColor(myState.color);
+ }
+
+ /**
+ * A class for managing the instance state of a {@link ColorPickerPreference}.
+ */
+ static class SavedState extends BaseSavedState {
+ int color;
+
+ /**
+ * Constructor of <code>SavedState</code>
+ *
+ * @param source The source
+ */
+ public SavedState(Parcel source) {
+ super(source);
+ this.color = source.readInt();
+ }
+
+ /**
+ * Constructor of <code>SavedState</code>
+ *
+ * @param superState The parcelable state
+ */
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.color);
+ }
+
+ /**
+ * A class that generates instances of the <code>SavedState</code> class from a Parcel.
+ */
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java
new file mode 100644
index 0000000..bb9332c
--- /dev/null
+++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/PicturesView.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpapers.photophase.widgets;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.AsyncTask.Status;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+
+import org.cyanogenmod.wallpapers.photophase.R;
+import org.cyanogenmod.wallpapers.photophase.tasks.AsyncPictureLoaderTask;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * A view that contains all the pictures of an album
+ */
+public class PicturesView extends HorizontalScrollView {
+
+ private HashMap<File, AsyncPictureLoaderTask> mTasks;
+
+ /**
+ * Constructor of <code>PicturesView</code>.
+ *
+ * @param context The current context
+ */
+ public PicturesView(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Constructor of <code>PicturesView</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public PicturesView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Constructor of <code>PicturesView</code>.
+ *
+ * @param context The current context
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view. If 0, no style
+ * will be applied (beyond what is included in the theme). This may
+ * either be an attribute resource, whose value will be retrieved
+ * from the current theme, or an explicit style resource.
+ */
+ public PicturesView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Method that initializes the structures of this class
+ */
+ private void init() {
+ mTasks = new HashMap<File, AsyncPictureLoaderTask>();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ cancelTasks();
+ }
+
+ /**
+ * Method that removes all tasks
+ */
+ public void cancelTasks() {
+ // Cancel all the pending task
+ Iterator<AsyncPictureLoaderTask> it = mTasks.values().iterator();
+ while (it.hasNext()) {
+ AsyncPictureLoaderTask task = it.next();
+ if (task.getStatus().compareTo(Status.PENDING) == 0) {
+ task.cancel(true);
+ }
+ }
+ mTasks.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ requestLoadOfPendingPictures();
+ }
+
+ /**
+ * Method that load in background all visible and pending pictures
+ */
+ public void requestLoadOfPendingPictures() {
+ // Get the visible rect
+ Rect r = new Rect();
+ getHitRect(r);
+
+ // Get all the image views
+ ViewGroup vg = (ViewGroup)getChildAt(0);
+ int count = vg.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ViewGroup picView = (ViewGroup)vg.getChildAt(i);
+ File image = new File((String)picView.getTag());
+ if (picView.getLocalVisibleRect(r) && !mTasks.containsKey(image)) {
+ ImageView iv = (ImageView)picView.findViewById(R.id.picture_thumbnail);
+ AsyncPictureLoaderTask task = new AsyncPictureLoaderTask(getContext(), iv);
+ task.execute(image);
+ mTasks.put(image, task);
+ }
+ }
+ }
+}