diff options
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 @@ -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 Binary files differnew file mode 100644 index 0000000..58bf972 --- /dev/null +++ b/res/drawable-hdpi/ic_accept.png diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..b60bb7e --- /dev/null +++ b/res/drawable-hdpi/ic_launcher.png diff --git a/res/drawable-hdpi/ic_overflow.png b/res/drawable-hdpi/ic_overflow.png Binary files differnew file mode 100644 index 0000000..493e1f1 --- /dev/null +++ b/res/drawable-hdpi/ic_overflow.png diff --git a/res/drawable-hdpi/ic_return.png b/res/drawable-hdpi/ic_return.png Binary files differnew file mode 100644 index 0000000..e487ee8 --- /dev/null +++ b/res/drawable-hdpi/ic_return.png diff --git a/res/drawable-mdpi/ic_accept.png b/res/drawable-mdpi/ic_accept.png Binary files differnew file mode 100644 index 0000000..cf5fab3 --- /dev/null +++ b/res/drawable-mdpi/ic_accept.png diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..cf96da3 --- /dev/null +++ b/res/drawable-mdpi/ic_launcher.png diff --git a/res/drawable-mdpi/ic_overflow.png b/res/drawable-mdpi/ic_overflow.png Binary files differnew file mode 100644 index 0000000..133a5c1 --- /dev/null +++ b/res/drawable-mdpi/ic_overflow.png diff --git a/res/drawable-mdpi/ic_return.png b/res/drawable-mdpi/ic_return.png Binary files differnew file mode 100644 index 0000000..ff100de --- /dev/null +++ b/res/drawable-mdpi/ic_return.png diff --git a/res/drawable-nodpi/bg_card.9.png b/res/drawable-nodpi/bg_card.9.png Binary files differnew file mode 100644 index 0000000..d0612b1 --- /dev/null +++ b/res/drawable-nodpi/bg_card.9.png diff --git a/res/drawable-nodpi/bg_notification.9.png b/res/drawable-nodpi/bg_notification.9.png Binary files differnew file mode 100644 index 0000000..4d60ecb --- /dev/null +++ b/res/drawable-nodpi/bg_notification.9.png diff --git a/res/drawable-xhdpi/ic_accept.png b/res/drawable-xhdpi/ic_accept.png Binary files differnew file mode 100644 index 0000000..b891571 --- /dev/null +++ b/res/drawable-xhdpi/ic_accept.png diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..6088d25 --- /dev/null +++ b/res/drawable-xhdpi/ic_launcher.png diff --git a/res/drawable-xhdpi/ic_overflow.png b/res/drawable-xhdpi/ic_overflow.png Binary files differnew file mode 100644 index 0000000..0c844f3 --- /dev/null +++ b/res/drawable-xhdpi/ic_overflow.png diff --git a/res/drawable-xhdpi/ic_return.png b/res/drawable-xhdpi/ic_return.png Binary files differnew file mode 100644 index 0000000..2f9e9bf --- /dev/null +++ b/res/drawable-xhdpi/ic_return.png 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 & 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); + } + } + } +} |