[
  {
    "path": ".gitignore",
    "content": "project.properties\nic_launcher-web.png\ngen/\nbin/\n.classpath\n.project\n.DS_Store\n/.settings/org.eclipse.jdt.core.prefs\n/.settings\n/.idea\n/out\n/Nemesis.iml\n!/libs/Android.mk\n!/libs/LICENSE-metadata-extractor.txt\n!/libs/metadata-extractor-2.6.4.jar\n!/libs/xmpcore.jar\n/libs/*/*\n/obj\n/res\\drawable-xxhdpi/Thumbs.db\n/jni\n/libs/android-support-v4.jar\n"
  },
  {
    "path": "Android.mk",
    "content": "# Copyright (C) 2013 The CyanogenMod Project\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n# MA  02110-1301, USA.\n\nLOCAL_PATH:= $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_SRC_FILES := $(call all-java-files-under, src)\n\nLOCAL_PACKAGE_NAME := Focal\n\n# Change this to LOCAL_JNI_SHARED_LIBRARIES to include\n# the binaries in the apk\nLOCAL_REQUIRED_MODULES := \\\n\tlibjni_mosaic2 \\\n\tlibxmphelper_jni \\\n\tlibpopen_helper_jni \\\n\tlibexiv2 \\\n\tlibglib-2.0 \\\n\tlibgmodule-2.0 \\\n\tlibgobject-2.0 \\\n\tlibgthread-2.0 \\\n\tlibjpeg \\\n\tlibpano13 \\\n\tlibtiffdecoder \\\n\tlibvigraimpex \\\n\tlibhugin \\\n\tlibxml2 \\\n\tlibiconv \\\n\tlibpng_static \\\n\tautooptimiser \\\n\tautopano \\\n\tceleste \\\n\tnona \\\n\tptclean \\\n\tenblend \\\n\tenfuse \\\n\tlibxmptoolkit\n\nLOCAL_STATIC_JAVA_LIBRARIES := \\\n\tmetadata-extractor \\\n\txmpcore \\\n        android-support-v4\n\ninclude $(BUILD_PACKAGE)\ninclude $(CLEAR_VARS)\n\nLOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := metadata-extractor:libs/metadata-extractor-2.6.4.jar xmpcore:libs/xmpcore.jar\ninclude $(BUILD_MULTI_PREBUILT)\n\ninclude $(call all-makefiles-under, $(ANDROID_BUILD_TOP)/external/Focal)\n"
  },
  {
    "path": "AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        package=\"fr.xplod.focal\"\n        android:versionCode=\"8\"\n        android:versionName=\"1.0-37a5749f31\">\n\n    <uses-sdk\n        android:minSdkVersion=\"16\"\n        android:targetSdkVersion=\"18\" />\n\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n\n    <uses-feature android:name=\"android.hardware.camera\" />\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n        android:largeHeap=\"true\"\n        android:name=\"org.cyanogenmod.focal.CameraApplication\">\n\n        <service android:name=\"org.cyanogenmod.focal.picsphere.PicSphereRenderingService\" />\n        <service android:name=\"org.cyanogenmod.focal.feats.SoftwareHdrRenderingService\" />\n\n        <activity\n            android:name=\"org.cyanogenmod.focal.CameraActivity\"\n            android:configChanges=\"orientation|keyboardHidden|screenSize\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"portrait\">\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.media.action.STILL_IMAGE_CAMERA\" />\n                <action android:name=\"android.media.action.STILL_IMAGE_CAMERA_SECURE\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n\n            <meta-data android:name=\"com.android.keyguard.layout\"\n                android:resource=\"@layout/keyguard_widget\" />\n        </activity>\n\n        <receiver android:name=\"org.cyanogenmod.focal.CameraButtonIntentReceiver\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.CAMERA_BUTTON\" />\n            </intent-filter>\n        </receiver>\n        <receiver android:name=\"org.cyanogenmod.focal.DisableCameraReceiver\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n            </intent-filter>\n        </receiver>\n\n        <receiver android:name=\"org.cyanogenmod.focal.WidgetProvider\" >\n            <intent-filter>\n                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n            </intent-filter>\n            <meta-data android:name=\"android.appwidget.provider\"\n                android:resource=\"@xml/widget_info\" />\n        </receiver>\n\n    </application>\n</manifest>\n"
  },
  {
    "path": "MODULE_LICENSE_GPL",
    "content": ""
  },
  {
    "path": "NOTICE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "libs/LICENSE-metadata-extractor.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "res/drawable/btn_pin_widget_inactive.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/ic_pin_widget_inactive_pressed\" />\n    <item android:drawable=\"@drawable/ic_pin_widget_inactive\" />\n</selector>\n"
  },
  {
    "path": "res/drawable/btn_shutter_photo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/btn_shutter_photo_pressed\" />\n    <item android:drawable=\"@drawable/btn_shutter_photo_normal\" />\n</selector>\n"
  },
  {
    "path": "res/drawable/btn_shutter_stop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/btn_shutter_stop_pressed\" />\n    <item android:drawable=\"@drawable/btn_shutter_stop_normal\" />\n</selector>\n"
  },
  {
    "path": "res/drawable/btn_shutter_video.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/btn_shutter_video_pressed\" />\n    <item android:drawable=\"@drawable/btn_shutter_video_normal\" />\n</selector>\n"
  },
  {
    "path": "res/drawable/cling_button_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/btn_cling_pressed\" />\n    <item android:drawable=\"@drawable/btn_cling_normal\" />\n</selector>\n"
  },
  {
    "path": "res/drawable/review_drawer_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@color/review_drawer_button_pressed\" />\n    <item android:drawable=\"@color/review_drawer_button_bg\" />\n</selector>\n"
  },
  {
    "path": "res/layout/activity_camera.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:id=\"@+id/camera_app_root\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        tools:context=\".CameraActivity\">\n\n    <FrameLayout\n        android:id=\"@+id/gl_renderer_container\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\">\n\n    </FrameLayout>\n\n    <org.cyanogenmod.focal.ui.RuleOfThirds\n        android:id=\"@+id/rule_of_thirds\"\n        android:layout_height=\"match_parent\"\n        android:layout_width=\"match_parent\"\n        android:visibility=\"gone\" />\n\n    <org.cyanogenmod.focal.ui.PreviewFrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"visible\"\n        android:layout_gravity=\"center\"\n        android:id=\"@+id/camera_preview_overlay_container\">\n\n        <ImageView\n            android:layout_height=\"match_parent\"\n            android:layout_width=\"match_parent\"\n            android:layout_centerInParent=\"true\"\n            android:scaleType=\"fitXY\"\n            android:alpha=\"0.0\"\n            android:id=\"@+id/camera_preview_overlay\" />\n\n    </org.cyanogenmod.focal.ui.PreviewFrameLayout>\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/recording_timer_container\"\n        android:padding=\"4dp\"\n        android:background=\"#88000000\"\n        android:visibility=\"gone\"\n        android:layout_margin=\"16dp\"\n        android:layout_gravity=\"right\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/recording_pin_image\"\n            android:contentDescription=\"@string/recording\"\n            android:src=\"@drawable/ic_recording_pin\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/recording_timer_text\"\n            android:paddingLeft=\"8dp\" />\n\n    </LinearLayout>\n\n    <FrameLayout\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:id=\"@+id/hud_container\">\n\n        <org.cyanogenmod.focal.ui.FocusHudRing\n            android:id=\"@+id/hud_ring_focus\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical|center_horizontal\" />\n\n        <org.cyanogenmod.focal.ui.ExposureHudRing\n            android:id=\"@+id/hud_ring_exposure\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:translationX=\"120dp\"\n            android:visibility=\"gone\" />\n\n    </FrameLayout>\n\n    <LinearLayout\n            android:orientation=\"vertical\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"fill_parent\"\n            android:layout_gravity=\"left|bottom\"\n            android:id=\"@+id/shortcuts_container\"\n            android:gravity=\"bottom\">\n    </LinearLayout>\n\n    <org.cyanogenmod.focal.ui.SideBar\n        android:id=\"@+id/sidebar_scroller\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"left\"\n        android:layout_marginTop=\"32dp\"\n        android:layout_marginBottom=\"32dp\">\n\n        <LinearLayout\n            android:id=\"@+id/sidebar_container\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n        </LinearLayout>\n\n    </org.cyanogenmod.focal.ui.SideBar>\n\n    <org.cyanogenmod.focal.ui.WidgetRenderer\n        android:id=\"@+id/widgets_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"left\"\n        android:orientation=\"horizontal\"\n        android:layout_marginBottom=\"64dp\"\n        android:layout_marginLeft=\"48dp\">\n\n    </org.cyanogenmod.focal.ui.WidgetRenderer>\n\n    <org.cyanogenmod.focal.ui.CircleTimerView\n        android:id=\"@+id/timer_view\"\n        android:layout_width=\"64dp\"\n        android:layout_height=\"64dp\"\n        android:layout_gravity=\"right\"\n        android:layout_margin=\"2dp\" />\n\n    <org.cyanogenmod.focal.ui.SavePinger\n        android:id=\"@+id/save_pinger\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:layout_gravity=\"right\"\n        android:layout_margin=\"2dp\" />\n\n    <org.cyanogenmod.focal.ui.PanoProgressBar\n        android:id=\"@+id/panorama_progress_bar\"\n        android:visibility=\"gone\"\n        android:layout_width=\"128dp\"\n        android:layout_height=\"32dp\"\n        android:layout_gravity=\"right|top\"\n        android:layout_marginRight=\"72dp\"\n        android:layout_marginTop=\"8dp\" />\n\n    <FrameLayout\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:id=\"@+id/thumb_flinger_container\">\n\n    </FrameLayout>\n\n\n    <FrameLayout\n        android:id=\"@+id/shutter_button_container\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\">\n\n        <org.cyanogenmod.focal.ui.SwitchRingPad\n            android:id=\"@+id/switch_ring_pad\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center_horizontal|bottom\"\n            android:visibility=\"visible\" />\n\n        <org.cyanogenmod.focal.ui.ShutterButton\n            android:id=\"@+id/btn_shutter\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal|bottom\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/shutter_button\"\n            android:src=\"@drawable/btn_shutter_photo\"\n            android:layout_marginBottom=\"-24dp\" />\n\n        <org.cyanogenmod.focal.ui.Notifier\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"4dp\"\n            android:background=\"#99333333\"\n            android:focusableInTouchMode=\"false\"\n            android:id=\"@+id/notifier_container\"\n            android:alpha=\"0\"\n            android:gravity=\"left|top\"\n            android:layout_margin=\"8dp\"\n            android:layout_gravity=\"left|top\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                android:textColor=\"#FFFFFFFF\"\n                android:gravity=\"left\"\n                android:id=\"@+id/notifier_text\" />\n\n        </org.cyanogenmod.focal.ui.Notifier>\n\n    </FrameLayout>\n\n    <org.cyanogenmod.focal.ui.ReviewDrawer\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"top|center_horizontal\"\n        android:id=\"@+id/review_drawer\"\n        android:background=\"#CC000000\"\n        android:layout_marginBottom=\"72dp\">\n\n\n        <android.support.v4.view.ViewPager\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/reviewed_image\"\n            android:layout_weight=\"1\"\n            android:padding=\"8dp\"\n            android:clickable=\"false\"\n            android:minHeight=\"400dp\"\n            android:contentDescription=\"@string/app_name\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_above=\"@+id/drawer_border\"\n            android:layout_alignParentTop=\"true\" />\n\n        <ImageButton\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentTop=\"true\"\n            android:layout_toLeftOf=\"@+id/button_retouch\"\n            android:id=\"@+id/button_open_in_gallery\"\n            style=\"@android:style/Widget.DeviceDefault.Light.Button.Borderless.Small\"\n            android:src=\"@drawable/ic_gallery\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"@drawable/review_drawer_button\"\n            android:layout_marginTop=\"8dp\" />\n\n        <ImageButton\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/button_retouch\"\n            android:textColor=\"@color/clock_white\"\n            style=\"@android:style/Widget.DeviceDefault.Button.Borderless.Small\"\n            android:src=\"@drawable/ic_retouch\"\n            android:layout_alignParentTop=\"true\"\n            android:layout_alignParentRight=\"true\"\n            android:background=\"@drawable/review_drawer_button\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginRight=\"8dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"4dp\"\n            android:layout_alignParentBottom=\"true\"\n            android:id=\"@+id/drawer_border\"\n            android:background=\"#FF0099cc\"\n            android:layout_weight=\"0\" />\n\n    </org.cyanogenmod.focal.ui.ReviewDrawer>\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/btn_picsphere_undo\"\n        android:layout_gravity=\"right\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:src=\"@drawable/ic_picsphere_undo\"\n        style=\"@android:style/Widget.Holo.Button.Borderless\"\n        android:visibility=\"gone\"\n        android:text=\"@string/picsphere_undo_button\"\n        android:drawableRight=\"@drawable/ic_picsphere_undo\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/txt_helper\"\n        android:textSize=\"18sp\"\n        android:shadowColor=\"#666666\"\n        android:shadowRadius=\"12\"\n        android:shadowDx=\"0\"\n        android:shadowDy=\"0\"\n        android:layout_gravity=\"center_horizontal\"\n        android:textStyle=\"bold\"\n        android:layout_marginTop=\"18dp\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "res/layout/handy.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:layout_width=\"wrap_content\"\n           android:layout_height=\"wrap_content\"\n           android:src=\"@drawable/hand\" />\n"
  },
  {
    "path": "res/layout/keyguard_widget.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/black\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginBottom=\"-24dp\"\n        android:src=\"@drawable/btn_shutter_photo_normal\"\n        android:scaleType=\"center\"\n        android:layout_alignParentEnd=\"false\"\n        android:layout_gravity=\"center_horizontal|bottom\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "res/layout/showcase_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<Button xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        style=\"@style/ShowcaseButton\"\n        android:text=\"@string/ok\"\n        android:id=\"@id/showcase_button\" />\n"
  },
  {
    "path": "res/layout/widget_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:orientation=\"horizontal\"\n              android:layout_width=\"250dp\"\n              android:layout_height=\"120dp\"\n              android:background=\"@color/widget_background\"\n              android:id=\"@+id/widget_container\">\n\n    <RelativeLayout\n            android:orientation=\"vertical\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"fill_parent\"\n            android:padding=\"4dp\">\n\n        <ImageView\n                android:layout_width=\"16dp\"\n                android:layout_height=\"16dp\"\n                android:id=\"@+id/widget_icon\"\n                android:src=\"@drawable/ic_widget_effect\"\n                android:adjustViewBounds=\"true\"\n                android:layout_alignParentLeft=\"true\"\n                android:layout_marginLeft=\"0dp\"\n                android:layout_alignParentTop=\"true\"\n                android:layout_marginTop=\"0dp\"/>\n\n        <Button\n                android:layout_width=\"16dp\"\n                android:layout_height=\"16dp\"\n                android:id=\"@+id/btn_pin_widget\"\n                android:layout_alignParentBottom=\"true\"\n                android:layout_centerHorizontal=\"true\"\n                android:layout_marginBottom=\"4dp\"\n                android:adjustViewBounds=\"true\"\n                android:padding=\"0dp\"\n                android:scaleType=\"centerInside\"\n                style=\"@android:style/Widget.DeviceDefault.Button.Borderless.Small\"\n                android:background=\"@drawable/btn_pin_widget_inactive\"/>\n\n    </RelativeLayout>\n\n    <TextView\n            android:layout_width=\"2dp\"\n            android:layout_height=\"fill_parent\"\n            android:id=\"@+id/textView\"\n            android:background=\"#33B5E5\"/>\n\n    <ScrollView\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"fill_parent\"\n            android:id=\"@+id/scrollView\"\n            android:layout_gravity=\"center\"\n            android:fadingEdge=\"vertical|horizontal\"\n            android:fadingEdgeLength=\"6dp\"\n            android:requiresFadingEdge=\"vertical|horizontal\">\n\n        <GridLayout\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"fill_parent\"\n                android:columnCount=\"3\"\n                android:rowCount=\"1\"\n                android:id=\"@+id/widget_options_container\"\n                android:fadingEdge=\"vertical|horizontal\"\n                android:fadingEdgeLength=\"6dp\"\n                android:requiresFadingEdge=\"vertical|horizontal\"\n                android:useDefaultMargins=\"true\">\n\n        </GridLayout>\n    </ScrollView>\n\n</LinearLayout>"
  },
  {
    "path": "res/layout/widget_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n    <Button\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/launch_app\"\n        android:id=\"@+id/button\"\n        android:layout_weight=\"1\"\n        android:drawableLeft=\"@drawable/ic_launcher\"\n        style=\"@android:style/Widget.DeviceDefault.Button.Borderless\"\n        android:padding=\"8dp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n        tools:locale=\"en\">\n    <!-- Hardware HDR settings keys. If any of these key is present, the software HDR widget\n         is hidden. If you need to force-disable software HDR, see config.xml. -->\n    <string-array name=\"hardware_hdr_keys\" translatable=\"false\">\n        <item>ae-bracket-hdr</item>\n    </string-array>\n\n    <!-- Voice shutter trigger commands -->\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n\n    <!-- Flash widget values -->\n    <string-array name=\"widget_flash_values\" translatable=\"false\">\n        <item>auto</item>\n        <item>off</item>\n        <item>on</item>\n        <item>torch</item>\n        <item>red-eye</item>\n    </string-array>\n\n    <array name=\"widget_flash_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_flash_auto</item>\n        <item>@drawable/ic_widget_flash_off</item>\n        <item>@drawable/ic_widget_flash_on</item>\n        <item>@drawable/ic_widget_flash_torch</item>\n        <item>@drawable/ic_widget_flash_redeye</item>\n    </array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Disabled</item>\n        <item>Enabled</item>\n        <item>Torch</item>\n        <item>Red-eye reduction</item>\n    </string-array>\n\n    <!-- Effects widget values -->\n    <string-array name=\"widget_effects_values\" translatable=\"false\">\n        <item>none</item>\n        <item>mono</item>\n        <item>negative</item>\n        <item>solarize</item>\n        <item>sepia</item>\n        <item>posterize</item>\n        <item>blackboard</item>\n        <item>whiteboard</item>\n        <item>aqua</item>\n        <item>emboss</item>\n        <item>sketch</item>\n        <item>neon</item>\n    </string-array>\n\n    <array name=\"widget_effects_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_effect_none</item>\n        <item>@drawable/ic_widget_effect_mono</item>\n        <item>@drawable/ic_widget_effect_negative</item>\n        <item>@drawable/ic_widget_effect_solarize</item>\n        <item>@drawable/ic_widget_effect_sepia</item>\n        <item>@drawable/ic_widget_effect_posterize</item>\n        <item>@drawable/ic_widget_effect_blackboard</item>\n        <item>@drawable/ic_widget_effect_whiteboard</item>\n        <item>@drawable/ic_widget_effect_aqua</item>\n        <item>@drawable/ic_widget_effect_emboss</item>\n        <item>@drawable/ic_widget_effect_sketch</item>\n        <item>@drawable/ic_widget_effect_neon</item>\n    </array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Disabled</item>\n        <item>Black &amp; White</item>\n        <item>Negative</item>\n        <item>Solarize</item>\n        <item>Sepia</item>\n        <item>Posterize</item>\n        <item>Blackboard</item>\n        <item>Whiteboard</item>\n        <item>Aqua</item>\n        <item>Emboss</item>\n        <item>Sketch</item>\n        <item>Neon</item>\n    </string-array>\n\n    <!-- ISO widget values -->\n    <string-array name=\"widget_iso_values\" translatable=\"false\">\n        <item>auto</item>\n        <item>ISO_HJR</item>\n        <item>ISO100</item>\n        <item>ISO200</item>\n        <item>ISO400</item>\n        <item>ISO800</item>\n        <item>ISO1600</item>\n    </string-array>\n\n    <array name=\"widget_iso_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_iso_auto</item>\n        <item>@drawable/ic_widget_iso_hjr</item>\n        <item>@drawable/ic_widget_iso_100</item>\n        <item>@drawable/ic_widget_iso_200</item>\n        <item>@drawable/ic_widget_iso_400</item>\n        <item>@drawable/ic_widget_iso_800</item>\n        <item>@drawable/ic_widget_iso_1600</item>\n    </array>\n\n    <string-array name=\"widget_iso_hints\" translatable=\"false\">\n        <item>@string/iso_hint_auto</item>\n        <item>@string/iso_hint_hjr</item>\n        <item>@string/iso_hint_100</item>\n        <item>@string/iso_hint_200</item>\n        <item>@string/iso_hint_400</item>\n        <item>@string/iso_hint_800</item>\n        <item>@string/iso_hint_1600</item>\n    </string-array>\n\n    <!-- Scene Mode widget values -->\n    <string-array name=\"widget_scenemode_values\" translatable=\"false\">\n        <item>auto</item>\n        <item>anti-motion-blur</item>\n        <item>AR</item>\n        <item>asd</item>\n        <item>baby</item>\n        <item>backlight</item>\n        <item>backlight-portrait</item>\n        <item>barcode</item>\n        <item>beach</item>\n        <item>candlelight</item>\n        <item>dark</item>\n        <item>dish</item>\n        <item>document</item>\n        <item>fireworks</item>\n        <item>flowers</item>\n        <item>handheld-twilight</item>\n        <item>hdr</item>\n        <item>high-sensitivity</item>\n        <item>landscape</item>\n        <item>night</item>\n        <item>nightportrait</item>\n        <item>night-portrait</item>\n        <item>party</item>\n        <item>pet</item>\n        <item>portrait</item>\n        <item>snow</item>\n        <item>soft-skin</item>\n        <item>sports</item>\n        <item>spot-light</item>\n        <item>steadyphoto</item>\n        <item>sunset</item>\n        <item>sweep-stitch</item>\n        <item>theatre</item>\n    </string-array>\n\n    <array name=\"widget_scenemode_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_scenemode_auto</item>\n        <item>@drawable/ic_widget_scenemode_antimotionblur</item>\n        <item>@drawable/ic_widget_scenemode_ar</item>\n        <item>@drawable/ic_widget_scenemode_asd</item>\n        <item>@drawable/ic_widget_scenemode_baby</item>\n        <item>@drawable/ic_widget_scenemode_backlight</item>\n        <item>@drawable/ic_widget_scenemode_backlightportrait</item>\n        <item>@drawable/ic_widget_scenemode_barcode</item>\n        <item>@drawable/ic_widget_scenemode_beach</item>\n        <item>@drawable/ic_widget_scenemode_candlelight</item>\n        <item>@drawable/ic_widget_scenemode_dark</item>\n        <item>@drawable/ic_widget_scenemode_dish</item>\n        <item>@drawable/ic_widget_scenemode_document</item>\n        <item>@drawable/ic_widget_scenemode_fireworks</item>\n        <item>@drawable/ic_widget_scenemode_flowers</item>\n        <item>@drawable/ic_widget_scenemode_handheld</item>\n        <item>@drawable/ic_widget_scenemode_hdr</item>\n        <item>@drawable/ic_widget_scenemode_highsensitivity</item>\n        <item>@drawable/ic_widget_scenemode_landscape</item>\n        <item>@drawable/ic_widget_scenemode_night</item>\n        <item>@drawable/ic_widget_scenemode_nightportrait</item>\n        <item>@drawable/ic_widget_scenemode_nightportrait</item>\n        <item>@drawable/ic_widget_scenemode_party</item>\n        <item>@drawable/ic_widget_scenemode_pet</item>\n        <item>@drawable/ic_widget_scenemode_portrait</item>\n        <item>@drawable/ic_widget_scenemode_snow</item>\n        <item>@drawable/ic_widget_scenemode_softskin</item>\n        <item>@drawable/ic_widget_scenemode_sports</item>\n        <item>@drawable/ic_widget_scenemode_spotlight</item>\n        <item>@drawable/ic_widget_scenemode_steadyphoto</item>\n        <item>@drawable/ic_widget_scenemode_sunset</item>\n        <item>@drawable/ic_widget_scenemode_sweepstitch</item>\n        <item>@drawable/ic_widget_scenemode_theatre</item>\n    </array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatic</item>\n        <item>Motion Blur Reduction</item>\n        <item>Augmented Reality</item>\n        <item>Best Shot</item>\n        <item>Baby</item>\n        <item>Backlight</item>\n        <item>Portrait Backlight</item>\n        <item>Barcode</item>\n        <item>Beach</item>\n        <item>Candlelight</item>\n        <item>Dark</item>\n        <item>Dish</item>\n        <item>Document</item>\n        <item>Fireworks</item>\n        <item>Flowers</item>\n        <item>Handheld Twilight</item>\n        <item>HDR</item>\n        <item>High sensitivity</item>\n        <item>Landscape</item>\n        <item>Night</item>\n        <item>Night portrait</item>\n        <item>Night portrait</item>\n        <item>Party</item>\n        <item>Pet</item>\n        <item>Portrait</item>\n        <item>Snow</item>\n        <item>Soft skin</item>\n        <item>Sports</item>\n        <item>Spotlight</item>\n        <item>Steady photo</item>\n        <item>Sunset</item>\n        <item>Sweep stitch</item>\n        <item>Theatre</item>\n    </string-array>\n\n    <!-- White balance widget values -->\n    <string-array name=\"widget_whitebalance_values\" translatable=\"false\">\n        <item>auto</item>\n        <item>cloudy</item>\n        <item>incandescent</item>\n        <item>fluorescent</item>\n        <item>daylight</item>\n        <item>cloudy-daylight</item>\n    </string-array>\n\n    <array name=\"widget_whitebalance_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_wb_auto</item>\n        <item>@drawable/ic_widget_wb_cloudy</item>\n        <item>@drawable/ic_widget_wb_incandescent</item>\n        <item>@drawable/ic_widget_wb_fluorescent</item>\n        <item>@drawable/ic_widget_wb_daylight</item>\n        <item>@drawable/ic_widget_wb_cloudy_daylight</item>\n    </array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatic</item>\n        <item>Cloudy</item>\n        <item>Incandescent</item>\n        <item>Fluorescent</item>\n        <item>Daylight</item>\n        <item>Cloudy Daylight</item>\n    </string-array>\n\n    <!-- Auto-exposure widget values -->\n    <string-array name=\"widget_autoexposure_values\" translatable=\"false\">\n        <item>frame-average</item>\n        <item>center-weighted</item>\n        <item>spot-metering</item>\n    </string-array>\n\n    <array name=\"widget_autoexposure_icons\" translatable=\"false\">\n        <item>@drawable/ic_widget_autoexposure_frameaverage</item>\n        <item>@drawable/ic_widget_autoexposure_centerweighted</item>\n        <item>@drawable/ic_widget_autoexposure_spotmetering</item>\n    </array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Frame average</item>\n        <item>Center weighted</item>\n        <item>Spot metering</item>\n    </string-array>\n\n    <!-- Shutter speed values -->\n    <string-array name=\"widget_shutter_speed_display_values\" translatable=\"false\">\n        <item>Auto</item>\n        <item>1\\\"</item>\n        <item>1/2</item>\n        <item>1/4</item>\n        <item>1/8</item>\n        <item>1/16</item>\n        <item>1/32</item>\n        <item>1/100</item>\n        <item>1/125</item>\n        <item>1/250</item>\n        <item>1/500</item>\n        <item>1/1000</item>\n        <item>1/2000</item>\n        <item>1/3333</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <declare-styleable name=\"ButtonBarContainerTheme\">\n        <attr name=\"buttonBarStyle\" format=\"reference\"/>\n        <attr name=\"buttonBarButtonStyle\" format=\"reference\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"ShowcaseView\">\n        <attr name=\"sv_backgroundColor\" format=\"color|reference\" />\n        <attr name=\"sv_detailTextColor\" format=\"color|reference\" />\n        <attr name=\"sv_titleTextColor\" format=\"color|reference\" />\n        <attr name=\"sv_buttonBackgroundColor\" format=\"color|reference\" />\n        <attr name=\"sv_buttonForegroundColor\" format=\"color|reference\" />\n        <attr name=\"sv_buttonText\" format=\"string|reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CustomTheme\">\n        <attr name=\"showcaseViewStyle\" format=\"reference\" />\n    </declare-styleable>\n</resources>\n"
  },
  {
    "path": "res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <color name=\"widget_background\">#AA000000</color>\n    <color name=\"widget_toggle_pressed\">#BB33B5E5</color>\n    <color name=\"widget_toggle_active\">#9033B5E5</color>\n    <color name=\"widget_option_active\">#FF33B5E5</color>\n    <color name=\"clock_red\">#FF4444</color>\n    <color name=\"clock_white\">#FFFFFF</color>\n    <color name=\"pano_progress_empty\">#FF2E2E2E</color>\n    <color name=\"pano_progress_done\">#FF33525E</color>\n    <color name=\"pano_progress_indication\">#FF0099CC</color>\n    <color name=\"pano_progress_indication_fast\">#FFFF2222</color>\n    <color name=\"pano_saving_done\">#FF5E5233</color>\n    <color name=\"pano_saving_indication\">#FF99CC00</color>\n    <color name=\"review_drawer_button_bg\">#88333333</color>\n    <color name=\"review_drawer_button_pressed\">#8833B5E5</color>\n</resources>\n"
  },
  {
    "path": "res/values/config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <!-- Whether or not to stop preview between taking shots -->\n    <bool name=\"config_stopPreviewBetweenShots\">false</bool>\n\n    <!-- Whether or not to enable camera-mode=1 for ZSL on Qualcomm devices -->\n    <bool name=\"config_qualcommZslCameraMode\">true</bool>\n\n    <!-- Whether or not to use preview data to take snapshots while in the video mode.\n         This enables snapshot during videos on older devices. -->\n    <bool name=\"config_usePreviewForVideoSnapshot\">false</bool>\n\n    <!-- The largest possible GL texture size -->\n    <integer name=\"config_maxTextureSize\">4096</integer>\n\n    <!-- The ideal panorama picture resolution to use. AOSP enforces a 640x480 resolution\n         whenever possible, but we can get better shots with higher resolution just as well. -->\n    <integer name=\"config_panoramaDefaultWidth\">1280</integer>\n    <integer name=\"config_panoramaDefaultHeight\">720</integer>\n\n    <!-- Whether or not to enable Samsung HDR capabilities -->\n    <bool name=\"config_useSamsungHDR\">false</bool>\n\n    <!-- Whether or not to enable Samsung ZSL capabilities -->\n    <bool name=\"config_useSamsungZSL\">false</bool>\n</resources>\n"
  },
  {
    "path": "res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <item type=\"id\" name=\"showcase_button\" />\n    <item type=\"id\" name=\"showcase_title_text\" />\n    <item type=\"id\" name=\"showcase_sub_text\" />\n</resources>\n"
  },
  {
    "path": "res/values/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <!-- Default max width of a widget, as number of options -->\n    <integer name=\"widget_default_max_width\">3</integer>\n\n    <!-- Size of a widget option button (square) -->\n    <dimen name=\"widget_option_button_size\">64dp</dimen>\n\n    <!-- Padding between each icon within a widget -->\n    <dimen name=\"widget_option_button_padding\">4dp</dimen>\n\n    <!-- Padding of the widget toggle buttons -->\n    <dimen name=\"widget_toggle_button_padding\">8dp</dimen>\n\n    <!-- Default font size of the WidgetOptionLabel -->\n    <integer name=\"widget_option_label_font_size\">12</integer>\n\n    <!-- Small font size of the WidgetOptionLabel -->\n    <integer name=\"widget_option_label_font_size_small\">10</integer>\n\n    <!-- Padding between each open widget -->\n    <dimen name=\"widget_spacing\">8dp</dimen>\n\n    <!-- Padding within the widget container window -->\n    <dimen name=\"widget_container_padding\">1dp</dimen>\n\n    <!-- Edge padding of the ring buttons -->\n    <dimen name=\"ringpad_edge_spacing\">32dp</dimen>\n\n    <!-- Radius of the ringpad -->\n    <dimen name=\"ringpad_radius\">140dp</dimen>\n\n    <!-- Size of the review drawer thumbnails -->\n    <dimen name=\"review_drawer_thumb_size\">96dp</dimen>\n\n    <dimen name=\"timer_circle_diameter\">100dip</dimen>\n    <dimen name=\"timer_circle_width\">125dip</dimen>\n\n    <dimen name=\"circletimer_diamond_size\">6dip</dimen>\n    <dimen name=\"circletimer_circle_size\">4dip</dimen>\n    <dimen name=\"circletimer_marker_size\">16dip</dimen>\n</resources>\n"
  },
  {
    "path": "res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n        tools:locale=\"en\">\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Launch Focal</string>\n    <string name=\"shutter_button\">Shutter button</string>\n    <string name=\"cannot_connect_hal\">Unable to connect to camera</string>\n    <string name=\"recording\">Recording</string>\n    <string name=\"double_tap_to_snapshot\">Double-tap to take a picture</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Your device doesn\\'t support GLES2</string>\n    <string name=\"no_gyroscope\">Your device doesn\\'t have a gyroscope</string>\n    <string name=\"ps_long_press_to_stop\">Long-press the shutter button to\\nfinish your sphere.</string>\n    <string name=\"disabled\">Disabled</string>\n    <string name=\"enabled\">Enabled</string>\n    <string name=\"no_video_player\">Couldn\\'t play the video, no player found</string>\n    <string name=\"fullscreen_shutter_info\">Tap anywhere to shoot</string>\n    <string name=\"please_wait\">Please wait\\u2026</string>\n    <string name=\"retouch\">RETOUCH</string>\n    <string name=\"open_in_gallery\">GALLERY</string>\n\n    <string name=\"mode_photo\">Photo</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Facing</string>\n\n    <!-- Widgets hints for non-arrayed widgets -->\n    <string name=\"widget_burstmode_count_shots\">Burst %d shots</string>\n    <string name=\"widget_burstmode_off\">Disable burst mode</string>\n    <string name=\"widget_burstmode_infinite\">Infinite burst</string>\n\n    <string name=\"widget_hdr_aebracket\">Auto bracketing</string>\n\n    <string name=\"widget_settings_choose_widgets\">Choose widgets in the sidebar</string>\n    <string name=\"widget_settings_choose_widgets_button\">Choose widgets</string>\n    <string name=\"widget_settings_picture_size\">Image size</string>\n    <string name=\"widget_settings_exposure_ring\">Show exposure ring</string>\n    <string name=\"widget_settings_autoenhance\">Auto-enhancement</string>\n    <string name=\"widget_settings_ruleofthirds\">Rule of Thirds</string>\n\n    <string name=\"widget_autoexposure\">Light measure</string>\n    <string name=\"widget_burstmode\">Burst mode</string>\n    <string name=\"widget_effect\">Effects</string>\n    <string name=\"widget_enhancements\">Color adjust.</string>\n    <string name=\"widget_exposure_compensation\">Exposure comp.</string>\n    <string name=\"widget_flash\">Flash</string>\n    <string name=\"widget_hdr\">High Dynamic Range</string>\n    <string name=\"widget_iso\">ISO</string>\n    <string name=\"widget_scenemode\">Scene mode</string>\n    <string name=\"widget_settings\">Settings</string>\n    <string name=\"widget_shutter_speed\">Shutter speed</string>\n    <string name=\"widget_skintone\">Skin Tone Enhancement</string>\n    <string name=\"widget_softwarehdr\">High Dynamic Range</string>\n    <string name=\"widget_timermode\">Timer mode</string>\n    <string name=\"widget_videofr\">Video framerate</string>\n    <string name=\"widget_videohdr\">Video High Dynamic Range</string>\n    <string name=\"widget_whitebalance\">White balance</string>\n\n    <!-- Panorama mode -->\n    <!-- Filename prefix for panorama output -->\n    <string name=\"pano_file_name_format\" translatable=\"false\">\"'PANO'_yyyyMMdd_HHmmss\"</string>\n\n    <string name=\"pano_panorama_rendering_failed\">Failed to render panorama.\\nTry to take a shorter one.</string>\n    <string name=\"pano_panorama_rendering\">Rendering panorama\\u2026</string>\n\n    <!-- PicSphere status -->\n    <string name=\"picsphere_undo_button\">UNDO</string>\n    <string name=\"picsphere_start_hint\">Take a picture to start a sphere</string>\n    <string name=\"picsphere_already_rendering\">Please wait for the current PicSphere to render</string>\n    <string name=\"picsphere_rendering_progress\">Rendering\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Rendering started\\u2026</string>\n    <string name=\"picsphere_notif_title\">Rendering PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">PicSphere rendering failed</string>\n    <string name=\"picsphere_failed_details\">Make sure pictures only overlap on their edges.</string>\n    <string name=\"picsphere_need_two_pics\">You need at least two pictures.</string>\n    <string name=\"picsphere_step_preparing\">Preparing\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Generating metadata\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Writing camera orientation\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Finding control points\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Aligning images\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Cleaning points\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Cropping empty areas\\u2026</string>\n    <string name=\"picsphere_step_nona\">Stitching pictures\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Blending pictures\\u2026</string>\n\n    <!-- Software HDR status -->\n    <string name=\"software_hdr_notif_title\">Computing HDR picture\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR rendering failed</string>\n    <string name=\"software_hdr_failed_details\">The source pictures have been kept.</string>\n\n    <!-- Showcase text -->\n    <string name=\"showcase_welcome_1_title\">Welcome!</string>\n    <string name=\"showcase_welcome_1_body\">The options are in the sidebar,\\njust slide it.</string>\n    <string name=\"showcase_welcome_2_title\">Shutter button</string>\n    <string name=\"showcase_welcome_2_body\">Tap the shutter button to take a picture.\\nSlide it to access other capture modes.</string>\n    <string name=\"showcase_panorama_title\">Panorama mode</string>\n    <string name=\"showcase_panorama_body\">Tap the shutter to start your panorama,\\nthen pan what you want to capture.\\nOnce done, tap the shutter button again.</string>\n    <string name=\"showcase_picsphere_title\">PicSphere mode</string>\n    <string name=\"showcase_picsphere_body\">Align your first picture where you like,\\nthen tap the shutter to take each picture\\nof the sphere. The blue dots are points\\nof reference you can follow.\\nLong-press the shutter once you\\'re done.</string>\n\n    <!-- ISO Value Hints -->\n    <string name=\"iso_hint_auto\">Automatic ISO</string>\n    <string name=\"iso_hint_hjr\">Hands-Jitter Reduction ISO</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- Application theme -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.NoActionBar.Fullscreen\">\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowBackground\">@null</item>\n    </style>\n\n    <!-- Showcase/cling -->\n    <style name=\"ShowcaseButton\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:paddingTop\">10dp</item>\n        <item name=\"android:paddingBottom\">15dp</item>\n        <item name=\"android:paddingLeft\">35dp</item>\n        <item name=\"android:paddingRight\">35dp</item>\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"android:textColor\">#FFFFFF</item>\n        <item name=\"android:background\">@drawable/cling_button_bg</item>\n    </style>\n\n    <style name=\"ShowcaseTitleText\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_marginBottom\">5dp</item>\n        <item name=\"android:textSize\">23sp</item>\n        <item name=\"android:textColor\">#49C0EC</item>\n        <item name=\"android:shadowColor\">#000000</item>\n        <item name=\"android:shadowDy\">2</item>\n        <item name=\"android:shadowRadius\">2.0</item>\n    </style>\n\n    <style name=\"ShowcaseText\">\n        <item name=\"android:textSize\">15sp</item>\n        <item name=\"android:textColor\">#FFFFFF</item>\n        <item name=\"android:shadowColor\">#000000</item>\n        <item name=\"android:shadowDy\">2</item>\n        <item name=\"android:shadowRadius\">2.0</item>\n        <item name=\"android:lineSpacingMultiplier\">1.1</item>\n    </style>\n\n    <style name=\"ShowcaseView.Light\">\n        <item name=\"sv_titleTextColor\">#33B5E5</item>\n        <item name=\"sv_detailTextColor\">#444</item>\n        <item name=\"sv_backgroundColor\">#3333B5E5</item>\n        <item name=\"sv_buttonText\">@string/ok</item>\n    </style>\n\n    <style name=\"ShowcaseView\">\n        <item name=\"sv_titleTextColor\">#33B5E5</item>\n        <item name=\"sv_detailTextColor\">#FFFFFF</item>\n        <item name=\"sv_backgroundColor\">#BB333333</item>\n        <item name=\"sv_buttonText\">@string/ok</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "res/values-cs/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>sýr</item>\n        <item>cide, udělej fotku</item>\n    </string-array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Automaticky</item>\n        <item>Zakázán</item>\n        <item>Povolen</item>\n        <item>Svítilna</item>\n        <item>Redukce červených očí</item>\n    </string-array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Vypnutý</item>\n        <item>Černobílý</item>\n        <item>Negativní</item>\n        <item>Solarizace</item>\n        <item>Sépie</item>\n        <item>Posterizace</item>\n        <item>Černá tabule</item>\n        <item>Bílá tabule</item>\n        <item>Voda</item>\n        <item>Reliéf</item>\n        <item>Skica</item>\n        <item>Žádný</item>\n    </string-array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatický</item>\n        <item>Redukce rozmazání</item>\n        <item>Rozšířená realita</item>\n        <item>Nejlepší snímek</item>\n        <item>Dítě</item>\n        <item>Protisvětlo</item>\n        <item>Portrét s protisvětlem</item>\n        <item>Čárový kód</item>\n        <item>Pláž</item>\n        <item>Osvíceno svíčkami</item>\n        <item>Temné</item>\n        <item>Jídlo</item>\n        <item>Dokument</item>\n        <item>Ohňostroj</item>\n        <item>Květiny</item>\n        <item>Stmívání bez stativu</item>\n        <item>HDR</item>\n        <item>Vysoká citlivost</item>\n        <item>Krajina</item>\n        <item>Noc</item>\n        <item>Noční portrét</item>\n        <item>Noční portrét</item>\n        <item>Párty</item>\n        <item>Mazlíček</item>\n        <item>Portrét</item>\n        <item>Sníh</item>\n        <item>Měkké světlo</item>\n        <item>Sporty</item>\n        <item>Reflektor</item>\n        <item>Ze stativu</item>\n        <item>Západ slunce</item>\n        <item>Plynulé panorama</item>\n        <item>Divadlo</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automaticky</item>\n        <item>Zataženo</item>\n        <item>Žárovka</item>\n        <item>Zářivka</item>\n        <item>Denní světlo</item>\n        <item>Zataženo ve dne</item>\n    </string-array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Celoplošné průměrové</item>\n        <item>Se zdůrazněným středem</item>\n        <item>Bodové</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-cs/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Spustit Focal</string>\n    <string name=\"shutter_button\">Spoušť</string>\n    <string name=\"cannot_connect_hal\">Nelze přistupovat k fotoaparátu zařízení</string>\n    <string name=\"recording\">Nahrávání</string>\n    <string name=\"double_tap_to_snapshot\">Dvojitým dotykem pořídíte fotografii</string>\n    <string name=\"no_gles20_support\">Zařízení nepodporuje standard GLES2</string>\n    <string name=\"no_gyroscope\">Zařízení nedisponuje gyroskopem</string>\n    <string name=\"ps_long_press_to_stop\">Dlouhým stiskem spouště\\ndokončíte sférickou fotografii.</string>\n    <string name=\"disabled\">Zakázano</string>\n    <string name=\"enabled\">Povoleno</string>\n    <string name=\"no_video_player\">Video nelze přehrát, není dostupný přehrávač</string>\n    <string name=\"fullscreen_shutter_info\">Dotykem kdekoliv pořídíte fotografii</string>\n    <string name=\"please_wait\">Počkejte prosím\\u2026</string>\n    <string name=\"retouch\">RETUŠOVAT</string>\n    <string name=\"open_in_gallery\">GALERIE</string>\n\n    <string name=\"mode_photo\">Fotografie</string>\n    <string name=\"mode_picsphere\">Sférická fotografie</string>\n    <string name=\"mode_switchcam\">Přední kamera</string>\n\n    <string name=\"widget_burstmode_count_shots\">Pořízení %d snímků v řadě</string>\n    <string name=\"widget_burstmode_off\">Zakázat pořizování snímků v řadě</string>\n    <string name=\"widget_burstmode_infinite\">Nekonečné pořizování snímků v řadě</string>\n\n    <string name=\"widget_hdr_aebracket\">Automatické posouvání expozice</string>\n\n    <string name=\"widget_settings_choose_widgets\">Zvolte widgety v postraní liště</string>\n    <string name=\"widget_settings_choose_widgets_button\">Zvolte widgety</string>\n    <string name=\"widget_settings_picture_size\">Velikost obrázku</string>\n    <string name=\"widget_settings_exposure_ring\">Zobrazit expoziční prstenec</string>\n    <string name=\"widget_settings_autoenhance\">Automatické vylepšení</string>\n    <string name=\"widget_settings_ruleofthirds\">Pravidlo třetin</string>\n\n    <string name=\"widget_autoexposure\">Režím měření expozice</string>\n    <string name=\"widget_burstmode\">Vícenásobný režim</string>\n    <string name=\"widget_effect\">Barevné efekty</string>\n    <string name=\"widget_enhancements\">Barevné vylepšení</string>\n    <string name=\"widget_exposure_compensation\">Kompenzace expozice</string>\n    <string name=\"widget_flash\">Režim blesku</string>\n    <string name=\"widget_hdr\">Dynamický rozsah expozice (HDR)</string>\n    <string name=\"widget_iso\">Citlivost ISO</string>\n    <string name=\"widget_scenemode\">Scénický režim</string>\n    <string name=\"widget_settings\">Nastavení</string>\n    <string name=\"widget_shutter_speed\">Rychlost uzávěrky</string>\n    <string name=\"widget_skintone\">Vylepšení tonality kůže</string>\n    <string name=\"widget_softwarehdr\">Dynamický rozsah expozice (HDR)</string>\n    <string name=\"widget_timermode\">Režim časovače</string>\n    <string name=\"widget_videofr\">Snímková rychlost videa</string>\n    <string name=\"widget_videohdr\">Dynamický rozsah expozice pro video</string>\n    <string name=\"widget_whitebalance\">Vyvážení bílé</string>\n\n    <string name=\"pano_panorama_rendering_failed\">Vykreslení panorama selhalo.\\nZkuste pořídit kratší panorama.</string>\n    <string name=\"pano_panorama_rendering\">Vykreslování panorama\\u2026</string>\n\n    <string name=\"picsphere_undo_button\">ZPĚT</string>\n    <string name=\"picsphere_start_hint\">Pro zahájení snímání sférické fotografie pořiďte fotografii</string>\n    <string name=\"picsphere_already_rendering\">Prosím vyčkejte dokud není sférická fotofrafie kompletně vykreslena</string>\n    <string name=\"picsphere_rendering_progress\">Vykreslování\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Vykreslování zahájeno\\u2026</string>\n    <string name=\"picsphere_notif_title\">Vykreslování sférické fotografie\\u2026</string>\n    <string name=\"picsphere_failed\">Vykreslování sférické fotografie selhalo</string>\n    <string name=\"picsphere_failed_details\">Ujistěte se, zda se pořízení fotografie na hranách překrývají.</string>\n    <string name=\"picsphere_need_two_pics\">Je potřeba pořídit alespoň dvě fotografie.</string>\n    <string name=\"picsphere_step_preparing\">Připravování\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Vytváření metadat\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Ukládání orientace fotoaparátu\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Vyhledávání řídících bodů\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Zarovnávání obrázků\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Čištění bodů\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Odstříhávání prázdých oblastí\\u2026</string>\n    <string name=\"picsphere_step_nona\">Sestavování obrázků\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Prolínání obrázků\\u2026</string>\n\n    <string name=\"software_hdr_notif_title\">Vytváření obrázku HDR\\u2026</string>\n    <string name=\"software_hdr_failed\">Vykreslování obrázku HDR selhalo</string>\n    <string name=\"software_hdr_failed_details\">Zdorjové obrázky zůstali uloženy.</string>\n\n    <string name=\"showcase_welcome_1_title\">Vítáme Vás!</string>\n    <string name=\"showcase_welcome_1_body\">Možnosti jsou dostupné na postraní liště,\\nstačí ji jen vysunout.</string>\n    <string name=\"showcase_welcome_2_title\">Tlačítko spouště</string>\n    <string name=\"showcase_welcome_2_body\">Dotykem na tlačítko spouště pořídíte obrázek.\\nPřesunem získáte možnost změnit režim.</string>\n    <string name=\"showcase_panorama_title\">Režim panorama</string>\n    <string name=\"showcase_panorama_body\">Dotykem na spoušti zahájíte snímání panorama,\\npoté natáčejte zařízení do směru, který chcete zachytit.\\nDlaším dotykem na spoušti dojde k ukončení snímání panorama.</string>\n    <string name=\"showcase_picsphere_title\">Režim sférické fotografie</string>\n    <string name=\"showcase_picsphere_body\">Srovnejte umístění první fotografie,\\npoté se dotkněte spouště pro zahájení snímání\\nsférické fotografie. Modré body Vás budou navádět pro získání fotografie\\nv daném směru.\\nDlouhým stiskem spouště bude pořizování ukončeno.</string>\n\n    <string name=\"iso_hint_auto\">automatické ISO</string>\n    <string name=\"iso_hint_hjr\">ISO pro redukci třesoucích se rukou</string>\n</resources>\n"
  },
  {
    "path": "res/values-da/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2013 Guillaume Lesniak\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version 2\n    of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Deaktiveret</item>\n        <item>Aktiveret</item>\n        <item>Lommelygte</item>\n        <item>Rødøjereduktion</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Deaktiveret</item>\n        <item>Sort &amp; Hvid</item>\n        <item>Negativ</item>\n        <item>Solarisering</item>\n        <item>Sepia</item>\n        <item>Posterisering</item>\n        <item>Sort tavle</item>\n        <item>Hvid tavle</item>\n        <item>Vand</item>\n        <item>Relief</item>\n        <item>Skitse</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatisk</item>\n        <item>Antisløring</item>\n        <item>Augmenteret virkelighed</item>\n        <item>Bedste foto</item>\n        <item>Baby</item>\n        <item>Bagbelysning</item>\n        <item>Bagbelyst portræt</item>\n        <item>Stregkode</item>\n        <item>Strand</item>\n        <item>Stearinlys</item>\n        <item>Mørk</item>\n        <item>Fad</item>\n        <item>Dokument</item>\n        <item>Fyrværkeri</item>\n        <item>Blomster</item>\n        <item>Håndholdt Skumring</item>\n        <item>HDR</item>\n        <item>Høj følsomhed</item>\n        <item>Landskab</item>\n        <item>Nat</item>\n        <item>Natportræt</item>\n        <item>Natportræt</item>\n        <item>Fest</item>\n        <item>Kæledyr</item>\n        <item>Portræt</item>\n        <item>Sne</item>\n        <item>Blød hud</item>\n        <item>Sport</item>\n        <item>Spotlight</item>\n        <item>Stabilt foto</item>\n        <item>Solnedgang</item>\n        <item>Sammenhæftning</item>\n        <item>Teater</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatisk</item>\n        <item>Overskyet</item>\n        <item>Hvidglødende</item>\n        <item>Fluorescerende</item>\n        <item>Dagslys</item>\n        <item>Overskyet dagslys</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Rammegennemsnit</item>\n        <item>Centervægtet</item>\n        <item>Spotmåling</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-da/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Start Focal</string>\n    <string name=\"shutter_button\">Udløserknap</string>\n    <string name=\"cannot_connect_hal\">Kan ikke forbinde til kamera</string>\n    <string name=\"recording\">Optager</string>\n    <string name=\"double_tap_to_snapshot\">Dobbelttryk for at tage et billede</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Din enhed understøtter ikke GLES2</string>\n    <string name=\"no_gyroscope\">Din enhed har ikke et gyroskop</string>\n    <string name=\"ps_long_press_to_stop\">Tryk på udløserknappen længe for at\\nafslutte din boble.</string>\n    <string name=\"disabled\">Deaktiveret</string>\n    <string name=\"enabled\">Aktiveret</string>\n    <string name=\"no_video_player\">Kunne ikke afspille video, ingen afspiller fundet</string>\n    <string name=\"fullscreen_shutter_info\">Tryk overalt for at tage et billede</string>\n    <string name=\"please_wait\">Vent venligst\\u2026</string>\n    <string name=\"retouch\">RETOUCHÉR</string>\n    <string name=\"open_in_gallery\">GALLERI</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">BilledBoble</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Sekundært kamera</string>\n    <string name=\"widget_burstmode_count_shots\">Burst %d fotos</string>\n    <string name=\"widget_burstmode_off\">Deaktivér burst-tilstand</string>\n    <string name=\"widget_burstmode_infinite\">Uendelig burst</string>\n    <string name=\"widget_hdr_aebracket\">Auto-bracketing</string>\n    <string name=\"widget_settings_choose_widgets\">Vælg widgets på sidelinjen</string>\n    <string name=\"widget_settings_choose_widgets_button\">Vælg widgets</string>\n    <string name=\"widget_settings_picture_size\">Billedstørrelse</string>\n    <string name=\"widget_settings_exposure_ring\">Vis eksponeringsring</string>\n    <string name=\"widget_settings_autoenhance\">Auto-forbedring</string>\n    <string name=\"widget_settings_ruleofthirds\">Det gyldne snit</string>\n    <string name=\"widget_autoexposure\">Eksponeringsmålingstilstand</string>\n    <string name=\"widget_burstmode\">Burst-tilstand</string>\n    <string name=\"widget_effect\">Farveeffekter</string>\n    <string name=\"widget_enhancements\">Farveforbedringer</string>\n    <string name=\"widget_exposure_compensation\">Eksponeringskompensation</string>\n    <string name=\"widget_flash\">Blitz-tilstand</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">ISO-følsomhed</string>\n    <string name=\"widget_scenemode\">Scenetilstand</string>\n    <string name=\"widget_settings\">Indstillinger</string>\n    <string name=\"widget_shutter_speed\">Udløserhastighed</string>\n    <string name=\"widget_skintone\">Forbedring af hudfarve</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Timer-tilstand</string>\n    <string name=\"widget_videofr\">Rammefrekvens til video</string>\n    <string name=\"widget_videohdr\">HDR til video</string>\n    <string name=\"widget_whitebalance\">Hvidbalance</string>\n    <string name=\"pano_panorama_rendering_failed\">Kunne ikke rendere panorama.\\nPrøv at lave et kortere ét.</string>\n    <string name=\"pano_panorama_rendering\">Renderer panorama\\u2026</string>\n    <string name=\"picsphere_undo_button\">FORTRYD</string>\n    <string name=\"picsphere_start_hint\">Tag et billede for at starte en boble</string>\n    <string name=\"picsphere_already_rendering\">Vent venligst, mens den aktuelle BilledBoble renderes</string>\n    <string name=\"picsphere_rendering_progress\">Renderer... (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Rendering startet\\u2026</string>\n    <string name=\"picsphere_notif_title\">Renderer BilledBoble\\u2026</string>\n    <string name=\"picsphere_failed\">Kunne ikke rendere BilledBoble</string>\n    <string name=\"picsphere_failed_details\">Sørg for, at billederne kun overlapper på deres kanter.</string>\n    <string name=\"picsphere_need_two_pics\">Du skal bruge mindst to billeder.</string>\n    <string name=\"picsphere_step_preparing\">Forbereder\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Optimerer matchede billeder\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Rydder matchende punkter\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Beskærer tomme områder\\u2026</string>\n    <string name=\"picsphere_step_nona\">Sammenhæfter billeder\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Blender billeder\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Beregner HDR-billede\\u2026</string>\n    <string name=\"software_hdr_failed\">Kunne ikke rendere HDR</string>\n    <string name=\"software_hdr_failed_details\">De oprindelige billeder er bibeholdt.</string>\n    <string name=\"showcase_welcome_1_title\">Velkommen!</string>\n    <string name=\"showcase_welcome_1_body\">Valgmulighederne er på sidelinjen,\\ndu skal bare stryge den.</string>\n    <string name=\"showcase_welcome_2_title\">Udløserknap</string>\n    <string name=\"showcase_welcome_2_body\">Tryk på udløserknappen for at tage et billede.\\nStryg den for at tilgå andre optagertilstande.</string>\n    <string name=\"showcase_panorama_title\">Tilstanden Panorama</string>\n    <string name=\"showcase_panorama_body\">Tryk på udløserknappen for at starte din panorama,\\nog panorér derefter til det, du vil indfange.\\nTryk på udløserknappen igen, når du er færdig.</string>\n    <string name=\"showcase_picsphere_title\">Tilstanden BilledBoble</string>\n    <string name=\"showcase_picsphere_body\">Justér dit første billede, hvor du vil,\\nog tryk derefter på udløserknappen for at tage hvert billde\\ni boblen. De blå prikker er reference-\\npunkter, som du kan følge.\\nHold udløserknappen nede længe, når du er færdig.</string>\n    <string name=\"iso_hint_auto\">Automatisk ISO</string>\n    <string name=\"iso_hint_hjr\">Hands-Jitter Reduction ISO</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-de/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>Cid</item>\n        <item>Civ</item>\n        <item>Whiskey</item>\n        <item>Cheese</item>\n    </string-array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Automatisch</item>\n        <item>Aus</item>\n        <item>An</item>\n        <item>Leuchte</item>\n        <item>Rote Augen-Reduzierung</item>\n    </string-array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Aus</item>\n        <item>Schwarz &amp; Weiß</item>\n        <item>Negativ</item>\n        <item>Solarisiert</item>\n        <item>Sepia</item>\n        <item>Posterisiert</item>\n        <item>Kreidetafel</item>\n        <item>Whiteboard</item>\n        <item>Aqua</item>\n        <item>Prägung</item>\n        <item>Skizze</item>\n        <item>Neon</item>\n    </string-array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatisch</item>\n        <item>Bewegungsunschärfe-Reduktion</item>\n        <item>Erweiterte Realität</item>\n        <item>Beste Aufnahme</item>\n        <item>Baby</item>\n        <item>Hintergrundlicht</item>\n        <item>Gegenlicht-Porträt</item>\n        <item>Barcode</item>\n        <item>Strand</item>\n        <item>Kerzenschein</item>\n        <item>Dunkelheit</item>\n        <item>Speisen</item>\n        <item>Dokumente</item>\n        <item>Feuerwerk</item>\n        <item>Blumen</item>\n        <item>Dämmerung</item>\n        <item>HDR</item>\n        <item>Hohe Empfindlichkeit</item>\n        <item>Landschaft</item>\n        <item>Nacht</item>\n        <item>Nacht-Porträt</item>\n        <item>Nacht-Porträt</item>\n        <item>Party</item>\n        <item>Tier</item>\n        <item>Porträt</item>\n        <item>Schnee</item>\n        <item>Weiche Haut</item>\n        <item>Sport</item>\n        <item>Scheinwerfer</item>\n        <item>Stationäres Foto</item>\n        <item>Sonnenuntergang</item>\n        <item>Schwenkpanorama</item>\n        <item>Theater</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatisch</item>\n        <item>Bewölkt</item>\n        <item>Glühlampe</item>\n        <item>Leuchtstofflampe</item>\n        <item>Tageslicht</item>\n        <item>Tageslicht bewölkt</item>\n    </string-array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Mehrfeld</item>\n        <item>Mittenbetont</item>\n        <item>Spot-Messung</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Focal wird gestartet</string>\n    <string name=\"shutter_button\">Auslöser</string>\n    <string name=\"cannot_connect_hal\">Keine Verbindung zur Kamera möglich</string>\n    <string name=\"recording\">Aufnehmen</string>\n    <string name=\"double_tap_to_snapshot\">Doppelklicken, um ein Foto zu machen</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Diese Funktion benötigt ein Gerät mit GLES2-Unterstützung</string>\n    <string name=\"no_gyroscope\">Diese Funktion benötigt ein Gerät mit Gyroskop</string>\n    <string name=\"ps_long_press_to_stop\">Auslöser lange drücken, um die PicSphere zu beenden</string>\n    <string name=\"disabled\">Aus</string>\n    <string name=\"enabled\">An</string>\n    <string name=\"no_video_player\">Video kann nicht abgespielt werden, weil kein geeignetes Wiedergabeprogramm gefunden wurde</string>\n    <string name=\"fullscreen_shutter_info\">Bildschirm berühren, um zu fotografieren</string>\n    <string name=\"please_wait\">Bitte warten\\u2026</string>\n    <string name=\"retouch\">RETUSCHE</string>\n    <string name=\"open_in_gallery\">GALERIE</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Kamera wechseln</string>\n    <string name=\"widget_burstmode_count_shots\">%d Aufnahmen in Serie</string>\n    <string name=\"widget_burstmode_off\">Serienaufnahme aus</string>\n    <string name=\"widget_burstmode_infinite\">Unendlich</string>\n    <string name=\"widget_hdr_aebracket\">Belichtungsreihe</string>\n    <string name=\"widget_settings_choose_widgets\">Widgets der Seitenleiste wählen</string>\n    <string name=\"widget_settings_choose_widgets_button\">Widgets auswählen</string>\n    <string name=\"widget_settings_picture_size\">Bildgröße</string>\n    <string name=\"widget_settings_exposure_ring\">Belichtungsring anzeigen</string>\n    <string name=\"widget_settings_autoenhance\">Autom. Verbesserung</string>\n    <string name=\"widget_settings_ruleofthirds\">Gitternetz</string>\n    <string name=\"widget_autoexposure\">Belichtungsmessung</string>\n    <string name=\"widget_burstmode\">Serienaufnahme</string>\n    <string name=\"widget_effect\">Farbeffekte</string>\n    <string name=\"widget_enhancements\">Farbverbesserungen</string>\n    <string name=\"widget_exposure_compensation\">Belichtungskorrektur</string>\n    <string name=\"widget_flash\">Blitzmodus</string>\n    <string name=\"widget_hdr\">HDR-Aufnahme</string>\n    <string name=\"widget_iso\">ISO</string>\n    <string name=\"widget_scenemode\">Szenenmodus</string>\n    <string name=\"widget_settings\">Einstellungen</string>\n    <string name=\"widget_shutter_speed\">Auslösegeschwindigkeit</string>\n    <string name=\"widget_skintone\">Verbesserung der Hauttöne</string>\n    <string name=\"widget_softwarehdr\">HDR-Aufnahme (SW)</string>\n    <string name=\"widget_timermode\">Selbstauslöser</string>\n    <string name=\"widget_videofr\">Video-Bildrate</string>\n    <string name=\"widget_videohdr\">HDR-Video</string>\n    <string name=\"widget_whitebalance\">Weißabgleich</string>\n    <string name=\"pano_panorama_rendering_failed\">Das Panoramabild konnte nicht erstellt werden./nBitte versuchen Sie eine kürzere Aufnahme</string>\n    <string name=\"pano_panorama_rendering\">Panoramabild wird erstellt\\u2026</string>\n    <string name=\"picsphere_undo_button\">Rückgängig</string>\n    <string name=\"picsphere_start_hint\">Zum Starten ein Bild machen</string>\n    <string name=\"picsphere_already_rendering\">Bitte warten, bis das laufende Zusammenfügen abgeschlossen ist</string>\n    <string name=\"picsphere_rendering_progress\">PicSphere wird zusammengefügt\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Zusammenfügen gestartet\\u2026</string>\n    <string name=\"picsphere_notif_title\">PicSphere wird zusammengefügt\\u2026</string>\n    <string name=\"picsphere_failed\">Zusammenfügen fehlgeschlagen</string>\n    <string name=\"picsphere_failed_details\">Bitte darauf achten, dass sich Fotos nur am Rand überlappen.</string>\n    <string name=\"picsphere_need_two_pics\">Es sind mindestens zwei Fotos erforderlich.</string>\n    <string name=\"picsphere_step_preparing\">Vorbereiten\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Metadaten generieren\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Kameraausrichtung speichern\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Kontrollpunkte suchen\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Bilder ausrichten\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Kontrollpunkte löschen\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Leere Bereiche entfernen\\u2026</string>\n    <string name=\"picsphere_step_nona\">Bilder zusammenfügen\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Bilder überblenden\\u2026</string>\n    <string name=\"software_hdr_notif_title\">HDR-Bild wird verarbeitet\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR-Berechnung fehlgeschlagen</string>\n    <string name=\"software_hdr_failed_details\">Die Quellbilder wurden erhalten.</string>\n    <string name=\"showcase_welcome_1_title\">Willkommen!</string>\n    <string name=\"showcase_welcome_1_body\">Die Optionen sind in der Seitenleiste,\\nziehen Sie sie einfach heraus.</string>\n    <string name=\"showcase_welcome_2_title\">Auslöser</string>\n    <string name=\"showcase_welcome_2_body\">Drücken Sie den Auslöser, um ein Foto zu machen.\\nZiehen Sie ihn, um weitere Modi anzuzeigen.</string>\n    <string name=\"showcase_panorama_title\">Panorama-Modus</string>\n    <string name=\"showcase_panorama_body\">Drücken Sie den Auslöser, um ein Panorama zu beginnen und schwenken Sie Ihr Gerät für die Aufnahme.\\nWenn Sie fertig sind, drücken Sie den Auslöser erneut.</string>\n    <string name=\"showcase_picsphere_title\">PicSphere-Modus</string>\n    <string name=\"showcase_picsphere_body\">Richten Sie das erste Bild aus. Danach drücken Sie für jedes Bild der PicSphere einmal den Auslöser. Die blauen Punkte dienen als Referenz.\\nDrücken Sie den Auslöser lange, um die Aufnahme abzuschließen.</string>\n    <string name=\"iso_hint_auto\">Automatisch</string>\n    <string name=\"iso_hint_hjr\">Bildstabilisierung</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-el/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Αυτόματο</item>\n        <item>Απενεργοποιημένο</item>\n        <item>Ενεργοποιημένο</item>\n        <item>Φακός</item>\n        <item>Μείωση κόκκινων ματιών</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Απενεργοποιημένο</item>\n        <item>Ασπρόμαυρο</item>\n        <item>Αρνητικό</item>\n        <item>Υπερφωτισμός</item>\n        <item>Σέπια</item>\n        <item>Ποστεροποίηση</item>\n        <item>Μαύρος πίνακας</item>\n        <item>Άσπρος πίνακας</item>\n        <item>Νερό</item>\n        <item>Ξεθώριασμα</item>\n        <item>Σκίτσο</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Αυτόματη</item>\n        <item>Μείωση motion blur</item>\n        <item>Επαυξημένη πραγματικότητα</item>\n        <item>Καλύτερη λήψη</item>\n        <item>Μωρό</item>\n        <item>Οπίσθιος φωτισμός</item>\n        <item>Πορτραίτο με οπίσθιο φωτισμό</item>\n        <item>Barcode</item>\n        <item>Παραλία</item>\n        <item>Φως κεριού</item>\n        <item>Σκοτάδι</item>\n        <item>Πιάτο</item>\n        <item>Έγγραφο</item>\n        <item>Πυροτεχνήματα</item>\n        <item>Λουλούδια</item>\n        <item>Αμυδρό φως</item>\n        <item>HDR</item>\n        <item>Υψηλή ευαισθησία</item>\n        <item>Τοπίο</item>\n        <item>Νύχτα</item>\n        <item>Νυχτερινό πορτραίτο</item>\n        <item>Νυχτερινό πορτραίτο</item>\n        <item>Πάρτι</item>\n        <item>Κατοικίδιο</item>\n        <item>Πορτραίτο</item>\n        <item>Χιόνι</item>\n        <item>Απαλό δέρμα</item>\n        <item>Αθλήματα</item>\n        <item>Προβολέας</item>\n        <item>Σταθερή φωτογραφία</item>\n        <item>Ηλιοβασίλεμα</item>\n        <item>Λειτουργία sweep</item>\n        <item>Θέατρο</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Αυτόματη</item>\n        <item>Συννεφιά</item>\n        <item>Λαμπτήρας πυρακτώσεως</item>\n        <item>Λαμπτήρας φθορισμού</item>\n        <item>Φως ημέρας</item>\n        <item>Φώς συννεφιασμένης ημέρας</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Μέσος όρος κάδρου</item>\n        <item>Μέτρηση κέντρου βάρους</item>\n        <item>Σημειακή μέτρηση</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-el/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Εκκίνηση Focal</string>\n    <string name=\"shutter_button\">Πλήκτρο κλείστρου</string>\n    <string name=\"cannot_connect_hal\">Δεν είναι δυνατή η σύνδεση με τη φωτογραφική μηχανή</string>\n    <string name=\"recording\">Εγγραφή</string>\n    <string name=\"double_tap_to_snapshot\">Διπλό πάτημα για λήψη φωτογραφίας</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">ΟΚ</string>\n    <string name=\"no_gles20_support\">Η συσκευή σας δεν υποστηρίζει GLES2</string>\n    <string name=\"no_gyroscope\">Η συσκευή σας δεν έχει γυροσκόπιο</string>\n    <string name=\"ps_long_press_to_stop\">Κρατήστε πατημένο το πλήκτρο\\nκλείστρου για να ολοκληρώσετε το sphere σας.</string>\n    <string name=\"disabled\">Ανενεργό</string>\n    <string name=\"enabled\">Ενεργό</string>\n    <string name=\"no_video_player\">Δεν είναι δυνατή η αναπαραγωγή του βίντεο, δεν βρέθηκε πρόγραμμα αναπαραγωγής</string>\n    <string name=\"fullscreen_shutter_info\">Πιέστε οπουδήποτε για λήψη</string>\n    <string name=\"please_wait\">Παρακαλώ περιμένετε\\u2026</string>\n    <string name=\"retouch\">ΡΕΤΟΥΣ</string>\n    <string name=\"open_in_gallery\">ΣΥΛΛΟΓΗ</string>\n    <string name=\"mode_photo\">Φωτογραφία</string>\n    <string name=\"mode_video\">Βίντεο</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Πανόραμα</string>\n    <string name=\"mode_switchcam\">Εμπρόσθια</string>\n    <string name=\"widget_burstmode_count_shots\">Ρίψη %d λήψεων</string>\n    <string name=\"widget_burstmode_off\">Απενεργοποίηση λειτουργίας ριπής</string>\n    <string name=\"widget_burstmode_infinite\">Απεριόριστη ριπή</string>\n    <string name=\"widget_hdr_aebracket\">Αυτόματο bracketing</string>\n    <string name=\"widget_settings_choose_widgets\">Επιλέξτε widget στην πλαϊνή μπάρα</string>\n    <string name=\"widget_settings_choose_widgets_button\">Επιλέξτε widget</string>\n    <string name=\"widget_settings_picture_size\">Μέγεθος εικόνας</string>\n    <string name=\"widget_settings_exposure_ring\">Εμφάνιση δαχτυλιδιού έκθεσης</string>\n    <string name=\"widget_settings_autoenhance\">Αυτόματη βελτίωση</string>\n    <string name=\"widget_settings_ruleofthirds\">Νόμος των τρίτων</string>\n    <string name=\"widget_autoexposure\">Λειτουργία μέτρησης έκθεσης</string>\n    <string name=\"widget_burstmode\">Λειτουργία ριπής</string>\n    <string name=\"widget_effect\">Εφέ χρώματος</string>\n    <string name=\"widget_enhancements\">Βελτιώσεις χρώματος</string>\n    <string name=\"widget_exposure_compensation\">Αντιστάθμιση έκθεσης</string>\n    <string name=\"widget_flash\">Λειτουργία φλας</string>\n    <string name=\"widget_hdr\">Υψηλό δυναμικό εύρος (HDR)</string>\n    <string name=\"widget_iso\">Ευαισθησία ISO</string>\n    <string name=\"widget_scenemode\">Λειτουργία σκηνής</string>\n    <string name=\"widget_settings\">Ρυθμίσεις</string>\n    <string name=\"widget_shutter_speed\">Ταχύτητα κλείστρου</string>\n    <string name=\"widget_skintone\">Βελτίωση τόνου δέρματος</string>\n    <string name=\"widget_softwarehdr\">Υψηλό δυναμικό εύρος (HDR)</string>\n    <string name=\"widget_timermode\">Λειτουργία χρονοδιακόπτη</string>\n    <string name=\"widget_videofr\">Ρυθμός καρέ βίντεο</string>\n    <string name=\"widget_videohdr\">Υψηλό δυναμικό εύρος βίντεο (HDR)</string>\n    <string name=\"widget_whitebalance\">Ισορροπία λευκού</string>\n    <string name=\"pano_panorama_rendering_failed\">Απέτυχε η επεξεργασία του πανοράματος.\\nΠροσπαθήστε να τραβήξετε ένα μικρότερο.</string>\n    <string name=\"pano_panorama_rendering\">Επεξεργασία πανοράματος\\u2026</string>\n    <string name=\"picsphere_undo_button\">ΑΝΑΙΡΕΣΗ</string>\n    <string name=\"picsphere_start_hint\">Τραβήξτε μια φωτογραφία για να ξεκινήσετε ένα sphere</string>\n    <string name=\"picsphere_already_rendering\">Παρακαλώ περιμένετε την επεξεργασία του τρέχοντος PicSphere</string>\n    <string name=\"picsphere_rendering_progress\">Επεξεργασία\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Η επεξεργασία ξεκίνησε\\u2026</string>\n    <string name=\"picsphere_notif_title\">Επεξεργασία PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Απέτυχε η επεξεργασία του PicSphere</string>\n    <string name=\"picsphere_failed_details\">Βεβαιωθείτε ότι οι εικόνες επικαλύπτονται μόνο στις άκρες τους.</string>\n    <string name=\"picsphere_need_two_pics\">Χρειάζεστε τουλάχιστον δύο φωτογραφίες.</string>\n    <string name=\"picsphere_step_preparing\">Προετοιμασία\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Δημιουργία μεταδεδομένων\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Αποθήκευση προσανατολισμού μηχανής\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Εύρεση σημείων ελέγχου\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Βελτίωση ταιριάγματος\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Καθαρισμός σημείων\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Περικόπή άδειων περιοχών\\u2026</string>\n    <string name=\"picsphere_step_nona\">Συρραφή εικόνων\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Συνδυασμός εικόνων\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Υπολογισμός φωτογραφίας HDR\\u2026</string>\n    <string name=\"software_hdr_failed\">Απέτυχε η επεξεργασία HDR</string>\n    <string name=\"software_hdr_failed_details\">Οι αρχικές φωτογραφίες έχουν αποθηκευτεί.</string>\n    <string name=\"showcase_welcome_1_title\">Καλώς ήλθατε!</string>\n    <string name=\"showcase_welcome_1_body\">Οι επιλογές είναι στην πλαϊνή μπάρα,\\nαπλώς σύρτε την.</string>\n    <string name=\"showcase_welcome_2_title\">Πλήκτρο κλείστρου</string>\n    <string name=\"showcase_welcome_2_body\">Πιέστε το πλήκτρο κλείστρου για να τραβήξετε\\nμια φωτογραφία. Συρετέ το για πρόσβαση\\nστις άλλες λειτουργίες λήψης.</string>\n    <string name=\"showcase_panorama_title\">Λειτουργία πανοράματος</string>\n    <string name=\"showcase_panorama_body\">Πιέστε το πλήκτρο κλείστρου για να ξεκινήσετε το\\nπανόραμα και επιλέξτε τι θέλετε να καταγράψετε.\\nΜόλις τελειώσετε, πιέστε ξανά το πλήκτρο κλείστρου.</string>\n    <string name=\"showcase_picsphere_title\">Λειτουργία PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Ευθυγραμμίστε την πρώτη εικόνα όπου σας αρέσει\\nκαι μετά πιέστε το πλήκτρο κλείστρου για να τραβήξετε\\nκάθε φωτογραφία του sphere. Οι μπλε τελείες είναι\\nσημεία αναφοράς που μπορείτε να ακολουθήσετε.\\nΜόλις τελειώσετε, πιέστε παρατεταμένα το πλήκτρο κλείστρου.</string>\n    <string name=\"iso_hint_auto\">Αυτόματο ISO</string>\n    <string name=\"iso_hint_hjr\">ISO μείωσης κουνήματος χεριού</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-en-rGB/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"widget_enhancements\">Colour adjust.</string>\n</resources>\n"
  },
  {
    "path": "res/values-es/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>patata</item>\n        <item>bien cid, toma una foto</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Automático</item>\n        <item>Apagado</item>\n        <item>Encendido</item>\n        <item>Linterna</item>\n        <item>Ojos rojos</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Ninguno</item>\n        <item>Blanco y negro</item>\n        <item>Negativo</item>\n        <item>Solarizar</item>\n        <item>Sepia</item>\n        <item>Posterizar</item>\n        <item>Fondo oscuro</item>\n        <item>Fondo claro</item>\n        <item>Agua</item>\n        <item>Realzar</item>\n        <item>Dibujo</item>\n        <item>Neón</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automático</item>\n        <item>Reducción desenfoque movimiento</item>\n        <item>Realidad aumentada</item>\n        <item>Mejor toma</item>\n        <item>Bebé</item>\n        <item>Contraluz</item>\n        <item>Retrato a contraluz</item>\n        <item>Código de barras</item>\n        <item>Playa</item>\n        <item>Luz de vela</item>\n        <item>Oscuro</item>\n        <item>Reflector</item>\n        <item>Documento</item>\n        <item>Fuegos artificiales</item>\n        <item>Flores</item>\n        <item>Crepúsculo</item>\n        <item>HDR</item>\n        <item>Alta sensibilidad</item>\n        <item>Paisaje</item>\n        <item>Noche</item>\n        <item>Retrato nocturno</item>\n        <item>Interior nocturno</item>\n        <item>Fiesta</item>\n        <item>Mascota</item>\n        <item>Retrato</item>\n        <item>Nieve</item>\n        <item>Suavizado</item>\n        <item>Deporte</item>\n        <item>Destacado</item>\n        <item>Estable</item>\n        <item>Atarceder</item>\n        <item>Superposición</item>\n        <item>Teatro</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automático</item>\n        <item>Nublado</item>\n        <item>Incandescente</item>\n        <item>Fluorescente</item>\n        <item>Luz natural</item>\n        <item>Parc. nublado</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Matricial</item>\n        <item>Ponderada al centro</item>\n        <item>Puntual</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Abrir Focal</string>\n    <string name=\"shutter_button\">Disparador</string>\n    <string name=\"cannot_connect_hal\">Imposible conectar con la cámara</string>\n    <string name=\"recording\">Grabando</string>\n    <string name=\"double_tap_to_snapshot\">Doble toque para tomar una foto</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">ACEPTAR</string>\n    <string name=\"no_gles20_support\">El dispositivo no soporta GLES2</string>\n    <string name=\"no_gyroscope\">El dispositivo no dispone de giróscopo</string>\n    <string name=\"ps_long_press_to_stop\">Para finalizar la captura haz una\\npulsación larga sobre el disparador.</string>\n    <string name=\"disabled\">Deshabilitado</string>\n    <string name=\"enabled\">Habilitado</string>\n    <string name=\"no_video_player\">Imposible reproducir el vídeo. No se ha encontrado un reproductor</string>\n    <string name=\"fullscreen_shutter_info\">Toca en cualquier sitio para disparar</string>\n    <string name=\"please_wait\">Por favor, espera\\u2026</string>\n    <string name=\"retouch\">RETOCAR</string>\n    <string name=\"open_in_gallery\">GALERÍA</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Vídeo</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorámica</string>\n    <string name=\"mode_switchcam\">Frontal</string>\n    <string name=\"widget_burstmode_count_shots\">Ráfaga de %d disparos</string>\n    <string name=\"widget_burstmode_off\">Modo ráfaga deshabilitado</string>\n    <string name=\"widget_burstmode_infinite\">Ráfaga continua</string>\n    <string name=\"widget_hdr_aebracket\">Exposición HDR automática</string>\n    <string name=\"widget_settings_choose_widgets\">Elegir los widgets para la barra lateral</string>\n    <string name=\"widget_settings_choose_widgets_button\">Elegir widgets</string>\n    <string name=\"widget_settings_picture_size\">Tamaño de imagen</string>\n    <string name=\"widget_settings_exposure_ring\">Control de exposición</string>\n    <string name=\"widget_settings_autoenhance\">Mejoras automáticas</string>\n    <string name=\"widget_settings_ruleofthirds\">Regla de los tercios</string>\n    <string name=\"widget_autoexposure\">Medición de exposición</string>\n    <string name=\"widget_burstmode\">Modo de ráfaga</string>\n    <string name=\"widget_effect\">Efectos de color</string>\n    <string name=\"widget_enhancements\">Mejoras de color</string>\n    <string name=\"widget_exposure_compensation\">Compensación de exposición</string>\n    <string name=\"widget_flash\">Modo de flash</string>\n    <string name=\"widget_hdr\">Alto rango dinámico (HDR)</string>\n    <string name=\"widget_iso\">Sensibilidad ISO</string>\n    <string name=\"widget_scenemode\">Modo de escena</string>\n    <string name=\"widget_settings\">Ajustes</string>\n    <string name=\"widget_shutter_speed\">Velocidad obturador</string>\n    <string name=\"widget_skintone\">Mejora tono de piel</string>\n    <string name=\"widget_softwarehdr\">Alto rango dinámico (HDR)</string>\n    <string name=\"widget_timermode\">Temporizador</string>\n    <string name=\"widget_videofr\">Fotogramas por segundo</string>\n    <string name=\"widget_videohdr\">Vídeo de alto rango dinámico</string>\n    <string name=\"widget_whitebalance\">Balance de blancos</string>\n    <string name=\"pano_panorama_rendering_failed\">Error al procesar la foto panorámica.\\nIntenta tomar una captura más corta.</string>\n    <string name=\"pano_panorama_rendering\">Procesando la foto panorámica\\u2026</string>\n    <string name=\"picsphere_undo_button\">DESHACER</string>\n    <string name=\"picsphere_start_hint\">Toma una foto para iniciar PicSphere</string>\n    <string name=\"picsphere_already_rendering\">Por favor, espera a que termine el procesado de la foto PicSphere actual</string>\n    <string name=\"picsphere_rendering_progress\">Procesando\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Iniciando procesamiento\\u2026</string>\n    <string name=\"picsphere_notif_title\">Procesando la foto PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Error al procesar la foto PicSphere</string>\n    <string name=\"picsphere_failed_details\">Asegúrate de que las imágenes solo se solapan en sus bordes.</string>\n    <string name=\"picsphere_need_two_pics\">Necesitas al menos dos imágenes.</string>\n    <string name=\"picsphere_step_preparing\">Preparando\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Generando metadatos\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Guardando orientación\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Buscando puntos de control\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Optimizando coincidencias\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Limpiando zonas coincidentes\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Recortando áreas vacías\\u2026</string>\n    <string name=\"picsphere_step_nona\">Procesando imágenes\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Uniendo imágenes\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Procesando imagen HDR\\u2026</string>\n    <string name=\"software_hdr_failed\">Error al procesar la imagen HDR</string>\n    <string name=\"software_hdr_failed_details\">Las imágenes originales se han conservado.</string>\n    <string name=\"showcase_welcome_1_title\">¡Bienvenido!</string>\n    <string name=\"showcase_welcome_1_body\">Las opciones se encuentran en la barra lateral.\\nDesliza sobre la pantalla para mostrarlas.</string>\n    <string name=\"showcase_welcome_2_title\">Disparador</string>\n    <string name=\"showcase_welcome_2_body\">Toca sobre el disparador para tomar una foto.\\nDesliza sobre él para acceder a otros modos de captura.</string>\n    <string name=\"showcase_panorama_title\">Panorámica</string>\n    <string name=\"showcase_panorama_body\">Toca sobre el disparador para iniciar la foto panorámica\\ny desplaza lentamente la cámara sobre lo que deseas capturar.\\nPara terminar, toca sobre el disparador nuevamente.</string>\n    <string name=\"showcase_picsphere_title\">PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Toca sobre el disparador para tomar la primera imagen.\\nAhora, toca sucesivamente el disparador en cada uno de los\\npuntos azules que aparecerán como referencia, para ir\\ncomponiendo la foto PicSphere.\\nUn toque largo sobre el disparador finalizará el proceso.</string>\n    <string name=\"iso_hint_auto\">ISO automático</string>\n    <string name=\"iso_hint_hjr\">ISO reductor temblor</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-fi/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Automaattinen</item>\n        <item>Pois käytöstä</item>\n        <item>Käytössä</item>\n        <item>Lamppu</item>\n        <item>Punasilmäisyyden poisto</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Pois käytöstä</item>\n        <item>Mustavalkoinen</item>\n        <item>Negatiivi</item>\n        <item>Valotus</item>\n        <item>Seepia</item>\n        <item>Juliste</item>\n        <item>Liitutaulu</item>\n        <item>Valkotaulu</item>\n        <item>Aqua</item>\n        <item>Kohokuva</item>\n        <item>Piirros</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automaattinen</item>\n        <item>Liikkeen sumennuksen vähentäminen</item>\n        <item>Lisätty todellisuus</item>\n        <item>Paras otos</item>\n        <item>Vauva</item>\n        <item>Taustavalo</item>\n        <item>Taustavalo/Muotokuva</item>\n        <item>Viivakoodi</item>\n        <item>Ranta</item>\n        <item>Kynttilänvalo</item>\n        <item>Pimeä</item>\n        <item>Tarjoilu</item>\n        <item>Dokumentti</item>\n        <item>Ilotulitus</item>\n        <item>Kukat</item>\n        <item>Hämärä/Käsikuvaus</item>\n        <item>HDR</item>\n        <item>Erittäin herkkä</item>\n        <item>Vaakataso</item>\n        <item>Yö</item>\n        <item>Yö/Muotokuva</item>\n        <item>Yö/Muotokuva</item>\n        <item>Juhla</item>\n        <item>Lemmikki</item>\n        <item>Muotokuva</item>\n        <item>Lumi</item>\n        <item>Pehmeä iho</item>\n        <item>Urheilu</item>\n        <item>Valokeila</item>\n        <item>Vakaa kuva</item>\n        <item>Auringonlasku</item>\n        <item>Puhdistettu yhdistelmä</item>\n        <item>Teatteri</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automaattinen</item>\n        <item>Pilvinen</item>\n        <item>Hehkuva</item>\n        <item>Fluoresoiva</item>\n        <item>Päivänvalo</item>\n        <item>Pilvinen/Päivänvalo</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Keskimääräinen kehys</item>\n        <item>Keskelle keskittynyt</item>\n        <item>Pisteeseen kohdistettu</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Käynnistä Focal</string>\n    <string name=\"shutter_button\">Kuvausnäppäin</string>\n    <string name=\"cannot_connect_hal\">Ei yhteyttä kameraan</string>\n    <string name=\"recording\">Nauhoitetaan</string>\n    <string name=\"double_tap_to_snapshot\">Tupla-kosketa ottaaksesi kuvan</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Laitteesi ei tue GLES2:ta</string>\n    <string name=\"no_gyroscope\">Laitteessasi ei ole gyroskooppia</string>\n    <string name=\"ps_long_press_to_stop\">Paina kuvausnäppäintä pitkään lopettaaksesi spheren.</string>\n    <string name=\"disabled\">Pois käytöstä</string>\n    <string name=\"enabled\">Käytössä</string>\n    <string name=\"no_video_player\">Videon toistaminen epäonnistui, soitinta ei löydy</string>\n    <string name=\"fullscreen_shutter_info\">Paina mihin tahansa ottaaksesi kuvan</string>\n    <string name=\"please_wait\">Odota\\u2026</string>\n    <string name=\"retouch\">UUDELLEENKOSKETA</string>\n    <string name=\"open_in_gallery\">GALLERIA</string>\n    <string name=\"mode_photo\">Kuva</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panoraama</string>\n    <string name=\"mode_switchcam\">Kameran vaihto</string>\n    <string name=\"widget_burstmode_count_shots\">%d kuvan sarja</string>\n    <string name=\"widget_burstmode_off\">Poista sarjakuvaus käytöstä</string>\n    <string name=\"widget_burstmode_infinite\">Loputon sarjakuvaus</string>\n    <string name=\"widget_hdr_aebracket\">Automaattinen haarukointi</string>\n    <string name=\"widget_settings_choose_widgets\">Valitse sivuvalikon widgetit</string>\n    <string name=\"widget_settings_choose_widgets_button\">Valitse widgetit</string>\n    <string name=\"widget_settings_picture_size\">Kuvan koko</string>\n    <string name=\"widget_settings_exposure_ring\">Näytä valotuskehä</string>\n    <string name=\"widget_settings_autoenhance\">Automaattinen parantaminen</string>\n    <string name=\"widget_settings_ruleofthirds\">3x3-ruudukko</string>\n    <string name=\"widget_autoexposure\">Automaattinen valotusarvo</string>\n    <string name=\"widget_burstmode\">Sarjakuvaus</string>\n    <string name=\"widget_effect\">Väriefektit</string>\n    <string name=\"widget_enhancements\">Värimuutokset</string>\n    <string name=\"widget_exposure_compensation\">Valotuksen korjaus</string>\n    <string name=\"widget_flash\">Salama</string>\n    <string name=\"widget_hdr\">High Dynamic Range</string>\n    <string name=\"widget_iso\">ISO-herkkyys</string>\n    <string name=\"widget_scenemode\">Scene Mode</string>\n    <string name=\"widget_settings\">Asetukset</string>\n    <string name=\"widget_shutter_speed\">Sulkimen nopeus</string>\n    <string name=\"widget_skintone\">Ihonvärin vaihto</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Ajastin</string>\n    <string name=\"widget_videofr\">Videon kuvausnopeus</string>\n    <string name=\"widget_videohdr\">Video HDR</string>\n    <string name=\"widget_whitebalance\">Valkotasapaino</string>\n    <string name=\"pano_panorama_rendering_failed\">Panoraamakuvan mallinnus epäonnistui.\\Yritä ottaa lyhyempi kuva.</string>\n    <string name=\"pano_panorama_rendering\">Mallinnetaan panoraamaa\\u2026</string>\n    <string name=\"picsphere_undo_button\">PERU</string>\n    <string name=\"picsphere_start_hint\">Ota kuva aloittaaksesi spheren</string>\n    <string name=\"picsphere_already_rendering\">Odota kun PicSphere-kuvaa mallinnetaan</string>\n    <string name=\"picsphere_rendering_progress\">Mallinnetan\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Mallinnus aloitettu\\u2026</string>\n    <string name=\"picsphere_notif_title\">Mallinnetaan PicSphere-kuvaa\\u2026</string>\n    <string name=\"picsphere_failed\">PicSpheren mallinnus epäonnistui</string>\n    <string name=\"picsphere_failed_details\">Varmista että kuvat ovat päällekkäin vain reunoilla.</string>\n    <string name=\"picsphere_need_two_pics\">Tarvitset vähintään kaksi kuvaa.</string>\n    <string name=\"picsphere_step_preparing\">Valmistellaan\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Luodaan metadataa\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Tarkistetaan kameran asentoa\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Etsitään ohjauspisteet\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Käännetään kuvia\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Puhdistetaan pisteitä\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Rajataan tyhjiä alueita\\u2026</string>\n    <string name=\"picsphere_step_nona\">Yhdistetään kuvia\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Sulautetaan kuvia\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Luodaan HDR-kuvaa\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR-kuvan mallinnus epäonnistui</string>\n    <string name=\"software_hdr_failed_details\">Alkuperäiset kuvat säilytettiin.</string>\n    <string name=\"showcase_welcome_1_title\">Tervetuloa!</string>\n    <string name=\"showcase_welcome_1_body\">Asetukset ovat sivuvalikossa, liu\\'uta näyttääksesi.</string>\n    <string name=\"showcase_welcome_2_title\">Kuvausnäppäin</string>\n    <string name=\"showcase_welcome_2_body\">Paina kuvausnäppäintä ottaksesi kuvan.\\nLiu\\'ta sitä päästäksesi muihin tiloihin.</string>\n    <string name=\"showcase_panorama_title\">Panoraamatila</string>\n    <string name=\"showcase_panorama_body\">Paina kuvausnäppäintä aloittaaksesi panoraamakuvan,\\nja kuvaa haluamaltasi alueelta.\\nKun kuva on valmis, paina suljinnäppäintä uudestaan.</string>\n    <string name=\"showcase_picsphere_title\">PicSphere-tila</string>\n    <string name=\"showcase_picsphere_body\">Osoita ensimmäinen kuva mihin haluat,\\nsitten paina kuvausnäppäintä ottaaksesi jokaisen kuvan spherestä.\\nSiniset pisteet ovat apuna.\\nPaina kuvausnäppäintä pitkään kun olet valmis.</string>\n    <string name=\"iso_hint_auto\">Automaattinen ISO</string>\n    <string name=\"iso_hint_hjr\">Käsien tärinän vähentäminen ISO</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-fr/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>ouistiti</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>cerise</item>\n        <item>ok cid, prend une photo</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Automatique</item>\n        <item>Désactivé</item>\n        <item>Activé</item>\n        <item>Flash</item>\n        <item>Yeux rouges</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Désactivé</item>\n        <item>Noir \\u0026 blanc</item>\n        <item>Negatif</item>\n        <item>Solariser</item>\n        <item>Sépia</item>\n        <item>Postérisé</item>\n        <item>Tableau noir</item>\n        <item>Tableau blanc</item>\n        <item>Eau</item>\n        <item>Embossage</item>\n        <item>Croquis</item>\n        <item>Néon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatique</item>\n        <item>Réduction du flou de mouvement</item>\n        <item>Réalité augmentée</item>\n        <item>Meilleure prise</item>\n        <item>Bébé</item>\n        <item>Rétroéclairé</item>\n        <item>Portrait rétroéclairé</item>\n        <item>Code barre</item>\n        <item>Plage</item>\n        <item>Bougie</item>\n        <item>Foncé</item>\n        <item>Délavé</item>\n        <item>Document</item>\n        <item>Feux d\\'artifice</item>\n        <item>Fleurs</item>\n        <item>Crépuscule</item>\n        <item>HDR</item>\n        <item>Haute sensibilité</item>\n        <item>Paysage</item>\n        <item>Nuit</item>\n        <item>Portrait de nuit</item>\n        <item>Portrait de nuit</item>\n        <item>Partie</item>\n        <item>Animal</item>\n        <item>Portrait</item>\n        <item>Neige</item>\n        <item>Peau douce</item>\n        <item>Sports</item>\n        <item>Projecteur</item>\n        <item>Photo stable</item>\n        <item>Coucher de soleil</item>\n        <item>Point de balayage</item>\n        <item>Théâtre</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatique</item>\n        <item>Nuageux</item>\n        <item>Incandescent</item>\n        <item>Fluorescent</item>\n        <item>Lumière du jour</item>\n        <item>Lumière du jour nuageux</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Cadre moyen</item>\n        <item>Pondéré au centre</item>\n        <item>Mesure du spot</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Lancer Focal</string>\n    <string name=\"shutter_button\">Déclencheur</string>\n    <string name=\"cannot_connect_hal\">Impossible de lancer l\\'appareil photo</string>\n    <string name=\"recording\">Enregistrement</string>\n    <string name=\"double_tap_to_snapshot\">Tapez deux fois pour prendre une photo</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Votre appareil n\\'est pas\\ncompatible OpenGLES 2.0</string>\n    <string name=\"no_gyroscope\">Votre appareil n\\'a pas de gyroscope</string>\n    <string name=\"ps_long_press_to_stop\">Maintenez appuyé le déclencheur\\npour terminer la sphère.</string>\n    <string name=\"disabled\">Désactivé</string>\n    <string name=\"enabled\">Activé</string>\n    <string name=\"no_video_player\">Impossible de lire la vidéo</string>\n    <string name=\"fullscreen_shutter_info\">Appuyez n\\'importe où pour capturer</string>\n    <string name=\"please_wait\">Veuillez patienter\\u2026</string>\n    <string name=\"retouch\">RETOUCHER</string>\n    <string name=\"open_in_gallery\">GALERIE</string>\n    <string name=\"mode_photo\">Photo</string>\n    <string name=\"mode_video\">Vidéo</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Direction</string>\n    <string name=\"widget_burstmode_count_shots\">Rafale de %d images</string>\n    <string name=\"widget_burstmode_off\">Sans mode rafale</string>\n    <string name=\"widget_burstmode_infinite\">Rafale infinie</string>\n    <string name=\"widget_hdr_aebracket\">Bracketing automatique</string>\n    <string name=\"widget_settings_choose_widgets\">Widgets à afficher</string>\n    <string name=\"widget_settings_choose_widgets_button\">Widgets à afficher</string>\n    <string name=\"widget_settings_picture_size\">Taille de l\\'image</string>\n    <string name=\"widget_settings_exposure_ring\">Bague d\\'exposition</string>\n    <string name=\"widget_settings_autoenhance\">Auto-amélioration</string>\n    <string name=\"widget_settings_ruleofthirds\">Règle de trois</string>\n    <string name=\"widget_autoexposure\">Mode de mesure d\\'exposition</string>\n    <string name=\"widget_burstmode\">Mode rafale</string>\n    <string name=\"widget_effect\">Effets de couleur</string>\n    <string name=\"widget_enhancements\">Réglage des couleurs</string>\n    <string name=\"widget_exposure_compensation\">Exposition</string>\n    <string name=\"widget_flash\">Mode de flash</string>\n    <string name=\"widget_hdr\">High Dynamic Range</string>\n    <string name=\"widget_iso\">Sensibilité ISO</string>\n    <string name=\"widget_scenemode\">Mode scène</string>\n    <string name=\"widget_settings\">Paramètres</string>\n    <string name=\"widget_shutter_speed\">Vitesse d\\'obturation</string>\n    <string name=\"widget_skintone\">Amélioration de la peau</string>\n    <string name=\"widget_softwarehdr\">High Dynamic Range</string>\n    <string name=\"widget_timermode\">Retardateur</string>\n    <string name=\"widget_videofr\">Vitesse vidéo</string>\n    <string name=\"widget_videohdr\">Vidéo High Dynamic Range</string>\n    <string name=\"widget_whitebalance\">Balance des blancs</string>\n    <string name=\"pano_panorama_rendering_failed\">Le rendu a échoué.\\nEssayez de prendre un panorama plus étroit.</string>\n    <string name=\"pano_panorama_rendering\">Rendu du panorama\\u2026</string>\n    <string name=\"picsphere_undo_button\">ANNULER</string>\n    <string name=\"picsphere_start_hint\">Prenez une photo pour commencer</string>\n    <string name=\"picsphere_already_rendering\">Veuillez attendre la fin du rendu\\nde la sphère précédente</string>\n    <string name=\"picsphere_rendering_progress\">Rendu en cours\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Rendu de la PicSphere\\u2026</string>\n    <string name=\"picsphere_notif_title\">Rendu de la PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Rendu de la PicSphere échoué</string>\n    <string name=\"picsphere_failed_details\">Les images ne doivent se superposer que sur les bords.</string>\n    <string name=\"picsphere_need_two_pics\">Deux images minimum sont requises.</string>\n    <string name=\"picsphere_step_preparing\">Préparation\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Génération des métadonnées\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Écriture de l\\'orientation de l\\'appareil photo\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Recherche des points de contrôle\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Alignement des images\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Nettoyage des points\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Rognage des coins\\u2026</string>\n    <string name=\"picsphere_step_nona\">Déformation des images\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Finalisation de l\\'image\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Rendu HDR en cours\\u2026</string>\n    <string name=\"software_hdr_failed\">Échec du rendu HDR</string>\n    <string name=\"software_hdr_failed_details\">Les images originales ont été conservées.</string>\n    <string name=\"showcase_welcome_1_title\">Bienvenue\\u00A0!</string>\n    <string name=\"showcase_welcome_1_body\">Les paramètres sont situés dans la\\nbarre latérale.</string>\n    <string name=\"showcase_welcome_2_title\">Déclencheur</string>\n    <string name=\"showcase_welcome_2_body\">Appuyez sur le déclencheur pour\\nprendre une photo.\\nFaites-le glisser pour changer de mode.</string>\n    <string name=\"showcase_panorama_title\">Mode Panorama</string>\n    <string name=\"showcase_panorama_body\">Appuyez sur le déclencheur pour démarrer,\\npuis orientez simplement le téléphone.\\nUne fois fini, appuyez de nouveau sur le déclencheur.</string>\n    <string name=\"showcase_picsphere_title\">Mode PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Prenez la première photo où vous le souhaitez,\\npuis chaque photo de votre sphère.\\nVous pouvez utiliser les points bleus\\ncomme point de repère idéal.\\nMaintenez appuyé le déclencheur pour finir.</string>\n    <string name=\"iso_hint_auto\">ISO automatique</string>\n    <string name=\"iso_hint_hjr\">ISO réduction du tremblement</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-hu/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Automatikus</item>\n        <item>Letiltva</item>\n        <item>Engedélyezve</item>\n        <item>Vaku</item>\n        <item>Vörösszem csökkentés</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Letiltva</item>\n        <item>Fekete &amp; Fehér</item>\n        <item>Negatív</item>\n        <item>Szolarizált</item>\n        <item>Szépia</item>\n        <item>Poszterizált</item>\n        <item>Feketetábla</item>\n        <item>Fehértábla</item>\n        <item>Víz</item>\n        <item>Dombormű</item>\n        <item>Vázlat</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatikus</item>\n        <item>Bemozdulás csökkentés</item>\n        <item>Kiterjesztett valóság</item>\n        <item>Legjobb kép</item>\n        <item>Gyerek</item>\n        <item>Háttérvilágítás</item>\n        <item>Háttérvilágítású portré</item>\n        <item>Vonalkód</item>\n        <item>Strand</item>\n        <item>Gyertyafény</item>\n        <item>Sötét</item>\n        <item>Étel</item>\n        <item>Dokumentum</item>\n        <item>Tűzijáték</item>\n        <item>Virágok</item>\n        <item>Kézben tartott szürkület</item>\n        <item>HDR</item>\n        <item>Nagy érzékenység</item>\n        <item>Tájkép</item>\n        <item>Éjszakai</item>\n        <item>Éjszakai portré</item>\n        <item>Éjszakai beltéri portré</item>\n        <item>Buli</item>\n        <item>Háziállat</item>\n        <item>Portré</item>\n        <item>Hó</item>\n        <item>Lágy bőr</item>\n        <item>Sport</item>\n        <item>Reflektorfény</item>\n        <item>Állókép</item>\n        <item>Napnyugta</item>\n        <item>Varrott</item>\n        <item>Színház</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatikus</item>\n        <item>Felhős</item>\n        <item>Izzólámpa</item>\n        <item>Fénycső</item>\n        <item>Napfény</item>\n        <item>Felhős napfény</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Kiértékelő</item>\n        <item>Középre súlyozott</item>\n        <item>Spotmérés</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-hu/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Focal indítása</string>\n    <string name=\"shutter_button\">Exponáló gomb</string>\n    <string name=\"cannot_connect_hal\">Nem lehet csatlakozni a kamerához</string>\n    <string name=\"recording\">Felvétel</string>\n    <string name=\"double_tap_to_snapshot\">Kép készítéséhez duplán érintse meg</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">A készülék nem támogatja a GLES2-t</string>\n    <string name=\"no_gyroscope\">A készülék nem rendelkezik giroszkóppal</string>\n    <string name=\"ps_long_press_to_stop\">A gömbpanoráma befejezéséhez\\nhosszan érintse meg az exponáló gombot.</string>\n    <string name=\"disabled\">Letiltva</string>\n    <string name=\"enabled\">Engedélyezve</string>\n    <string name=\"no_video_player\">Nem lehet lejátszani a videót, nem található lejátszó</string>\n    <string name=\"fullscreen_shutter_info\">Bárhol érintse meg a kép készítéséhez</string>\n    <string name=\"please_wait\">Kérem, várjon\\u2026</string>\n    <string name=\"retouch\">RETUSÁLÁS</string>\n    <string name=\"open_in_gallery\">GALÉRIA</string>\n    <string name=\"mode_photo\">Fotó</string>\n    <string name=\"mode_video\">Videó</string>\n    <string name=\"mode_picsphere\">Gömbpanoráma</string>\n    <string name=\"mode_panorama\">Panoráma</string>\n    <string name=\"mode_switchcam\">Kamera</string>\n    <string name=\"widget_burstmode_count_shots\">%d kép</string>\n    <string name=\"widget_burstmode_off\">Sorozatfelvétel letiltva</string>\n    <string name=\"widget_burstmode_infinite\">Végtelen számú kép</string>\n    <string name=\"widget_hdr_aebracket\">Automatikus expozíció sorozat</string>\n    <string name=\"widget_settings_choose_widgets\">Válasszon modulokat az oldalsávon</string>\n    <string name=\"widget_settings_choose_widgets_button\">Válasszon modulokat</string>\n    <string name=\"widget_settings_picture_size\">Kép méret</string>\n    <string name=\"widget_settings_exposure_ring\">Expozíció gyűrű megjelenítése</string>\n    <string name=\"widget_settings_autoenhance\">Auto-feljavítás</string>\n    <string name=\"widget_settings_ruleofthirds\">Harmadolási szabály</string>\n    <string name=\"widget_autoexposure\">Exponálás mérés módja</string>\n    <string name=\"widget_burstmode\">Sorozatfelvétel</string>\n    <string name=\"widget_effect\">Színhatások</string>\n    <string name=\"widget_enhancements\">Színfokozás</string>\n    <string name=\"widget_exposure_compensation\">Expo korrekció</string>\n    <string name=\"widget_flash\">Zseblámpa</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">ISO érzékenység</string>\n    <string name=\"widget_scenemode\">Kép jellege</string>\n    <string name=\"widget_settings\">Beállítások</string>\n    <string name=\"widget_shutter_speed\">Zársebesség</string>\n    <string name=\"widget_skintone\">Bőrtónus kiemelése</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Időzítő</string>\n    <string name=\"widget_videofr\">Videó képkocka</string>\n    <string name=\"widget_videohdr\">Videó HDR</string>\n    <string name=\"widget_whitebalance\">Fehéregyensúly</string>\n    <string name=\"pano_panorama_rendering_failed\">Nem sikerült összeállítani a panorámaképet.\\nPróbáljon meg egy képkockával kisebb képet készíteni.</string>\n    <string name=\"pano_panorama_rendering\">Panorámakép összeállítása\\u2026</string>\n    <string name=\"picsphere_undo_button\">VISSZAVONÁS</string>\n    <string name=\"picsphere_start_hint\">A gömbpanoráma készítésének elindításához készítsen egy képet</string>\n    <string name=\"picsphere_already_rendering\">Várjon, amíg a gömbpanoráma elkészül</string>\n    <string name=\"picsphere_rendering_progress\">Összeállítás... (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Összeállítás elindítva\\u2026</string>\n    <string name=\"picsphere_notif_title\">Gömbpanoráma összeállítása\\u2026</string>\n    <string name=\"picsphere_failed\">Nem sikerült összeállítani a gömbpanorámát</string>\n    <string name=\"picsphere_failed_details\">Győződjön meg róla, hogy a képek átfedik egymást.</string>\n    <string name=\"picsphere_need_two_pics\">Legalább két képre van szükség.</string>\n    <string name=\"picsphere_step_preparing\">Előkészítés\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Metaadatok generálása\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Kamera orientáció rögzítése\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Vezérlőpontok keresése\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Képek elrendezése\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Azonos részek eltávolítása\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Üres területek kivágása\\u2026</string>\n    <string name=\"picsphere_step_nona\">Képek összeillesztésének rögzítése\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Képek elegyítése\\u2026</string>\n    <string name=\"software_hdr_notif_title\">HDR kép számítása\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR összeállítás sikertelen</string>\n    <string name=\"software_hdr_failed_details\">Az eredeti kép megtartva.</string>\n    <string name=\"showcase_welcome_1_title\">Üdvözöljük!</string>\n    <string name=\"showcase_welcome_1_body\">A beállításokat az oldalsávon találja,\\ncsak húzza elő.</string>\n    <string name=\"showcase_welcome_2_title\">Exponáló gomb</string>\n    <string name=\"showcase_welcome_2_body\">Érintse meg az exponáló gombot a kép elkészítéséhez.\\nTovábbi felvételi módokhoz csúsztatással fér hozzá.</string>\n    <string name=\"showcase_panorama_title\">Panoráma mód</string>\n    <string name=\"showcase_panorama_body\">Érintse meg az exponáló gombot a panoráma kép készítés elindításához,\\nazután pásztázzon arra, amit rögzíteni kíván.\\nMiután végzett, érintse meg ismét az exponáló gombot.</string>\n    <string name=\"showcase_picsphere_title\">Gömbpanoráma mód</string>\n    <string name=\"showcase_picsphere_body\">Igazítsa oda az első képet ahová szeretné,\\nmajd minden egyes képhez érintse meg az exponáló gombot.\\nA kék referenciapontok segítenek a lekövetésben.\\nNyomja meg hosszan az exponáló gombot a befejezéshez.</string>\n    <string name=\"iso_hint_auto\">Automatikus ISO</string>\n    <string name=\"iso_hint_hjr\">Kézremegést csökkentő ISO</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-it/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>sorridete</item>\n        <item>sorridi</item>\n        <item>scatta</item>\n        <item>foto</item>\n        <item>ciis</item>\n        <item>scatta una foto</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Disattivato</item>\n        <item>Attivato</item>\n        <item>Torcia</item>\n        <item>Riduzione occhi rossi</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Disattivato</item>\n        <item>Bianco &amp; nero</item>\n        <item>Negativo</item>\n        <item>Solarizza</item>\n        <item>Seppia</item>\n        <item>Posterizza</item>\n        <item>Lavagna</item>\n        <item>Lavagna bianca</item>\n        <item>Acqua</item>\n        <item>Rilievo</item>\n        <item>Disegno</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatico</item>\n        <item>Riduzione sfocatura movimento</item>\n        <item>Realtà aumentata</item>\n        <item>Scatto migliore</item>\n        <item>Baby</item>\n        <item>Luce da dietro</item>\n        <item>Ritratto luce da dietro</item>\n        <item>Codice a barre</item>\n        <item>Spiaggia</item>\n        <item>Luce di candela</item>\n        <item>Scuro</item>\n        <item>Piatto</item>\n        <item>Documento</item>\n        <item>Fuochi d\\'artificio</item>\n        <item>Fiori</item>\n        <item>Crepuscolo</item>\n        <item>HDR</item>\n        <item>Alta sensibilità</item>\n        <item>Paesaggio</item>\n        <item>Notte</item>\n        <item>Ritratto di notte</item>\n        <item>Ritratto di notte</item>\n        <item>Party</item>\n        <item>Cucciolo</item>\n        <item>Ritratto</item>\n        <item>Neve</item>\n        <item>Pelle morbida</item>\n        <item>Sport</item>\n        <item>Lampadina</item>\n        <item>Foto stabilizzata</item>\n        <item>Tramonto</item>\n        <item>Panorama multifoto</item>\n        <item>Teatro</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatico</item>\n        <item>Nuvoloso</item>\n        <item>Incandescente</item>\n        <item>Fluorescente</item>\n        <item>Diurno</item>\n        <item>Diurno nuvoloso</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Valutativa</item>\n        <item>Media pesata al centro</item>\n        <item>Spot</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2013 The CyanogenMod Project\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version 2\n    of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n    MA  02110-1301, USA.\n-->\n<resources>\n\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Avvia Focal</string>\n    <string name=\"shutter_button\">Pulsante scatto</string>\n    <string name=\"cannot_connect_hal\">Impossibile collegarsi alla fotocamera</string>\n    <string name=\"recording\">Registrazione</string>\n    <string name=\"double_tap_to_snapshot\">Scatta con doppio tocco</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Il dispositivo non supporta GLES2</string>\n    <string name=\"no_gyroscope\">Il dispositivo non ha il giroscopio</string>\n    <string name=\"ps_long_press_to_stop\">Tenere premuto il pulsante scatto per finire la sfera.</string>\n    <string name=\"disabled\">Disattivato</string>\n    <string name=\"enabled\">Attivato</string>\n    <string name=\"no_video_player\">Impossibile riprodurre il video, nessun lettore trovato</string>\n    <string name=\"fullscreen_shutter_info\">Toccare un punto qualsiasi per scattare</string>\n    <string name=\"please_wait\">Attendere&#8230;</string>\n    <string name=\"retouch\">RITOCCO</string>\n    <string name=\"open_in_gallery\">GALLERIA</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSfera</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Frontale</string>\n    <string name=\"widget_burstmode_count_shots\">%d scatti in sequenza</string>\n    <string name=\"widget_burstmode_off\">Disattiva scatti multipli</string>\n    <string name=\"widget_burstmode_infinite\">Scatti infiniti</string>\n    <string name=\"widget_hdr_aebracket\">Auto bracketing</string>\n    <string name=\"widget_settings_choose_widgets\">Widget nella barra degli strumenti</string>\n    <string name=\"widget_settings_choose_widgets_button\">Scegliere i widget</string>\n    <string name=\"widget_settings_picture_size\">Dimensione immagine</string>\n    <string name=\"widget_settings_exposure_ring\">Esposizione</string>\n    <string name=\"widget_settings_autoenhance\">Miglioramenti automatici</string>\n    <string name=\"widget_settings_ruleofthirds\">Regola dei terzi</string>\n    <string name=\"widget_autoexposure\">Autoesposizione</string>\n    <string name=\"widget_burstmode\">Scatti continui</string>\n    <string name=\"widget_effect\">Effetti colore</string>\n    <string name=\"widget_enhancements\">Esaltazione colori</string>\n    <string name=\"widget_exposure_compensation\">Compensazione esposizione</string>\n    <string name=\"widget_flash\">Flash</string>\n    <string name=\"widget_hdr\">HDR - High Dynamic Range</string>\n    <string name=\"widget_iso\">Sensibilità ISO</string>\n    <string name=\"widget_scenemode\">Scena</string>\n    <string name=\"widget_settings\">Impostazioni</string>\n    <string name=\"widget_shutter_speed\">Velocità otturatore</string>\n    <string name=\"widget_skintone\">Esaltazione toni pelle</string>\n    <string name=\"widget_softwarehdr\">HDR - High Dynamic Range</string>\n    <string name=\"widget_timermode\">Timer</string>\n    <string name=\"widget_videofr\">Video Framerate</string>\n    <string name=\"widget_videohdr\">Video High Dynamic Range</string>\n    <string name=\"widget_whitebalance\">Bilanciamento bianco</string>\n    <string name=\"pano_panorama_rendering_failed\">Impossibile elaborare il panorama.\\nProvare con uno più breve.</string>\n    <string name=\"pano_panorama_rendering\">Elaborazione panorama&#8230;</string>\n    <string name=\"picsphere_undo_button\">ANNULLA</string>\n    <string name=\"picsphere_start_hint\">Scattare una foto per cominciare</string>\n    <string name=\"picsphere_already_rendering\">Attendere l\\'elaborazione della PicSfera</string>\n    <string name=\"picsphere_rendering_progress\">Elaborazione... (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Elaborazione iniziata&#8230;</string>\n    <string name=\"picsphere_notif_title\">Elaborazione PicSsfera&#8230;</string>\n    <string name=\"picsphere_failed\">Errore nell\\'elaborazione della PicSfera</string>\n    <string name=\"picsphere_failed_details\">Assicurarsi che gli scatti si sovrappongano solo sui bordi.</string>\n    <string name=\"picsphere_need_two_pics\">Occorrono almeno due scatti.</string>\n    <string name=\"picsphere_step_preparing\">Preparazione&#8230;</string>\n    <string name=\"picsphere_step_ptogen\">Generazione dei metadati\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Registrazione orientamento fotocamera\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Ricerca dei punti di controllo\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Ottimizzazione&#8230;</string>\n    <string name=\"picsphere_step_ptclean\">Azzeramento punti di corrispondenza&#8230;</string>\n    <string name=\"picsphere_step_panomodify\">Eliminazione zone vuote&#8230;</string>\n    <string name=\"picsphere_step_nona\">Deformazione&#8230;</string>\n    <string name=\"picsphere_step_enblend\">Fusione degli scatti&#8230;</string>\n    <string name=\"software_hdr_notif_title\">Elaborazione HDR&#8230;</string>\n    <string name=\"software_hdr_failed\">Elaborazione HDR fallita</string>\n    <string name=\"software_hdr_failed_details\">Gli scatti originali sono stati mantenuti.</string>\n    <string name=\"showcase_welcome_1_title\">Benvenuto!</string>\n    <string name=\"showcase_welcome_1_body\">Le opzioni sono nella barra degli strumenti,\\nattivabile scorrendo da sinistra a destra.</string>\n    <string name=\"showcase_welcome_2_title\">Pulsante scatto</string>\n    <string name=\"showcase_welcome_2_body\">Toccare il pulsante scatto per fotografare.\\nScorrere dal pulsante per scegliere le modalità di scatto.</string>\n    <string name=\"showcase_panorama_title\">Panorama</string>\n    <string name=\"showcase_panorama_body\">Toccare il pulsante per iniziare,\\npoi fare una lenta panoramica.\\nUna volta fatto, premere nuovamente il pulsante.</string>\n    <string name=\"showcase_picsphere_title\">PicSfera</string>\n    <string name=\"showcase_picsphere_body\">Scattare a piacimento la prima immagine,\\npoi premere il pulsante per ogni\\nimmagine successiva della sfera,\\nutilizzando i puntini blu di riferimento.\\nPremere a lungo il pulsante una volta terminato.</string>\n    <string name=\"iso_hint_auto\">ISO automatico</string>\n    <string name=\"iso_hint_hjr\">ISO riduzione tremolio</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-nl/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, take a picture</item>\n    </string-array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Automatisch</item>\n        <item>Uitgeschakeld</item>\n        <item>Ingeschakeld</item>\n        <item>Zaklamp</item>\n        <item>Rode ogen verminderen</item>\n    </string-array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Uitgeschakeld</item>\n        <item>Zwart-wit</item>\n        <item>Negatief</item>\n        <item>Solariseren</item>\n        <item>Sepia</item>\n        <item>Posteriseren</item>\n        <item>Schoolbord</item>\n        <item>Whiteboard</item>\n        <item>Aqua</item>\n        <item>Reliëf</item>\n        <item>Schets</item>\n        <item>Neon</item>\n    </string-array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatisch</item>\n        <item>Bewegingsonscherpte verwijderen</item>\n        <item>Toegevoegde realiteit</item>\n        <item>Beste opname</item>\n        <item>Baby</item>\n        <item>Achtergrondlicht</item>\n        <item>Portret (achtergrondlicht)</item>\n        <item>Streepjescode</item>\n        <item>Strand</item>\n        <item>Kaarslicht</item>\n        <item>Donker</item>\n        <item>Gerecht</item>\n        <item>Document</item>\n        <item>Vuurwerk</item>\n        <item>Bloemen</item>\n        <item>Schemering</item>\n        <item>HDR</item>\n        <item>Hoge gevoeligheid</item>\n        <item>Landschap</item>\n        <item>Nacht</item>\n        <item>Nachtportret</item>\n        <item>Nachtportret</item>\n        <item>Feest</item>\n        <item>Huisdier</item>\n        <item>Portret</item>\n        <item>Sneeuw</item>\n        <item>Zachte huid</item>\n        <item>Sport</item>\n        <item>Schijnwerper</item>\n        <item>Stabiele foto</item>\n        <item>Zonsondergang</item>\n        <item>Draaipanorama</item>\n        <item>Theater</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatisch</item>\n        <item>Bewolkt</item>\n        <item>Gloeilamp</item>\n        <item>TL-lamp</item>\n        <item>Daglicht</item>\n        <item>Daglicht (bewolkt)</item>\n    </string-array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Meervelds</item>\n        <item>Centrumgericht</item>\n        <item>Spot</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-nl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Focal starten</string>\n    <string name=\"shutter_button\">Sluiterknop</string>\n    <string name=\"cannot_connect_hal\">Kan geen verbinding maken met de camera</string>\n    <string name=\"recording\">Opnemen</string>\n    <string name=\"double_tap_to_snapshot\">Dubbeltikken om een foto te maken</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Dit apparaat ondersteunt geen GLES2</string>\n    <string name=\"no_gyroscope\">Dit apparaat heeft geen gyroscoop</string>\n    <string name=\"ps_long_press_to_stop\">Sluiterknop lang indrukken/n om de PicSphere te voltooien</string>\n    <string name=\"disabled\">Uitgeschakeld</string>\n    <string name=\"enabled\">Ingeschakeld</string>\n    <string name=\"no_video_player\">Kan video niet afspelen, geen mediaspeler gevonden</string>\n    <string name=\"fullscreen_shutter_info\">Tik ergens om een foto te maken</string>\n    <string name=\"please_wait\">Een ogenblik geduld\\u2026</string>\n    <string name=\"retouch\">BEWERKEN</string>\n    <string name=\"open_in_gallery\">GALERIJ</string>\n\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Camera wisselen</string>\n\n    <string name=\"widget_burstmode_count_shots\">%d opnamen achter elkaar</string>\n    <string name=\"widget_burstmode_off\">Serieopname uitschakelen</string>\n    <string name=\"widget_burstmode_infinite\">Oneindig</string>\n\n    <string name=\"widget_hdr_aebracket\">Brackets automatisch</string>\n\n    <string name=\"widget_settings_choose_widgets\">Zijbalkwidgets kiezen</string>\n    <string name=\"widget_settings_choose_widgets_button\">Widgets kiezen</string>\n    <string name=\"widget_settings_picture_size\">Grootte van foto</string>\n    <string name=\"widget_settings_exposure_ring\">Belichtingsring tonen</string>\n    <string name=\"widget_settings_autoenhance\">Automatisch verbeteren</string>\n    <string name=\"widget_settings_ruleofthirds\">Raster</string>\n\n    <string name=\"widget_autoexposure\">Belichting meten</string>\n    <string name=\"widget_burstmode\">Serieopname</string>\n    <string name=\"widget_effect\">Kleureffecten</string>\n    <string name=\"widget_enhancements\">Kleurverbeteringen</string>\n    <string name=\"widget_exposure_compensation\">Belichtingscompensatie</string>\n    <string name=\"widget_flash\">Flits</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">ISO</string>\n    <string name=\"widget_scenemode\">Scènekeuze</string>\n    <string name=\"widget_settings\">Instellingen</string>\n    <string name=\"widget_shutter_speed\">Sluitertijd</string>\n    <string name=\"widget_skintone\">Huidtint verbeteren</string>\n    <string name=\"widget_softwarehdr\">HDR (SW)</string>\n    <string name=\"widget_timermode\">Timer</string>\n    <string name=\"widget_videofr\">Videoframerate</string>\n    <string name=\"widget_videohdr\">HDR-video</string>\n    <string name=\"widget_whitebalance\">Witbalans</string>\n\n    <string name=\"pano_panorama_rendering_failed\">Panoramaopname mislukt./n Probeer een kortere.</string>\n    <string name=\"pano_panorama_rendering\">Panorama renderen\\u2026</string>\n\n    <string name=\"picsphere_undo_button\">ONGEDAAN MAKEN</string>\n    <string name=\"picsphere_start_hint\">Neem een foto om de PicSphere te starten</string>\n    <string name=\"picsphere_already_rendering\">Een ogenlbik geduld a.u.b. De huidige PicSphere wordt voltooid</string>\n    <string name=\"picsphere_rendering_progress\">Renderen\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Renderen gestart\\u2026</string>\n    <string name=\"picsphere_notif_title\">PicSphere renderen\\u2026</string>\n    <string name=\"picsphere_failed\">PicSphere renderen mislukt</string>\n    <string name=\"picsphere_failed_details\">Zorg ervoor dat de afbeeldingen alleen met de randen overlappen</string>\n    <string name=\"picsphere_need_two_pics\">Er zijn minimaal twee foto\\'s nodig.</string>\n    <string name=\"picsphere_step_preparing\">Voorbereiden\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Metadata genereren\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Camerastand opslaan\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Controlepunten zoeken\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Afbeeldingen uitlijnen\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Punten opruimen\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Lege stukken wegknippen\\u2026</string>\n    <string name=\"picsphere_step_nona\">Afbeeldingen aan elkaar plakken\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Afbeelding in elkaar laten overvloeien\\u2026</string>\n\n    <string name=\"software_hdr_notif_title\">HDR-afbeelding berekenen\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR renderen mislukt</string>\n    <string name=\"software_hdr_failed_details\">De bronafbeeldingen zijn bewaard.</string>\n\n    <string name=\"showcase_welcome_1_title\">Welkom!</string>\n    <string name=\"showcase_welcome_1_body\">De opties staan in de zijbalk,\\nsleep eroverheen om te kiezen.</string>\n    <string name=\"showcase_welcome_2_title\">Sluiterknop</string>\n    <string name=\"showcase_welcome_2_body\">Tik op de sluiterknop om een foto te maken.\\nSleep erover voor andere opnamestanden.</string>\n    <string name=\"showcase_panorama_title\">Panoramaopname</string>\n    <string name=\"showcase_panorama_body\">Tik op de sluiterknop om de panoramaopname te starten\\nen richt op wat u wilt opnemen.\\nTik nogmaals op de sluiterknop wanneer u klaar bent.</string>\n    <string name=\"showcase_picsphere_title\">PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Lijn de eerste afbeelding uit,\\ntik op de sluiterknop om alle foto\\'s te maken\\nvan de sphere. De blauwe stippen zijn referentiepunten die u kunt volgen.\\nHoud de sluiterknop ingedrukt als u klaar bent.</string>\n\n    <string name=\"iso_hint_auto\">Automatisch</string>\n    <string name=\"iso_hint_hjr\">Beeldstabilisatie (HJR)</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-pl/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>Cid</item>\n        <item>Civ</item>\n        <item>Whiskey</item>\n        <item>Cheese</item>\n    </string-array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Automatyczny</item>\n        <item>Wyłączony</item>\n        <item>Włączony</item>\n        <item>Latarka</item>\n        <item>Redukcja czerwonych oczu</item>\n    </string-array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Wyłączony</item>\n        <item>Czarny &amp; Biały</item>\n        <item>Negatyw</item>\n        <item>Solaryzacja</item>\n        <item>Sepia</item>\n        <item>Posteryzacja</item>\n        <item>Czarna tablica</item>\n        <item>Biała tablica</item>\n        <item>Woda</item>\n        <item>Wyrycie</item>\n        <item>Szkic</item>\n        <item>Neon</item>\n    </string-array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatyczny</item>\n        <item>Redukcja rozmycia w ruchu</item>\n        <item>Rozszerzona rzeczywistość</item>\n        <item>Najlepsze zdjęcie</item>\n        <item>Dziecko</item>\n        <item>Pod światło</item>\n        <item>Porter pod światło</item>\n        <item>Kod kreskowy</item>\n        <item>Plaża</item>\n        <item>Światło świecy</item>\n        <item>Mrok</item>\n        <item>Jedzenie</item>\n        <item>Dokument</item>\n        <item>Fajerwerki</item>\n        <item>Kwiaty</item>\n        <item>Zmierzch</item>\n        <item>HDR</item>\n        <item>Wysoka czułość</item>\n        <item>Krajobraz</item>\n        <item>Noc</item>\n        <item>Nocny portret</item>\n        <item>Nocny portret</item>\n        <item>Impreza</item>\n        <item>Zwierzę</item>\n        <item>Portret</item>\n        <item>Śnieg</item>\n        <item>Miękka skóra</item>\n        <item>Sport</item>\n        <item>Reflektor</item>\n        <item>Stacjonarne zdjęcie</item>\n        <item>Zachód słońca</item>\n        <item>Zszyta panorama</item>\n        <item>Teatr</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automatyczny</item>\n        <item>Pochmurno</item>\n        <item>Rozżarzony</item>\n        <item>Fluorescencyjny</item>\n        <item>Światło dzienne</item>\n        <item>Zachmurzony dzień</item>\n    </string-array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Średnia pola</item>\n        <item>Centralnie ważony</item>\n        <item>Punktowy</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Uruchom Focal</string>\n    <string name=\"shutter_button\">Spust migawki</string>\n    <string name=\"cannot_connect_hal\">Nie można połączyć z aparatem</string>\n    <string name=\"recording\">Nagrywanie</string>\n    <string name=\"double_tap_to_snapshot\">Naciśnij dwukrotnie by zrobić zdjęcie</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Twoje urządzenie nie obsługuje GLES2</string>\n    <string name=\"no_gyroscope\">Twoje urządzenie nie posiada żyroskopu</string>\n    <string name=\"ps_long_press_to_stop\">Naciśnij długo spust migawki aby zakończyć PicSphere</string>\n    <string name=\"disabled\">Wyłączony</string>\n    <string name=\"enabled\">Włączony</string>\n    <string name=\"no_video_player\">Nie można odtworzyć wideo, nie znaleziono odtwarzacza.</string>\n    <string name=\"fullscreen_shutter_info\">Dotknij ekranu aby zrobić zdjęcie</string>\n    <string name=\"please_wait\">Proszę czekać\\u2026</string>\n    <string name=\"retouch\">RETUSZ</string>\n    <string name=\"open_in_gallery\">GALERIA</string>\n    <string name=\"mode_photo\">Zdjęcie</string>\n    <string name=\"mode_video\">Wideo</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Zmiana aparatu</string>\n    <string name=\"widget_burstmode_count_shots\">Seria %d zdjęć</string>\n    <string name=\"widget_burstmode_off\">Wyłącz serię zdjęć</string>\n    <string name=\"widget_burstmode_infinite\">Nieskończona seria</string>\n    <string name=\"widget_hdr_aebracket\">Seria o różnej ekspozycji</string>\n    <string name=\"widget_settings_choose_widgets\">Wybierz widżety paska bocznego</string>\n    <string name=\"widget_settings_choose_widgets_button\">Wybierz widżety</string>\n    <string name=\"widget_settings_picture_size\">Rozmiar zdjęcia</string>\n    <string name=\"widget_settings_exposure_ring\">Pokaż pierścień ekspozycji</string>\n    <string name=\"widget_settings_autoenhance\">Auto-wzmocnienie</string>\n    <string name=\"widget_settings_ruleofthirds\">Siatka</string>\n    <string name=\"widget_autoexposure\">Tryb pomiaru ekspozycji</string>\n    <string name=\"widget_burstmode\">Zdjęcia seryjne</string>\n    <string name=\"widget_effect\">Efekt kolorystyczny</string>\n    <string name=\"widget_enhancements\">Wzmocnienie kolorów</string>\n    <string name=\"widget_exposure_compensation\">Kompensacja ekspozycji</string>\n    <string name=\"widget_flash\">Tryb lampy błyskowej</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">Czułość ISO</string>\n    <string name=\"widget_scenemode\">Tryb sceny</string>\n    <string name=\"widget_settings\">Ustawienia</string>\n    <string name=\"widget_shutter_speed\">Szybkość migawki</string>\n    <string name=\"widget_skintone\">Poprawa koloru skóry</string>\n    <string name=\"widget_softwarehdr\">HDR (SW)</string>\n    <string name=\"widget_timermode\">Samowyzwalacz</string>\n    <string name=\"widget_videofr\">Wideo-klatek/sek</string>\n    <string name=\"widget_videohdr\">HDR-Wideo</string>\n    <string name=\"widget_whitebalance\">Balans bieli</string>\n    <string name=\"pano_panorama_rendering_failed\">Obraz panoramy nie może zostać utworzony./nSpróbuj zrobić krótszą panoramę.</string>\n    <string name=\"pano_panorama_rendering\">Renderowanie panoramy\\u2026</string>\n    <string name=\"picsphere_undo_button\">Wytnij</string>\n    <string name=\"picsphere_start_hint\">Zrób zdjęcię by zacząć</string>\n    <string name=\"picsphere_already_rendering\">Proszę czekać na zakończenie renderowania PicSphere</string>\n    <string name=\"picsphere_rendering_progress\">PicSphere renderuje\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Renderowanie rozpoczęte\\u2026</string>\n    <string name=\"picsphere_notif_title\">Renderowanie PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Błąd renderowania PicSphere</string>\n    <string name=\"picsphere_failed_details\">Upewnij się, że zdjęcia zachodzą tylko na swoje krawędzie.</string>\n    <string name=\"picsphere_need_two_pics\">PicSphere potrzebuje przynajmniej dwóch zdjęć.</string>\n    <string name=\"picsphere_step_preparing\">Przygotowywanie\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Generowanie metadanych\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Zapisywanie orientacji aparatu\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Znajdowanie punktu kontrolnego\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Dopasowywanie obrazów\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Usuwanie punktów\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Kadrowanie pustych obszarów\\u2026</string>\n    <string name=\"picsphere_step_nona\">Zszywanie zdjęć\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Mieszanie zdjęć\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Obraz HDR jest przetwarzany\\u2026</string>\n    <string name=\"software_hdr_failed\">Błąd renderowania HDR</string>\n    <string name=\"software_hdr_failed_details\">Zdjęcia źródłowe zostały zachowane.</string>\n    <string name=\"showcase_welcome_1_title\">Witam!</string>\n    <string name=\"showcase_welcome_1_body\">Opcje znajdują się na pasku bocznym,\\npo prostu go przesuń.</string>\n    <string name=\"showcase_welcome_2_title\">Spust migawki</string>\n    <string name=\"showcase_welcome_2_body\">Dotknij przycisku migawki żeby zrobić zdjęcie.\\nPrzesuń go żeby uzyskać dostęp do innych trybów.</string>\n    <string name=\"showcase_panorama_title\">Tryb Panoramy</string>\n    <string name=\"showcase_panorama_body\">Dotknij spustu migawki żeby zacząć panoramę,\\nnastępnie przesuń i uchwyć obraz.\\nKiedy skończysz, dotknij spustu migawki jeszcze raz.</string>\n    <string name=\"showcase_picsphere_title\">Tryb PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Zrób pierwsze zdjęcie gdzie tylko chcesz,\\nnastępnie dotknij przycisku migawki żeby zrobić każde zdjęcie sfery.\\nNiebieskie kropki są punktami za którymi powinieś podążać.\\nNaciśnij długo przycisk migawki kiedy skończysz.</string>\n    <string name=\"iso_hint_auto\">Automatyczny</string>\n    <string name=\"iso_hint_hjr\">Stabilizacja obrazu</string>\n    <string name=\"iso_hint_100\">ISO 100</string>\n    <string name=\"iso_hint_200\">ISO 200</string>\n    <string name=\"iso_hint_400\">ISO 400</string>\n    <string name=\"iso_hint_800\">ISO 800</string>\n    <string name=\"iso_hint_1600\">ISO 1600</string>\n</resources>\n"
  },
  {
    "path": "res/values-pt-rBR/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2013 The CyanogenMod Project\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version 2\n    of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, tire uma foto</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Desativado</item>\n        <item>Ativado</item>\n        <item>Tocha</item>\n        <item>Redução de olhos vermelhos</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Desativado</item>\n        <item>Preto &amp; Branco</item>\n        <item>Negativo</item>\n        <item>Solarização</item>\n        <item>Sépia</item>\n        <item>Posterização</item>\n        <item>Quadro negro</item>\n        <item>Quadro branco</item>\n        <item>Aqua</item>\n        <item>Realçar</item>\n        <item>Rascunho</item>\n        <item>Néon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automático</item>\n        <item>Redução de Desfocagem</item>\n        <item>Realidade aumentada</item>\n        <item>Melhor foto</item>\n        <item>Bebê</item>\n        <item>Luz de fundo</item>\n        <item>Luz de fundo retrato</item>\n        <item>Código de barras</item>\n        <item>Praia</item>\n        <item>Luz de velas</item>\n        <item>Escuro</item>\n        <item>Prato</item>\n        <item>Documento</item>\n        <item>Fogos de artifício</item>\n        <item>Flores</item>\n        <item>Crepúsculo</item>\n        <item>HDR</item>\n        <item>Alta sensitividade</item>\n        <item>Paisagem</item>\n        <item>Noite</item>\n        <item>Noite retrato</item>\n        <item>Noite retrato</item>\n        <item>Festa</item>\n        <item>Bicho de estimação</item>\n        <item>Retrato</item>\n        <item>Neve</item>\n        <item>Pele</item>\n        <item>Esportes</item>\n        <item>Holofote</item>\n        <item>Moto estável</item>\n        <item>Pôr do sol</item>\n        <item>Varre ponto</item>\n        <item>Teatro</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automático</item>\n        <item>Nublado</item>\n        <item>Incandescente</item>\n        <item>Fluorescente</item>\n        <item>Luz do dia</item>\n        <item>Luz do dia nublado</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Quadro médio</item>\n        <item>Ponderado ao centro</item>\n        <item>Medição pontual</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2013 Guillaume Lesniak\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version 2\n    of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n-->\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Abrir Focal</string>\n    <string name=\"shutter_button\">Botão de disparo</string>\n    <string name=\"cannot_connect_hal\">Não foi possível conectar à câmera</string>\n    <string name=\"recording\">Gravando</string>\n    <string name=\"double_tap_to_snapshot\">Toque duplo para tirar um foto</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Seu dispositivo não suporta GLES2</string>\n    <string name=\"no_gyroscope\">Seu dispositivo não possui um giroscópio</string>\n    <string name=\"ps_long_press_to_stop\">Pressione o botão de disparar para\\nterminar sua esfera.</string>\n    <string name=\"disabled\">Desativado</string>\n    <string name=\"enabled\">Ativado</string>\n    <string name=\"no_video_player\">Não foi possível reproduzir o vídeo, nenhum tocador foi encontrado</string>\n    <string name=\"fullscreen_shutter_info\">Toque em qualquer lugar para tirar foto</string>\n    <string name=\"please_wait\">Por favor, aguarde\\u2026</string>\n    <string name=\"retouch\">RETOQUE</string>\n    <string name=\"open_in_gallery\">GALERIA</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Vídeo</string>\n    <string name=\"mode_picsphere\">FotoEsfera</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Direção</string>\n    <string name=\"widget_burstmode_count_shots\">%d fotos em modo contínuo</string>\n    <string name=\"widget_burstmode_off\">Desativar disparo em modo contínuo</string>\n    <string name=\"widget_burstmode_infinite\">Contínuo infinito</string>\n    <string name=\"widget_hdr_aebracket\">Enquadramento automático</string>\n    <string name=\"widget_settings_choose_widgets\">Escolher widget na barra lateral</string>\n    <string name=\"widget_settings_choose_widgets_button\">Escolher widgets</string>\n    <string name=\"widget_settings_picture_size\">Tamanho da imagem</string>\n    <string name=\"widget_settings_exposure_ring\">Mostrar exposição de anel</string>\n    <string name=\"widget_settings_autoenhance\">Reaçar automático</string>\n    <string name=\"widget_settings_ruleofthirds\">Regra dos Terços</string>\n    <string name=\"widget_autoexposure\">Modo de medida de exposição</string>\n    <string name=\"widget_burstmode\">Modo contínuo</string>\n    <string name=\"widget_effect\">Efeitos de cores</string>\n    <string name=\"widget_enhancements\">Realce de cores</string>\n    <string name=\"widget_exposure_compensation\">Compensação de exposição</string>\n    <string name=\"widget_flash\">Modo de flash</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">Sensitividade ISO</string>\n    <string name=\"widget_scenemode\">Modo de cena</string>\n    <string name=\"widget_settings\">Configurações</string>\n    <string name=\"widget_shutter_speed\">Velocidade do disparador</string>\n    <string name=\"widget_skintone\">Realce de tone de pele</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Modo temporizador</string>\n    <string name=\"widget_videofr\">Taxa de frames do vídeo</string>\n    <string name=\"widget_videohdr\">HDR de vídeo</string>\n    <string name=\"widget_whitebalance\">Balanço do branco</string>\n    <string name=\"pano_panorama_rendering_failed\">Falha ou renderizar o panorama.\\nTente tirar um mais curto.</string>\n    <string name=\"pano_panorama_rendering\">Renderizando o panorama\\u2026</string>\n    <string name=\"picsphere_undo_button\">DESFAZER</string>\n    <string name=\"picsphere_start_hint\">Tire uma foto para iniciar a esfera</string>\n    <string name=\"picsphere_already_rendering\">Por favor aguarde a FotoEsfera atual renderizar</string>\n    <string name=\"picsphere_rendering_progress\">Renderizando\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Renderização iniciada\\u2026</string>\n    <string name=\"picsphere_notif_title\">Renderizando FotoEsfera\\u2026</string>\n    <string name=\"picsphere_failed\">Falha ao renderizar FotoEsfera</string>\n    <string name=\"picsphere_failed_details\">Certifique que as fotos somente sobrepõe nas bordas.</string>\n    <string name=\"picsphere_need_two_pics\">Você precisa de pelo menos duas fotos.</string>\n    <string name=\"picsphere_step_preparing\">Preparando\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Gerando metadados\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Gravando orientação da câmera\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Buscando pontos de controle\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Alinhando imagens\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Limpando pontos\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Cortando áreas vazias\\u2026</string>\n    <string name=\"picsphere_step_nona\">Juntando as fotos…</string>\n    <string name=\"picsphere_step_enblend\">Misturando as fotos\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Computando imagem HDR\\u2026</string>\n    <string name=\"software_hdr_failed\">Falha ao renderizar HDR</string>\n    <string name=\"software_hdr_failed_details\">As fotos de origem foram mantidas.</string>\n    <string name=\"showcase_welcome_1_title\">Bem vindo!</string>\n    <string name=\"showcase_welcome_1_body\">As opções estão na barra lateral.\\nsó deslize-a.</string>\n    <string name=\"showcase_welcome_2_title\">Botão de disparo</string>\n    <string name=\"showcase_welcome_2_body\">Toque no botão de disparo para tirar uma foto.\\nDeslize para acessar outros modos de captura.</string>\n    <string name=\"showcase_panorama_title\">Modo panorama</string>\n    <string name=\"showcase_panorama_body\">Toque o botão de disparo para iniciar seu panorama.\\nentão desloque para onde você deseja capturar.\\nAo terminar, toque no disparador de novo.</string>\n    <string name=\"showcase_picsphere_title\">Modo FotoEspera</string>\n    <string name=\"showcase_picsphere_body\">Alinhe sua primeira foto onde você desejar,\\n então toque no disparador para tirar cada foto\\nda esfera. Os pontos azuis são pontos\\nde referência que você pode seguir.\\nPressione o disparador quando você terminar.</string>\n    <string name=\"iso_hint_auto\">ISO Automático</string>\n    <string name=\"iso_hint_hjr\">ISO Redução de Vibração</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-pt-rPT/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>cheese</item>\n        <item>ok cid, tira uma foto</item>\n    </string-array>\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Inativo</item>\n        <item>Ativo</item>\n        <item>Tocha</item>\n        <item>Redução de olhos vermelhos</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Inativo</item>\n        <item>Preto e Branco</item>\n        <item>Negativo</item>\n        <item>Solarização</item>\n        <item>Sépia</item>\n        <item>Posterização</item>\n        <item>Quadro negro</item>\n        <item>Quadro branco</item>\n        <item>Aqua</item>\n        <item>Realçar</item>\n        <item>Esboço</item>\n        <item>Neon</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automático</item>\n        <item>Redução de desfoque</item>\n        <item>Realidade Aumentada</item>\n        <item>Melhor foto</item>\n        <item>Bebé</item>\n        <item>Luz de fundo</item>\n        <item>Luz de fundo retrato</item>\n        <item>Código de barras</item>\n        <item>Praia</item>\n        <item>Luz de velas</item>\n        <item>Escuro</item>\n        <item>Prato</item>\n        <item>Documento</item>\n        <item>Fogo de artifício</item>\n        <item>Flores</item>\n        <item>Crepúsculo</item>\n        <item>HDR</item>\n        <item>Alta sensibilidade</item>\n        <item>Paisagem</item>\n        <item>Noite</item>\n        <item>Noite retrato</item>\n        <item>Noite retrato</item>\n        <item>Festa</item>\n        <item>Animal de estimação</item>\n        <item>Retrato</item>\n        <item>Neve</item>\n        <item>Pele</item>\n        <item>Desporto</item>\n        <item>Holofote</item>\n        <item>Foto estável</item>\n        <item>Pôr do sol</item>\n        <item>Costura</item>\n        <item>Teatro</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automático</item>\n        <item>Nublado</item>\n        <item>Incandescente</item>\n        <item>Fluorescente</item>\n        <item>Luz do sol</item>\n        <item>Luz do sol nublada</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Média de frame</item>\n        <item>Ponderado ao centro</item>\n        <item>Medição pontual</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-pt-rPT/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Abrir Focal</string>\n    <string name=\"shutter_button\">Botão obturador</string>\n    <string name=\"cannot_connect_hal\">Não é possível ligar à câmara</string>\n    <string name=\"recording\">A gravar</string>\n    <string name=\"double_tap_to_snapshot\">Toque duas vezes para tirar uma fotografia</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">O seu dispositivo não suporta GLES2</string>\n    <string name=\"no_gyroscope\">O seu dispositivo não tem um giroscópio</string>\n    <string name=\"ps_long_press_to_stop\">Pressione continuamente o botão\\nobturador para terminar a sua sphere.</string>\n    <string name=\"disabled\">Inactivo</string>\n    <string name=\"enabled\">Activo</string>\n    <string name=\"no_video_player\">Não foi possível reproduzir o vídeo, nenhum reprodutor encontrado</string>\n    <string name=\"fullscreen_shutter_info\">Toque em qualquer sítio para capturar</string>\n    <string name=\"please_wait\">Por favor aguarde\\u2026</string>\n    <string name=\"retouch\">RETOCAR</string>\n    <string name=\"open_in_gallery\">GALERIA</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Vídeo</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Direção</string>\n    <string name=\"widget_burstmode_count_shots\">%d fotos em modo contínuo</string>\n    <string name=\"widget_burstmode_off\">Destavar disparo em modo contínuo</string>\n    <string name=\"widget_burstmode_infinite\">Contínuo infinito</string>\n    <string name=\"widget_hdr_aebracket\">Enquadramento automático</string>\n    <string name=\"widget_settings_choose_widgets\">Escolha os widgets na barra lateral</string>\n    <string name=\"widget_settings_choose_widgets_button\">Escolher widgets</string>\n    <string name=\"widget_settings_picture_size\">Tamanho da imagem</string>\n    <string name=\"widget_settings_exposure_ring\">Mostrar anel de exposição</string>\n    <string name=\"widget_settings_autoenhance\">Melhoria automática</string>\n    <string name=\"widget_settings_ruleofthirds\">Regra dos Terços</string>\n    <string name=\"widget_autoexposure\">Modo de medida da exposição</string>\n    <string name=\"widget_burstmode\">Modo de disparo contínuo</string>\n    <string name=\"widget_effect\">Efeitos de cor</string>\n    <string name=\"widget_enhancements\">Realce de cor</string>\n    <string name=\"widget_exposure_compensation\">Compensação de exposição</string>\n    <string name=\"widget_flash\">Modo de flash</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">Sensibilidade ISO</string>\n    <string name=\"widget_scenemode\">Modo de cena</string>\n    <string name=\"widget_settings\">Definições</string>\n    <string name=\"widget_shutter_speed\">Velocidade de obturação</string>\n    <string name=\"widget_skintone\">Realce de tom de pele</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Modo de temporizador</string>\n    <string name=\"widget_videofr\">Taxa de frames de vídeo</string>\n    <string name=\"widget_videohdr\">HDR de vídeo</string>\n    <string name=\"widget_whitebalance\">Equilíbrio de brancos</string>\n    <string name=\"pano_panorama_rendering_failed\">Falha ao compor o panorama.\\nTente tirar um mais curto.</string>\n    <string name=\"pano_panorama_rendering\">A compor panorama\\u2026</string>\n    <string name=\"picsphere_undo_button\">DESFAZER</string>\n    <string name=\"picsphere_start_hint\">Tire uma foto para começar uma sphere</string>\n    <string name=\"picsphere_already_rendering\">Por favor aguarde que a PicSphere atual se componha</string>\n    <string name=\"picsphere_rendering_progress\">A compor\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Composição iniciada\\u2026</string>\n    <string name=\"picsphere_notif_title\">A compor a PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Composição da PicSphere falhada</string>\n    <string name=\"picsphere_failed_details\">Assegure-se que as imagens só se sobrepõem nas suas margens.</string>\n    <string name=\"picsphere_need_two_pics\">Precisa de pelo menos duas imagens.</string>\n    <string name=\"picsphere_step_preparing\">A preparar\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">A gerar metadados\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">A gravar a orientação da câmara\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">À procura dos pontos de controlo\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">A alinhar imagens\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">A limpar pontos\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">A cortar áreas vazias\\u2026</string>\n    <string name=\"picsphere_step_nona\">A juntar as fotos\\u2026</string>\n    <string name=\"picsphere_step_enblend\">A misturar as fotos\\u2026</string>\n    <string name=\"software_hdr_notif_title\">A computar imagem HDR\\u2026</string>\n    <string name=\"software_hdr_failed\">Falha ao compor HDR</string>\n    <string name=\"software_hdr_failed_details\">As imagens de origem foram mantidas.</string>\n    <string name=\"showcase_welcome_1_title\">Bem-vindo!</string>\n    <string name=\"showcase_welcome_1_body\">As opções estão na barra lateral,\\né só deslizá-la.</string>\n    <string name=\"showcase_welcome_2_title\">Botão obturador</string>\n    <string name=\"showcase_welcome_2_body\">Toque no botão obturador para tirar uma fotografia.\\nDeslize-o para aceder a outros modos de captura.</string>\n    <string name=\"showcase_panorama_title\">Modo panorama</string>\n    <string name=\"showcase_panorama_body\">Toque no obturador para começar o panorama,\\ndepois desloque para onde quer capturar.\\nPara terminar, toque no obturador de novo.</string>\n    <string name=\"showcase_picsphere_title\">Modo PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Alinhe a sua primeira imagem onde quer,\\ndepois toque no obturador para tirar cada foto\\nda esfera. Os pontos azuis são pontos\\nde referência que pode seguir.\\nPressione continuamente o obturador para terminar.</string>\n    <string name=\"iso_hint_auto\">ISO Automático</string>\n    <string name=\"iso_hint_hjr\">ISO Redução de Vibração</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-ru/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"widget_flash_hints\">\n        <item>Авто</item>\n        <item>Выкл.</item>\n        <item>Вкл.</item>\n        <item>Фонарик</item>\n        <item>Удаление эфф. красных глаз</item>\n    </string-array>\n    <string-array name=\"widget_effects_hints\">\n        <item>Выкл.</item>\n        <item>Ч/Б</item>\n        <item>Негатив</item>\n        <item>Соляризация</item>\n        <item>Сепия</item>\n        <item>Постеризация</item>\n        <item>Тёмная доска</item>\n        <item>Доска</item>\n        <item>Под водой</item>\n        <item>Рельеф</item>\n        <item>Рисунок</item>\n        <item>Неон</item>\n    </string-array>\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Автоматически</item>\n        <item>Устранение размытого движения</item>\n        <item>Дополненная реальность</item>\n        <item>Лучший снимок</item>\n        <item>Ребёнок</item>\n        <item>Контровой свет</item>\n        <item>Портрет с контровым светом</item>\n        <item>Штрих-код</item>\n        <item>Пляж</item>\n        <item>Искусственный свет</item>\n        <item>Темнота</item>\n        <item>Еда</item>\n        <item>Документ</item>\n        <item>Фейерверк</item>\n        <item>Цветы</item>\n        <item>Сумерки</item>\n        <item>HDR</item>\n        <item>Высокая чувствительность</item>\n        <item>Пейзаж</item>\n        <item>Ночь</item>\n        <item>Портрет ночью</item>\n        <item>Портрет ночью</item>\n        <item>Вечеринка</item>\n        <item>Домашнее животное</item>\n        <item>Портрет</item>\n        <item>Снег</item>\n        <item>Мягкая кожа</item>\n        <item>Спорт</item>\n        <item>Освещение</item>\n        <item>Стабильный снимок</item>\n        <item>Закат</item>\n        <item>Sweep stitch</item>\n        <item>Театр</item>\n    </string-array>\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Авто</item>\n        <item>Пасмурный день</item>\n        <item>Лампа накаливания</item>\n        <item>Лампа дн. света</item>\n        <item>Солнечный свет</item>\n        <item>Облачно</item>\n    </string-array>\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Усреднённый</item>\n        <item>По центру кадра</item>\n        <item>Точечный замер</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Запуск Focal</string>\n    <string name=\"shutter_button\">Кнопка затвора</string>\n    <string name=\"cannot_connect_hal\">Не удаётся подключиться к камере</string>\n    <string name=\"recording\">Запись</string>\n    <string name=\"double_tap_to_snapshot\">Нажмите дважды, чтобы сделать снимок</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Ваше устройство не поддерживает GLES2</string>\n    <string name=\"no_gyroscope\">Ваше устройство не оснащено гироскопом</string>\n    <string name=\"ps_long_press_to_stop\">Нажмите и удерживайте кнопку затвора для окончания фотосферы</string>\n    <string name=\"disabled\">Выключено</string>\n    <string name=\"enabled\">Включено</string>\n    <string name=\"no_video_player\">Не удалось воспроизвести видео. Видеоплеер не найден.</string>\n    <string name=\"fullscreen_shutter_info\">Нажмите в любом месте, чтобы сделать снимок</string>\n    <string name=\"please_wait\">Пожалуйста, подождите\\u2026</string>\n    <string name=\"retouch\">РЕТУШЬ</string>\n    <string name=\"open_in_gallery\">ГАЛЕРЕЯ</string>\n    <string name=\"mode_photo\">Фото</string>\n    <string name=\"mode_video\">Видео</string>\n    <string name=\"mode_picsphere\">Фотосфера</string>\n    <string name=\"mode_panorama\">Панорама</string>\n    <string name=\"mode_switchcam\">Фронт. камера</string>\n    <string name=\"widget_burstmode_count_shots\">Серия из %d снимков</string>\n    <string name=\"widget_burstmode_off\">Выключить серийную съёмку</string>\n    <string name=\"widget_burstmode_infinite\">Непрерывная съёмка</string>\n    <string name=\"widget_hdr_aebracket\">Автобрекетинг</string>\n    <string name=\"widget_settings_choose_widgets\">Выберите виджеты в боковой панели</string>\n    <string name=\"widget_settings_choose_widgets_button\">Выберите виджеты</string>\n    <string name=\"widget_settings_picture_size\">Размер изображения</string>\n    <string name=\"widget_settings_exposure_ring\">Показывать кольцо экспозиции</string>\n    <string name=\"widget_settings_autoenhance\">Автоулучшение</string>\n    <string name=\"widget_settings_ruleofthirds\">Сетка</string>\n    <string name=\"widget_autoexposure\">Экспозамер</string>\n    <string name=\"widget_burstmode\">Серийная съёмка</string>\n    <string name=\"widget_effect\">Цветовые эффекты</string>\n    <string name=\"widget_enhancements\">Улучшение цвета</string>\n    <string name=\"widget_exposure_compensation\">Компенсация экспозиции</string>\n    <string name=\"widget_flash\">Режим вспышки</string>\n    <string name=\"widget_hdr\">HDR</string>\n    <string name=\"widget_iso\">Чувствительность ISO</string>\n    <string name=\"widget_scenemode\">Режим съёмки</string>\n    <string name=\"widget_settings\">Настройки</string>\n    <string name=\"widget_shutter_speed\">Скорость съёмки</string>\n    <string name=\"widget_skintone\">Улучшение цвета кожи</string>\n    <string name=\"widget_softwarehdr\">HDR</string>\n    <string name=\"widget_timermode\">Таймер</string>\n    <string name=\"widget_videofr\">Частота кадров видео</string>\n    <string name=\"widget_videohdr\">HDR-видео</string>\n    <string name=\"widget_whitebalance\">Баланс белого</string>\n    <string name=\"pano_panorama_rendering_failed\">Ошибка создания панорамы.\\nПопробуйте сделать ещё снимок.</string>\n    <string name=\"pano_panorama_rendering\">Создание панорамы\\u2026</string>\n    <string name=\"picsphere_undo_button\">ОТМЕНА</string>\n    <string name=\"picsphere_start_hint\">Сделайте снимок для создания фотосферы</string>\n    <string name=\"picsphere_already_rendering\">Пожалуйста, подождите завершения создания текущей фотосферы</string>\n    <string name=\"picsphere_rendering_progress\">Создание\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Создание началось\\u2026</string>\n    <string name=\"picsphere_notif_title\">Создание фотосферы\\u2026</string>\n    <string name=\"picsphere_failed\">Создание фотосферы завершилось с ошибкой</string>\n    <string name=\"picsphere_failed_details\">Убедитесь, что снимки накладываются друг на друга по краям</string>\n    <string name=\"picsphere_need_two_pics\">Сделайте минимум два снимка</string>\n    <string name=\"picsphere_step_preparing\">Подготовка\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Создание метаданных\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Запись ориентации камеры\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Поиск контрольных точек\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Выравнивание изображений\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Очистка точек\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Обрезка пустых областей\\u2026</string>\n    <string name=\"picsphere_step_nona\">Сборка изображений\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Смешивание изображений\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Вычисление HDR-изображения\\u2026</string>\n    <string name=\"software_hdr_failed\">Создание HDR-изображения завершилось с ошибкой</string>\n    <string name=\"software_hdr_failed_details\">Исходные изображения были сохранены</string>\n    <string name=\"showcase_welcome_1_title\">Добро пожаловать!</string>\n    <string name=\"showcase_welcome_1_body\">Настройки находятся в боковой панели, \\nпросто сдвиньте её.</string>\n    <string name=\"showcase_welcome_2_title\">Кнопка затвора</string>\n    <string name=\"showcase_welcome_2_body\">Нажмите на кнопку затвора, чтобы сделать снимок.\\nСдвиньте её для доступа к другим режимам съёмки.</string>\n    <string name=\"showcase_panorama_title\">Режим панорамы</string>\n    <string name=\"showcase_panorama_body\">Нажмите на кнопку затвора для начала создания панорамы,\\nа затем перемещайте камеру.\\nДля завершения панорамы нажмите на кнопку затвора.</string>\n    <string name=\"showcase_picsphere_title\">Режим фотосферы</string>\n    <string name=\"showcase_picsphere_body\">Наведите объектив на  понравившееся место,\\n затем делайте снимки фотосферы.\\nСледуйте по направлению синих точек.\\nДолгое нажатие кнопки затвора завершает создание фотосферы.</string>\n    <string name=\"iso_hint_auto\">Авто ISO</string>\n    <string name=\"iso_hint_hjr\">HJR ISO</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-sk/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string-array name=\"transformer_timer_voice_words\">\n        <item>cid</item>\n        <item>civ</item>\n        <item>whisky</item>\n        <item>whiskey</item>\n        <item>sýýr</item>\n        <item>odfoť ma</item>\n    </string-array>\n\n    <string-array name=\"widget_flash_hints\">\n        <item>Auto</item>\n        <item>Zakázaný</item>\n        <item>Povolený</item>\n        <item>Svietidlo</item>\n        <item>Odstránenie červených očí</item>\n    </string-array>\n\n    <string-array name=\"widget_effects_hints\">\n        <item>Zakázaný</item>\n        <item>Čiernobiely</item>\n        <item>Negatív</item>\n        <item>Solarizovať</item>\n        <item>Sépia</item>\n        <item>Plagát</item>\n        <item>Čierna tabuľa</item>\n        <item>Biela tabuľa</item>\n        <item>Voda</item>\n        <item>Reliéf</item>\n        <item>Náčrtok</item>\n        <item>Neón</item>\n    </string-array>\n\n    <string-array name=\"widget_scenemode_hints\">\n        <item>Automatická</item>\n        <item>Odstránenie rozmazania pohybom</item>\n        <item>Rozšírená realita</item>\n        <item>Najlepší záber</item>\n        <item>Dieťa</item>\n        <item>Podsvietenie</item>\n        <item>Podsvietenie portrétu</item>\n        <item>Čiarový kód</item>\n        <item>Pláž</item>\n        <item>Šero</item>\n        <item>Tma</item>\n        <item>Dish</item>\n        <item>Dokument</item>\n        <item>Ohňostroj</item>\n        <item>Kvety</item>\n        <item>Súmrak voľnou rukou</item>\n        <item>HDR</item>\n        <item>Vysoká citlivosť</item>\n        <item>Krajina</item>\n        <item>Noc</item>\n        <item>Nočný portrét</item>\n        <item>Nočný portrét</item>\n        <item>Večierok</item>\n        <item>Zvieratá</item>\n        <item>Portrét</item>\n        <item>Sneh</item>\n        <item>Jemný odtieň pleti</item>\n        <item>Šport</item>\n        <item>Reflektor</item>\n        <item>Stála fotografia</item>\n        <item>Západ slnka</item>\n        <item>Spájanie pohybom</item>\n        <item>Divadlo</item>\n    </string-array>\n\n    <string-array name=\"widget_whitebalance_hints\">\n        <item>Automaticky</item>\n        <item>Zamračené</item>\n        <item>Žiarovka</item>\n        <item>Žiarivka</item>\n        <item>Denné svetlo</item>\n        <item>Nepriame denné svetlo</item>\n    </string-array>\n\n    <string-array name=\"widget_autoexposure_hints\">\n        <item>Priemer snímku</item>\n        <item>Vyváženie na stred</item>\n        <item>Bodové meranie</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "res/values-sk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Spustiť Focal</string>\n    <string name=\"shutter_button\">Tlačidlo spúšte</string>\n    <string name=\"cannot_connect_hal\">Nepodarilo sa pripojiť k fotoaparátu</string>\n    <string name=\"recording\">Zaznamenáva sa</string>\n    <string name=\"double_tap_to_snapshot\">Dvojitým ťuknutím zachyťte snímku</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Vaše zariadenie nepodporuje GLES2</string>\n    <string name=\"no_gyroscope\">Vaše zariadenie neobsahuje gyroskop</string>\n    <string name=\"ps_long_press_to_stop\">Dlhým stlačením spúšte\\ndokončíte sféru.</string>\n    <string name=\"disabled\">Zakázané</string>\n    <string name=\"enabled\">Povolené</string>\n    <string name=\"no_video_player\">Nedá sa prehrať video, nenašiel sa žiadny prehrávač</string>\n    <string name=\"fullscreen_shutter_info\">Ťuknite kamkoľvek na zachytenie snímky</string>\n    <string name=\"please_wait\">Prosím, čakajte\\u2026</string>\n    <string name=\"retouch\">ŤUKNITE ZNOVU</string>\n    <string name=\"open_in_gallery\">GALÉRIA</string>\n    <string name=\"mode_photo\">Fotografia</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">PicSphere</string>\n    <string name=\"mode_panorama\">Panoráma</string>\n    <string name=\"mode_switchcam\">Predný fotoaparát</string>\n    <string name=\"widget_burstmode_count_shots\">Dávka %d snímkov</string>\n    <string name=\"widget_burstmode_off\">Zakázať dávkový režim</string>\n    <string name=\"widget_burstmode_infinite\">Nekonečná dávka</string>\n    <string name=\"widget_hdr_aebracket\">Auto bracketing</string>\n    <string name=\"widget_settings_choose_widgets\">Zvoľte miniaplikácie v bočnej lište</string>\n    <string name=\"widget_settings_choose_widgets_button\">Zvolenie miniaplikácií</string>\n    <string name=\"widget_settings_picture_size\">Veľkosť obrázka</string>\n    <string name=\"widget_settings_exposure_ring\">Zobrazenie kruhu expozície</string>\n    <string name=\"widget_settings_autoenhance\">Automatické vylepšenie</string>\n    <string name=\"widget_settings_ruleofthirds\">Rozdelenie na tretiny</string>\n    <string name=\"widget_autoexposure\">Režim merania expozície</string>\n    <string name=\"widget_burstmode\">Dávkový režim</string>\n    <string name=\"widget_effect\">Farebné efekty</string>\n    <string name=\"widget_enhancements\">Vylepšenie farieb</string>\n    <string name=\"widget_exposure_compensation\">Vyrovnanie expozície</string>\n    <string name=\"widget_flash\">Režim blesku</string>\n    <string name=\"widget_hdr\">Veľmi dynamický rozsah</string>\n    <string name=\"widget_iso\">Citlivosť ISO</string>\n    <string name=\"widget_scenemode\">Režim scény</string>\n    <string name=\"widget_settings\">Nastavenia</string>\n    <string name=\"widget_shutter_speed\">Rýchlosť spúšte</string>\n    <string name=\"widget_skintone\">Vylepšenie odtieňu pleti</string>\n    <string name=\"widget_softwarehdr\">Veľmi dynamický rozsah</string>\n    <string name=\"widget_timermode\">Režim časovača</string>\n    <string name=\"widget_videofr\">Snímková frekvencia videa</string>\n    <string name=\"widget_videohdr\">Veľmi dynamický rozsah videa</string>\n    <string name=\"widget_whitebalance\">Vyváženie bielej</string>\n    <string name=\"pano_panorama_rendering_failed\">Zlyhalo vykreslenie panorámy.\\nSkúste zachytiť kratšiu.</string>\n    <string name=\"pano_panorama_rendering\">Vykresľuje sa panoráma\\u2026</string>\n    <string name=\"picsphere_undo_button\">VRÁTIŤ SPÄŤ</string>\n    <string name=\"picsphere_start_hint\">Zachytením snímky spustíte sféru</string>\n    <string name=\"picsphere_already_rendering\">Prosím, počkajte na vykreslenie aktuálnej PicSphere</string>\n    <string name=\"picsphere_rendering_progress\">Vykresľuje sa... (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Vykresľovanie bolo spustené\\u2026</string>\n    <string name=\"picsphere_notif_title\">Vykresľovanie PicSphere\\u2026</string>\n    <string name=\"picsphere_failed\">Vykresľovanie PicSphere zlyhalo</string>\n    <string name=\"picsphere_failed_details\">Uistite sa, že sa obrázky prelínajú iba v rohoch.</string>\n    <string name=\"picsphere_need_two_pics\">Potrebujete aspoň dva snímky.</string>\n    <string name=\"picsphere_step_preparing\">Pripravuje sa\\u2026</string>\n    <string name=\"picsphere_step_ptogen\">Generujú sa metaúdaje\\u2026</string>\n    <string name=\"picsphere_step_ptovar\">Zapisuje sa otočenie fotoaparátu\\u2026</string>\n    <string name=\"picsphere_step_cpfind\">Hľadajú sa kontrolné body\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Optimalizujú sa zhody\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Čistia sa body zhôd\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Orezávajú sa prázdne plochy\\u2026</string>\n    <string name=\"picsphere_step_nona\">Spájajú sa snímky\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Tvarujú sa snímky\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Vypočítavanie HDR snímky\\u2026</string>\n    <string name=\"software_hdr_failed\">Vykresľovanie HDR zlyhalo</string>\n    <string name=\"software_hdr_failed_details\">Pôvodná snímka bola uchovaná.</string>\n    <string name=\"showcase_welcome_1_title\">Vitajte!</string>\n    <string name=\"showcase_welcome_1_body\">Voľby sú v bočnej lište,\\nstačí ju potiahnúť.</string>\n    <string name=\"showcase_welcome_2_title\">Tlačidlo spúšte</string>\n    <string name=\"showcase_welcome_2_body\">Ťuknutím na tlačidlo spúšte zachytíte snímku.\\nJeho potiahnútím sprístupníte ostatné režimy snímania.</string>\n    <string name=\"showcase_panorama_title\">Režim panorámy</string>\n    <string name=\"showcase_panorama_body\">Ťuknutím na tlačidlo spúšte spustíte vašu panorámu,\\npotom posúvajte na to, čo chcete zachytiť.\\nNa dokončenie ťuknite znovu na tlačidlo spúšte.</string>\n    <string name=\"showcase_picsphere_title\">Režim PicSphere</string>\n    <string name=\"showcase_picsphere_body\">Zarovnajte vašu prvú snímku podľa potreby,\\npotom ťuknite na spúšť na zachytenie každej snímky\\nsféry. Modré bodky sú referenčné\\nbody, ktoré môžete nasledovať.\\nDlhším stlačením spúšte dokončíte sféru.</string>\n    <string name=\"iso_hint_auto\">Automatické ISO</string>\n    <string name=\"iso_hint_hjr\">ISO odstraňujúce chvenie rúk</string>\n    <string name=\"iso_hint_100\">100 ISO</string>\n    <string name=\"iso_hint_200\">200 ISO</string>\n    <string name=\"iso_hint_400\">400 ISO</string>\n    <string name=\"iso_hint_800\">800 ISO</string>\n    <string name=\"iso_hint_1600\">1600 ISO</string>\n</resources>\n"
  },
  {
    "path": "res/values-sv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 Guillaume Lesniak\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<resources>\n    <string name=\"app_name\">Focal</string>\n    <string name=\"launch_app\">Starta Focal</string>\n    <string name=\"shutter_button\">Avtryckare</string>\n    <string name=\"cannot_connect_hal\">Kan inte kontakta kameran</string>\n    <string name=\"recording\">Inspelning</string>\n    <string name=\"double_tap_to_snapshot\">Dubbelklicka för att ta en bild</string>\n    <string name=\"video_res_1080p\">Full HD (1080p)</string>\n    <string name=\"video_res_720p\">HD (720p)</string>\n    <string name=\"video_res_480p\">SD (480p)</string>\n    <string name=\"video_res_mms\">MMS (288p)</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"no_gles20_support\">Din enhet stöder inte GLES2</string>\n    <string name=\"no_gyroscope\">Din enhet har inget gyroskop</string>\n    <string name=\"ps_long_press_to_stop\">Långtryck på avtryckaren för att slutföra din sfär.</string>\n    <string name=\"disabled\">Avaktiverad</string>\n    <string name=\"enabled\">Aktiverad</string>\n    <string name=\"no_video_player\">Kan inte spela videon, ingen spelare funnen</string>\n    <string name=\"fullscreen_shutter_info\">Tryck var som helst för att ta en bild</string>\n    <string name=\"please_wait\">Var god vänta\\u2026</string>\n    <string name=\"retouch\">RETUSCHERA</string>\n    <string name=\"open_in_gallery\">GALLERI</string>\n    <string name=\"mode_photo\">Foto</string>\n    <string name=\"mode_video\">Video</string>\n    <string name=\"mode_picsphere\">Fotosfär</string>\n    <string name=\"mode_panorama\">Panorama</string>\n    <string name=\"mode_switchcam\">Frontkamera</string>\n    <string name=\"widget_burstmode_count_shots\">Sekvensfota %d bilder</string>\n    <string name=\"widget_burstmode_off\">Avaktivera sekvensläge</string>\n    <string name=\"widget_burstmode_infinite\">Oändlig sekvens</string>\n    <string name=\"widget_hdr_aebracket\">Autogaffling</string>\n    <string name=\"widget_settings_choose_widgets\">Välj widgets i sidmenyn</string>\n    <string name=\"widget_settings_choose_widgets_button\">Välj widgets</string>\n    <string name=\"widget_settings_picture_size\">Bildstorlek</string>\n    <string name=\"widget_settings_exposure_ring\">Visa exponeringsring</string>\n    <string name=\"widget_settings_autoenhance\">Auto-förbättring</string>\n    <string name=\"widget_settings_ruleofthirds\">Tredjedelsregeln</string>\n    <string name=\"widget_autoexposure\">Exponeringsmätarläge</string>\n    <string name=\"widget_burstmode\">Sekvensläge</string>\n    <string name=\"widget_effect\">Färgeffekter</string>\n    <string name=\"widget_enhancements\">Färgförbättringar</string>\n    <string name=\"widget_exposure_compensation\">Exponeringskompensation</string>\n    <string name=\"widget_flash\">Blixtläge</string>\n    <string name=\"widget_hdr\">High Dynamic Range</string>\n    <string name=\"widget_iso\">ISO-grader</string>\n    <string name=\"widget_scenemode\">Scenläge</string>\n    <string name=\"widget_settings\">Inställningar</string>\n    <string name=\"widget_shutter_speed\">Slutartid</string>\n    <string name=\"widget_skintone\">Hudtonsförbättring</string>\n    <string name=\"widget_softwarehdr\">High Dynamic Range</string>\n    <string name=\"widget_timermode\">Självutlösare</string>\n    <string name=\"widget_videofr\">Framerate</string>\n    <string name=\"widget_videohdr\">Video High Dynamic Range</string>\n    <string name=\"widget_whitebalance\">Vitbalans</string>\n    <string name=\"pano_panorama_rendering_failed\">Lyckades inte rendera panorama.\\nFörsök att ta en kortare.</string>\n    <string name=\"pano_panorama_rendering\">Renderar panorama\\u2026</string>\n    <string name=\"picsphere_undo_button\">ÅNGRA</string>\n    <string name=\"picsphere_start_hint\">Ta en bild för att påbörja en sfär</string>\n    <string name=\"picsphere_already_rendering\">Var god vänta medan den nuvarande bildsfären renderas</string>\n    <string name=\"picsphere_rendering_progress\">Renderar\\u2026 (%d %%)</string>\n    <string name=\"picsphere_toast_background_render\">Rendering påbörjad\\u2026</string>\n    <string name=\"picsphere_notif_title\">Renderar bildsfär\\u2026</string>\n    <string name=\"picsphere_failed\">Lyckades inte rendera bildsfär</string>\n    <string name=\"picsphere_failed_details\">Kontrollera att bilderna endast överlappar vid kanterna.</string>\n    <string name=\"picsphere_need_two_pics\">Du behöver åtminstone två bilder.</string>\n    <string name=\"picsphere_step_preparing\">Förbereder\\u2026</string>\n    <string name=\"picsphere_step_autooptimiser\">Optimerar matchningar\\u2026</string>\n    <string name=\"picsphere_step_ptclean\">Rensar matchningspunkter\\u2026</string>\n    <string name=\"picsphere_step_panomodify\">Beskär tomma områden\\u2026</string>\n    <string name=\"picsphere_step_nona\">Sömmar bilder\\u2026</string>\n    <string name=\"picsphere_step_enblend\">Förenar bilder\\u2026</string>\n    <string name=\"software_hdr_notif_title\">Beräknar HDR-bild\\u2026</string>\n    <string name=\"software_hdr_failed\">HDR-renderingen misslyckades</string>\n    <string name=\"software_hdr_failed_details\">Originalbilderna har sparats.</string>\n    <string name=\"showcase_welcome_1_title\">Välkommen!</string>\n    <string name=\"showcase_welcome_1_body\">Inställningarna finns i sidmenyn,\\nsvep bara fram den.</string>\n    <string name=\"showcase_welcome_2_title\">Avtryckare</string>\n    <string name=\"showcase_welcome_2_body\">Tryck på avtryckaren för att ta en bild.\\nSvep från den för att komma åt andra kameralägen.</string>\n    <string name=\"showcase_panorama_title\">Panoramaläge</string>\n    <string name=\"showcase_panorama_body\">Tryck på avtryckaren för att börja din panorama,\\npanorera sedan det du vill fota.\\nNär du är klar, tryck på avtryckaren igen.</string>\n    <string name=\"showcase_picsphere_title\">Bildsfärläge</string>\n    <string name=\"showcase_picsphere_body\">Ta din första bild var du vill,\\ntryck sedan på avtryckaren för att övriga bilder\\nav sfären. De blå prickarna är\\nreferenspunkter som du kan följa.\\nLångtryck på avtryckaren när du är klar.</string>\n</resources>\n"
  },
  {
    "path": "res/xml/widget_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2013 The CyanogenMod Project\n\n     This program is free software; you can redistribute it and/or\n     modify it under the terms of the GNU General Public License\n     as published by the Free Software Foundation; either version 2\n     of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful,\n     but WITHOUT ANY WARRANTY; without even the implied warranty of\n     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n     GNU General Public License for more details.\n\n     You should have received a copy of the GNU General Public License\n     along with this program; if not, write to the Free Software\n     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n     MA  02110-1301, USA.\n-->\n<appwidget-provider xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:minWidth=\"400dp\"\n        android:minHeight=\"92dp\"\n        android:updatePeriodMillis=\"86400000\"\n        android:previewImage=\"@drawable/ic_launcher\"\n        android:initialLayout=\"@layout/widget_layout\"\n        android:resizeMode=\"\"\n        android:widgetCategory=\"home_screen|keyguard\"\n        android:initialKeyguardLayout=\"@layout/widget_layout\">\n\n</appwidget-provider>\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/BitmapFilter.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BlurMaskFilter;\nimport android.graphics.BlurMaskFilter.Blur;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.LightingColorFilter;\nimport android.graphics.Paint;\nimport android.renderscript.Allocation;\nimport android.renderscript.Element;\nimport android.renderscript.RenderScript;\nimport android.renderscript.ScriptIntrinsicBlur;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * This class renders a bitmap with a certain effect\n * and cache it for future use.\n */\npublic class BitmapFilter {\n    private static BitmapFilter mSingleton;\n\n    private Map<String, Bitmap> mGlowCache;\n    private RenderScript mRS;\n    private Allocation mBlurInputAllocation;\n    private Allocation mBlurOutputAllocation;\n    private ScriptIntrinsicBlur mBlurScript;\n\n    public static BitmapFilter getSingleton() {\n        if (mSingleton == null) {\n            mSingleton = new BitmapFilter();\n        }\n\n        return mSingleton;\n    }\n\n    private BitmapFilter() {\n        mGlowCache = new HashMap<String, Bitmap>();\n    }\n\n    /**\n     * Blurs a bitmap. There's no caching on this one.\n     *\n     * @param src The bitmap to blur\n     * @return A blurred bitmap\n     */\n    public Bitmap getBlur(Context context, Bitmap src, float radius) {\n        if (android.os.Build.VERSION.SDK_INT >= 17) {\n            if (mRS == null) {\n                mRS = RenderScript.create(context);\n            }\n\n            if (mBlurScript == null) {\n                mBlurScript = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));\n            }\n\n            if (mBlurInputAllocation == null) {\n                mBlurInputAllocation = Allocation.createFromBitmap(mRS, src,\n                        Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE);\n                mBlurScript.setInput(mBlurInputAllocation);\n            } else {\n                mBlurInputAllocation.copyFrom(src);\n            }\n\n            if (mBlurOutputAllocation == null) {\n                mBlurOutputAllocation = Allocation.createTyped(mRS, mBlurInputAllocation.getType());\n            }\n\n            mBlurScript.setRadius(radius);\n            mBlurScript.forEach(mBlurOutputAllocation);\n            mBlurOutputAllocation.copyTo(src);\n        }\n\n        return src;\n    }\n\n    /**\n     * Returns a glowed image of the provided icon. If the\n     * provided name is already in the cache, the cached image\n     * will be returned. Otherwise, the bitmap will be glowed and\n     * cached under the provided name\n     *\n     * @param name The name of the bitmap\n     * @param src  The bitmap of the icon itself\n     * @return Glowed bitmap\n     */\n    public Bitmap getGlow(String name, int glowColor, Bitmap src) {\n        if (mGlowCache.containsKey(name)) {\n            return mGlowCache.get(name);\n        } else {\n            // An added margin to the initial image\n            int margin = 0;\n            int halfMargin = margin / 2;\n\n            // The glow radius\n            int glowRadius = 4;\n\n            // Extract the alpha from the source image\n            Bitmap alpha = src.extractAlpha();\n\n            // The output bitmap (with the icon + glow)\n            Bitmap bmp = Bitmap.createBitmap(src.getWidth() + margin,\n                    src.getHeight() + margin, Bitmap.Config.ARGB_8888);\n\n            // The canvas to paint on the image\n            Canvas canvas = new Canvas(bmp);\n\n            Paint paint = new Paint();\n            paint.setColor(glowColor);\n\n            // Outer glow\n            ColorFilter emphasize = new LightingColorFilter(glowColor, 1);\n            paint.setColorFilter(emphasize);\n            canvas.drawBitmap(src, halfMargin, halfMargin, paint);\n            paint.setColorFilter(null);\n            paint.setMaskFilter(new BlurMaskFilter(glowRadius, Blur.OUTER));\n            canvas.drawBitmap(alpha, halfMargin, halfMargin, paint);\n\n            // Cache icon\n            mGlowCache.put(name, bmp);\n\n            return bmp;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/CameraActivity.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.app.Activity;\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.pm.ConfigurationInfo;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.Point;\nimport android.hardware.Camera;\nimport android.opengl.GLSurfaceView;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.OrientationEventListener;\nimport android.view.ScaleGestureDetector;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.view.ViewGroup;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.Button;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport org.cyanogenmod.focal.feats.CaptureTransformer;\nimport org.cyanogenmod.focal.feats.SoftwareHdrCapture;\nimport org.cyanogenmod.focal.pano.MosaicProxy;\nimport org.cyanogenmod.focal.picsphere.PicSphereCaptureTransformer;\nimport org.cyanogenmod.focal.picsphere.PicSphereManager;\nimport org.cyanogenmod.focal.ui.CircleTimerView;\nimport org.cyanogenmod.focal.ui.ExposureHudRing;\nimport org.cyanogenmod.focal.ui.FocusHudRing;\nimport org.cyanogenmod.focal.ui.Notifier;\nimport org.cyanogenmod.focal.ui.PanoProgressBar;\nimport org.cyanogenmod.focal.ui.ReviewDrawer;\nimport org.cyanogenmod.focal.ui.SavePinger;\nimport org.cyanogenmod.focal.ui.ShutterButton;\nimport org.cyanogenmod.focal.ui.SideBar;\nimport org.cyanogenmod.focal.ui.SwitchRingPad;\nimport org.cyanogenmod.focal.ui.ThumbnailFlinger;\nimport org.cyanogenmod.focal.ui.WidgetRenderer;\nimport org.cyanogenmod.focal.ui.showcase.ShowcaseView;\n\nimport fr.xplod.focal.R;\n\npublic class CameraActivity extends Activity implements CameraManager.CameraReadyListener,\n        ShowcaseView.OnShowcaseEventListener {\n    public final static String TAG = \"CameraActivity\";\n\n    public final static int CAMERA_MODE_PHOTO     = 1;\n    public final static int CAMERA_MODE_VIDEO     = 2;\n    public final static int CAMERA_MODE_PANO      = 3;\n    public final static int CAMERA_MODE_PICSPHERE = 4;\n\n    // whether or not to enable profiling\n    private final static boolean DEBUG_PROFILE = true;\n\n    private static int mCameraMode = CAMERA_MODE_PHOTO;\n\n    private CameraManager mCamManager;\n    private SnapshotManager mSnapshotManager;\n    private MainSnapshotListener mSnapshotListener;\n    private FocusManager mFocusManager;\n    private PicSphereManager mPicSphereManager;\n    private MosaicProxy mMosaicProxy;\n    private CameraOrientationEventListener mOrientationListener;\n    private GestureDetector mGestureDetector;\n    private CaptureTransformer mCaptureTransformer;\n    private Handler mHandler;\n    private boolean mPaused;\n\n    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;\n    private int mOrientationCompensation = 0;\n\n    private SideBar mSideBar;\n    private WidgetRenderer mWidgetRenderer;\n    private FocusHudRing mFocusHudRing;\n    private ExposureHudRing mExposureHudRing;\n    private SwitchRingPad mSwitchRingPad;\n    private ShutterButton mShutterButton;\n    private SavePinger mSavePinger;\n    private PanoProgressBar mPanoProgressBar;\n    private Button mPicSphereUndo;\n    private CircleTimerView mTimerView;\n    private ViewGroup mRecTimerContainer;\n    private static Notifier mNotifier;\n    private ReviewDrawer mReviewDrawer;\n    private ScaleGestureDetector mZoomGestureDetector;\n    private TextView mHelperText;\n    private ShowcaseView mShowcaseView;\n    private boolean mHasPinchZoomed;\n    private boolean mCancelSideBarClose;\n    private boolean mIsFocusButtonDown;\n    private boolean mIsShutterButtonDown;\n    private boolean mUserWantsExposureRing;\n    private boolean mIsFullscreenShutter;\n    private int mShowcaseIndex;\n    private boolean mIsCamSwitching;\n    private boolean mIsShutterLongClicked = false;\n    private CameraPreviewListener mCamPreviewListener;\n    private GLSurfaceView mGLSurfaceView;\n    private boolean mIsFocusing = false;\n\n    private final static int SHOWCASE_INDEX_WELCOME_1 = 0;\n    private final static int SHOWCASE_INDEX_WELCOME_2 = 1;\n    private final static int SHOWCASE_INDEX_PANORAMA  = 0;\n    private final static int SHOWCASE_INDEX_PICSPHERE = 0;\n\n    private final static String KEY_SHOWCASE_WELCOME = \"SHOWCASE_WELCOME\";\n    private final static String KEY_SHOWCASE_PANORAMA = \"SHOWCASE_PANORAMA\";\n    private final static String KEY_SHOWCASE_PICSPHERE = \"SHOWCASE_PICSPHERE\";\n\n    /**\n     * Gesture listeners to apply on camera previews views\n     */\n    private View.OnTouchListener mPreviewTouchListener =  new View.OnTouchListener() {\n        @Override\n        public boolean onTouch(View v, MotionEvent ev) {\n            if (ev.getAction() == MotionEvent.ACTION_UP) {\n                mSideBar.clampSliding();\n                mReviewDrawer.clampSliding();\n            }\n\n            // Process HUD gestures only if we aren't pinching\n            mHasPinchZoomed = false;\n            mZoomGestureDetector.onTouchEvent(ev);\n\n            if (!mHasPinchZoomed) {\n                mGestureDetector.onTouchEvent(ev);\n            }\n\n            return true;\n        }\n    };\n\n    /**\n     * Event: Activity created\n     */\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_camera);\n\n        mPaused = false;\n        mIsCamSwitching = false;\n\n        getWindow().getDecorView()\n                .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);\n\n        mUserWantsExposureRing = true;\n        mIsFullscreenShutter = false;\n\n        mSideBar = (SideBar) findViewById(R.id.sidebar_scroller);\n        mWidgetRenderer = (WidgetRenderer) findViewById(R.id.widgets_container);\n        mSavePinger = (SavePinger) findViewById(R.id.save_pinger);\n        mTimerView = (CircleTimerView) findViewById(R.id.timer_view);\n        mHelperText = (TextView) findViewById(R.id.txt_helper);\n        mPicSphereUndo = (Button) findViewById(R.id.btn_picsphere_undo);\n\n        mSwitchRingPad = (SwitchRingPad) findViewById(R.id.switch_ring_pad);\n        mSwitchRingPad.setListener(new MainRingPadListener());\n\n        mPanoProgressBar = (PanoProgressBar) findViewById(R.id.panorama_progress_bar);\n        mRecTimerContainer = (ViewGroup) findViewById(R.id.recording_timer_container);\n        mNotifier = (Notifier) findViewById(R.id.notifier_container);\n\n        mReviewDrawer = (ReviewDrawer) findViewById(R.id.review_drawer);\n\n        // Create orientation listener. This should be done first because it\n        // takes some time to get first orientation.\n        mOrientationListener = new CameraOrientationEventListener(this);\n        mOrientationListener.enable();\n\n        mHandler = new Handler();\n\n        // Setup the camera hardware and preview\n        setupCamera();\n\n        SoundManager.getSingleton().preload(this);\n\n        // Setup HUDs\n        mFocusHudRing = (FocusHudRing) findViewById(R.id.hud_ring_focus);\n\n        mExposureHudRing = (ExposureHudRing) findViewById(R.id.hud_ring_exposure);\n        mExposureHudRing.setManagers(mCamManager);\n\n        // Setup shutter button\n        mShutterButton = (ShutterButton) findViewById(R.id.btn_shutter);\n        MainShutterClickListener shutterClickListener = new MainShutterClickListener();\n        mShutterButton.setOnClickListener(shutterClickListener);\n        mShutterButton.setOnLongClickListener(shutterClickListener);\n        mShutterButton.setOnTouchListener(shutterClickListener);\n        mShutterButton.setSlideListener(new MainShutterSlideListener());\n\n        // Setup gesture detection\n        mGestureDetector = new GestureDetector(this, new GestureListener());\n        mZoomGestureDetector = new ScaleGestureDetector(this, new ZoomGestureListener());\n\n        findViewById(R.id.gl_renderer_container).setOnTouchListener(mPreviewTouchListener);\n\n        // Use SavePinger to animate a bit while we open the camera device\n        mSavePinger.setPingMode(SavePinger.PING_MODE_SIMPLE);\n        mSavePinger.startSaving();\n\n        // Hack because review drawer size might not be measured yet\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                mReviewDrawer.open();\n                mReviewDrawer.close();\n            }\n        }, 300);\n\n        startShowcaseWelcome();\n    }\n\n    public int getOrientation() {\n        return mOrientationCompensation;\n    }\n\n    public void startShowcaseWelcome() {\n        if (SettingsStorage.getAppSetting(this, KEY_SHOWCASE_WELCOME, \"0\").equals(\"0\")) {\n            SettingsStorage.storeAppSetting(this, KEY_SHOWCASE_WELCOME, \"1\");\n            ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions();\n            co.hideOnClickOutside = true;\n            mShowcaseView = ShowcaseView.insertShowcaseView(mSideBar,\n                    this, getString(R.string.showcase_welcome_1_title),\n                    getString(R.string.showcase_welcome_1_body), co);\n\n            // Animate gesture\n            Point size = new Point();\n            getWindowManager().getDefaultDisplay().getSize(size);\n\n            mShowcaseView.animateGesture(size.x/2, size.y*2.0f/3.0f, size.x/2, size.y/2.0f);\n            mShowcaseView.setOnShowcaseEventListener(this);\n            mShowcaseIndex = SHOWCASE_INDEX_WELCOME_1;\n        }\n    }\n\n    public void startShowcasePanorama() {\n        if (SettingsStorage.getAppSetting(this, KEY_SHOWCASE_PANORAMA, \"0\").equals(\"0\")) {\n            SettingsStorage.storeAppSetting(this, KEY_SHOWCASE_PANORAMA, \"1\");\n            ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions();\n            co.hideOnClickOutside = true;\n            Point size = new Point();\n            getWindowManager().getDefaultDisplay().getSize(size);\n            mShowcaseView = ShowcaseView.insertShowcaseView(size.x/2, size.y - Util.dpToPx(this, 16),\n                    this, getString(R.string.showcase_panorama_title),\n                    getString(R.string.showcase_panorama_body), co);\n\n            mShowcaseIndex = SHOWCASE_INDEX_PANORAMA;\n        }\n    }\n\n    public void startShowcasePicSphere() {\n        if (SettingsStorage.getAppSetting(this, KEY_SHOWCASE_PICSPHERE, \"0\").equals(\"0\")) {\n            SettingsStorage.storeAppSetting(this, KEY_SHOWCASE_PICSPHERE, \"1\");\n            ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions();\n            co.hideOnClickOutside = true;\n            Point size = new Point();\n            getWindowManager().getDefaultDisplay().getSize(size);\n\n            mShowcaseView = ShowcaseView.insertShowcaseView(size.x / 2,\n                    size.y - Util.dpToPx(this, 16), this, getString(R.string.showcase_picsphere_title),\n                    getString(R.string.showcase_picsphere_body), co);\n\n            mShowcaseIndex = SHOWCASE_INDEX_PICSPHERE;\n\n            mShowcaseView.notifyOrientationChanged(mOrientationCompensation);\n        }\n    }\n\n    @Override\n    protected void onPause() {\n        // Pause the camera preview\n        mPaused = true;\n\n        if (mCamManager != null) {\n            mCamManager.pause();\n        }\n\n        if (mSnapshotManager != null) {\n            mSnapshotManager.onPause();\n        }\n\n        if (mOrientationListener != null) {\n            mOrientationListener.disable();\n        }\n\n        if (mPicSphereManager != null) {\n            mPicSphereManager.onPause();\n        }\n\n        if (SoftwareHdrCapture.isServiceBound()) {\n            try {\n                unbindService(SoftwareHdrCapture.getServiceConnection());\n            } catch (IllegalArgumentException e) {\n                // Do nothing\n            }\n        }\n\n        // Reset capture transformers on pause, if we are in\n        // PicSphere mode\n        if (mCameraMode == CAMERA_MODE_PICSPHERE) {\n            mCaptureTransformer = null;\n        }\n\n        super.onPause();\n    }\n\n    @Override\n    protected void onResume() {\n        // Restore the camera preview\n        mPaused = false;\n\n        if (mCamManager != null) {\n            mCamManager.resume();\n        }\n\n        super.onResume();\n\n        if (mSnapshotManager != null) {\n            mSnapshotManager.onResume();\n        }\n\n        if (mPicSphereManager != null) {\n            mPicSphereManager.onResume();\n        }\n\n        mOrientationListener.enable();\n\n        mReviewDrawer.close();\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (mReviewDrawer.isOpen()) {\n            mReviewDrawer.close();\n        } else {\n            super.onBackPressed();\n        }\n    }\n\n    /**\n     * Returns the mode of the activity\n     * See CameraActivity.CAMERA_MODE_*\n     *\n     * @return int\n     */\n    public static int getCameraMode() {\n        return mCameraMode;\n    }\n\n    /**\n     * Notify, like a toast, but orientation aware\n     * @param text The text to show\n     * @param lengthMs The duration\n     */\n    public static void notify(String text, int lengthMs) {\n        mNotifier.notify(text, lengthMs);\n    }\n\n    /**\n     * Notify, like a toast, but orientation aware at the specified position\n     * @param text The text to show\n     * @param lengthMs The duration\n     *\n     */\n    public static void notify(String text, int lengthMs, float x, float y) {\n        mNotifier.notify(text, lengthMs, x, y);\n    }\n\n    /**\n     * @return The Panorama Progress Bar view\n     */\n    public PanoProgressBar getPanoProgressBar() {\n        return mPanoProgressBar;\n    }\n\n    public void displayOverlayBitmap(Bitmap bmp) {\n        final ImageView iv = (ImageView) findViewById(R.id.camera_preview_overlay);\n        iv.setImageBitmap(bmp);\n        iv.setAlpha(1.0f);\n        iv.setScaleType(ImageView.ScaleType.FIT_CENTER);\n        Util.fadeIn(iv);\n        iv.setVisibility(View.VISIBLE);\n    }\n\n    public void hideOverlayBitmap() {\n        final ImageView iv = (ImageView) findViewById(R.id.camera_preview_overlay);\n        Util.fadeOut(iv);\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                iv.setVisibility(View.GONE);\n            }\n        }, 300);\n    }\n\n    /**\n     * Sets the mode of the activity\n     * See CameraActivity.CAMERA_MODE_*\n     *\n     * @param newMode\n     */\n    public void setCameraMode(final int newMode) {\n        if (mCameraMode == newMode) {\n            return;\n        }\n\n        if (mCamManager.getParameters() == null) {\n            mHandler.post(new Runnable() {\n                public void run() {\n                    setCameraMode(newMode);\n                }\n            });\n        }\n\n        if (mCamPreviewListener != null) {\n            mCamPreviewListener.onPreviewPause();\n        }\n\n        setHelperText(\"\");\n\n        // Reset PicSphere 3D renderer if we were in PS mode\n        if (mCameraMode == CAMERA_MODE_PICSPHERE) {\n            resetPicSphere();\n        } else if (mCameraMode == CAMERA_MODE_PANO) {\n            resetPanorama();\n        } \n        else if (mCameraMode == CAMERA_MODE_VIDEO){\n            // must release the camera\n            // to reset internals - at least on find5\n            mCamManager.pause();\n            mCamManager.resume();\n        }\n\n        mCameraMode = newMode;\n\n        // Reset any capture transformer\n        mCaptureTransformer = null;\n\n        if (newMode == CAMERA_MODE_PHOTO) {\n            mShutterButton.setImageDrawable(getResources().getDrawable(R.drawable.btn_shutter_photo));\n            mCamManager.setStabilization(false);\n        } else if (newMode == CAMERA_MODE_VIDEO) {\n            mShutterButton.setImageDrawable(getResources().getDrawable(R.drawable.btn_shutter_video));\n            mCamManager.setStabilization(true);\n            mNotifier.notify(getString(R.string.double_tap_to_snapshot), 2500);\n        } else if (newMode == CAMERA_MODE_PICSPHERE) {\n            initializePicSphere();\n            mShutterButton.setImageDrawable(getResources().getDrawable(R.drawable.btn_shutter_photo));\n            startShowcasePicSphere();\n        } else if (newMode == CAMERA_MODE_PANO) {\n            mShutterButton.setImageDrawable(getResources().getDrawable(R.drawable.btn_shutter_photo));\n        }\n\n        mCamManager.setCameraMode(mCameraMode);\n\n        if (newMode == CAMERA_MODE_PANO) {\n            initializePanorama();\n            startShowcasePanorama();\n        }\n\n        // Reload pictures in the ReviewDrawer\n        mReviewDrawer.updateFromGallery(newMode != CAMERA_MODE_VIDEO, 0);\n        mHandler.post(new Runnable() {\n            public void run() {\n                updateCapabilities();\n            }\n        });\n    }\n\n    /**\n     * Sets the active capture transformer. See {@link CaptureTransformer} for\n     * more details on what's a capture transformer.\n     *\n     * @param transformer The new transformer to apply\n     */\n    public void setCaptureTransformer(CaptureTransformer transformer) {\n        if (mCaptureTransformer != null) {\n            mSnapshotManager.removeListener(mCaptureTransformer);\n        }\n        mCaptureTransformer = transformer;\n\n        if (mCaptureTransformer != null && mSnapshotManager != null) {\n            mSnapshotManager.addListener(transformer);\n        }\n    }\n\n    /**\n     * Updates the orientation of the whole UI (in place)\n     * based on the calculations given by the orientation listener\n     */\n    public void updateInterfaceOrientation() {\n        setViewRotation(mShutterButton, mOrientationCompensation);\n        setViewRotation(mRecTimerContainer, mOrientationCompensation);\n        setViewRotation(mPanoProgressBar, mOrientationCompensation);\n        setViewRotation(mPicSphereUndo, mOrientationCompensation);\n        setViewRotation(mHelperText, mOrientationCompensation);\n        mNotifier.notifyOrientationChanged(mOrientationCompensation);\n        mSideBar.notifyOrientationChanged(mOrientationCompensation);\n        mWidgetRenderer.notifyOrientationChanged(mOrientationCompensation);\n        mSwitchRingPad.notifyOrientationChanged(mOrientationCompensation);\n        mSavePinger.notifyOrientationChanged(mOrientationCompensation);\n        mReviewDrawer.notifyOrientationChanged(mOrientationCompensation);\n    }\n\n    public void updateCapabilities() {\n        // Populate the sidebar buttons a little later (so we have camera parameters)\n        mHandler.post(new Runnable() {\n            public void run() {\n                Camera.Parameters params = mCamManager.getParameters();\n\n                // We don't have the camera parameters yet, retry later\n                if (params == null) {\n                    if (!mPaused) {\n                        mHandler.postDelayed(this, 100);\n                    }\n                } else {\n                    mCamManager.startParametersBatch();\n\n                    // Close all widgets\n                    mWidgetRenderer.closeAllWidgets();\n\n                    // Update focus/exposure ring support\n                    updateRingsVisibility();\n\n                    // Update sidebar\n                    mSideBar.checkCapabilities(CameraActivity.this,\n                            (ViewGroup) findViewById(R.id.widgets_container));\n\n                    // Set orientation\n                    updateInterfaceOrientation();\n\n                    mCamManager.stopParametersBatch();\n                }\n            }\n        });\n    }\n\n    public void updateRingsVisibility() {\n        // Rings logic:\n        //    * PicSphere and panorama don't need it (infinity focus when possible)\n        //    * Show focus all the time otherwise in photo and video\n        //    * Show exposure ring in photo and video, if it's not toggled off\n        //    * Fullscreen shutter hides all the rings\n        if ((mCameraMode == CAMERA_MODE_PHOTO && !mIsFullscreenShutter)\n                || mCameraMode == CAMERA_MODE_VIDEO) {\n            mFocusHudRing.setVisibility(mCamManager.isFocusAreaSupported() ?\n                    View.VISIBLE : View.GONE);\n            mExposureHudRing.setVisibility(mCamManager.isExposureAreaSupported()\n                    && mUserWantsExposureRing ? View.VISIBLE : View.GONE);\n        } else {\n            mFocusHudRing.setVisibility(View.GONE);\n            mExposureHudRing.setVisibility(View.GONE);\n        }\n    }\n\n    public boolean isExposureRingVisible() {\n        return (mExposureHudRing.getVisibility() == View.VISIBLE);\n    }\n\n    public void setExposureRingVisible(boolean visible) {\n        mUserWantsExposureRing = visible;\n        updateRingsVisibility();\n\n        // Internally reset the position of the exposure ring, while still\n        // leaving it at its position so that if the user toggles it back\n        // on, it will appear at its previous location\n        mCamManager.setExposurePoint(0, 0);\n    }\n\n    public void startTimerCountdown(int timeMs) {\n        mTimerView.animate().alpha(1.0f).setDuration(300).start();\n        mTimerView.setIntervalTime(timeMs);\n        mTimerView.startIntervalAnimation();\n    }\n\n    public void hideTimerCountdown() {\n        mTimerView.animate().alpha(0.0f).setDuration(300).start();\n    }\n\n    protected void setupCamera() {\n        // Setup the Camera hardware and preview\n        mCamManager = new CameraManager(this);\n        ((CameraApplication)getApplication()).setCameraManager(mCamManager);\n\n        setGLRenderer(mCamManager.getRenderer());\n\n        mCamPreviewListener = new CameraPreviewListener();\n        mCamManager.setPreviewPauseListener(mCamPreviewListener);\n        mCamManager.setCameraReadyListener(this);\n\n        mCamManager.open(Camera.CameraInfo.CAMERA_FACING_BACK);\n    }\n\n    @Override\n    public void onCameraReady() {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                Profiler.getDefault().start(\"OnCameraReady\");\n                Camera.Parameters params = mCamManager.getParameters();\n\n                if (params == null) {\n                    // Are we too fast? Let's try again.\n                    mHandler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            onCameraReady();\n                        }\n                    }, 20);\n                    return;\n                }\n\n                mCamManager.updateDisplayOrientation();\n                Camera.Size picSize = params.getPictureSize();\n\n                Camera.Size sz = Util.getOptimalPreviewSize(CameraActivity.this, params.getSupportedPreviewSizes(),\n                        ((float) picSize.width / (float) picSize.height));\n                if (sz == null) {\n                    Log.e(TAG, \"No preview size!! Something terribly wrong with camera!\");\n                    return;\n                }\n                //mCamManager.setPreviewSize(sz.width, sz.height);\n\n                if (mIsCamSwitching) {\n                    mCamManager.restartPreviewIfNeeded();\n                    mIsCamSwitching = false;\n                }\n\n                if (mFocusManager == null) {\n                    mFocusManager = new FocusManager(mCamManager);\n                    mFocusManager.setListener(new MainFocusListener());\n                }\n\n                mFocusHudRing.setManagers(mCamManager, mFocusManager);\n\n                if (mSnapshotManager == null) {\n                    mSnapshotManager = new SnapshotManager(mCamManager, mFocusManager, CameraActivity.this);\n                    mSnapshotListener = new MainSnapshotListener();\n                    mSnapshotManager.addListener(mSnapshotListener);\n                }\n\n                // Hide sidebar after start\n                mCancelSideBarClose = false;\n                mHandler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        if (!mCancelSideBarClose) {\n                            mSideBar.slideClose();\n                            mWidgetRenderer.notifySidebarSlideClose();\n                        }\n                    }\n                }, 1500);\n\n                Profiler.getDefault().start(\"OnCameraReady-updateCapa\");\n                updateCapabilities();\n                Profiler.getDefault().logProfile(\"OnCameraReady-updateCapa\");\n\n                mSavePinger.stopSaving();\n                Profiler.getDefault().logProfile(\"OnCameraReady\");\n            }\n        });\n    }\n\n    public void onCameraFailed() {\n        Log.e(TAG, \"Could not open camera HAL\");\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                Toast.makeText(CameraActivity.this,\n                        getResources().getString(R.string.cannot_connect_hal),\n                        Toast.LENGTH_LONG).show();\n            }\n        });\n    }\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        switch (keyCode) {\n            case KeyEvent.KEYCODE_FOCUS:\n            case KeyEvent.KEYCODE_VOLUME_DOWN:\n                // Use the volume down button as focus button\n                if (!mIsFocusButtonDown) {\n                    mCamManager.doAutofocus(mFocusManager);\n                    mCamManager.setLockSetup(true);\n                    mIsFocusButtonDown = true;\n                }\n                return true;\n            case KeyEvent.KEYCODE_CAMERA:\n            case KeyEvent.KEYCODE_VOLUME_UP:\n                // Use the volume up button as shutter button (or snapshot button in video mode)\n                if (!mIsShutterButtonDown) {\n                    if (mCameraMode == CAMERA_MODE_VIDEO) {\n                        mSnapshotManager.queueSnapshot(true, 0);\n                    } else {\n                        mShutterButton.performClick();\n                    }\n                    mIsShutterButtonDown = true;\n                }\n                return true;\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @Override\n    public boolean onKeyUp(int keyCode, KeyEvent event) {\n        switch (keyCode) {\n            case KeyEvent.KEYCODE_FOCUS:\n            case KeyEvent.KEYCODE_VOLUME_DOWN:\n                mIsFocusButtonDown = false;\n                mCamManager.setLockSetup(false);\n                break;\n\n            case KeyEvent.KEYCODE_CAMERA:\n            case KeyEvent.KEYCODE_VOLUME_UP:\n                mIsShutterButtonDown = false;\n                break;\n        }\n\n        return super.onKeyUp(keyCode, event);\n    }\n\n    public CameraManager getCamManager() {\n        return mCamManager;\n    }\n\n    public SnapshotManager getSnapManager() {\n        return mSnapshotManager;\n    }\n\n    public PicSphereManager getPicSphereManager() {\n        return mPicSphereManager;\n    }\n\n    public ReviewDrawer getReviewDrawer() {\n        return mReviewDrawer;\n    }\n\n    public void initializePicSphere() {\n        // Check if device has a gyroscope and GLES2 support\n        // XXX: Should we make a fallback for super super old devices?\n        final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);\n        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();\n        final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;\n\n        if (!supportsEs2) {\n            mNotifier.notify(getString(R.string.no_gles20_support), 4000);\n            return;\n        }\n        // Close widgets and slide sidebar to make room and focus on the sphere\n        mSideBar.slideClose();\n        mWidgetRenderer.closeAllWidgets();\n\n        // Setup the 3D rendering\n        if (mPicSphereManager == null) {\n            mPicSphereManager = new PicSphereManager(this, mSnapshotManager);\n        }\n        setGLRenderer(mPicSphereManager.getRenderer());\n\n        // Setup the capture transformer\n        final PicSphereCaptureTransformer transformer =\n                new PicSphereCaptureTransformer(this);\n        setCaptureTransformer(transformer);\n\n        mPicSphereUndo.setVisibility(View.VISIBLE);\n        mPicSphereUndo.setAlpha(0.0f);\n        mPicSphereUndo.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                transformer.removeLastPicture();\n            }\n        });\n\n        // Notify how to start a sphere\n        setHelperText(getString(R.string.picsphere_start_hint));\n    }\n\n    /**\n     * Tear down the PicSphere mode and set the default renderer back on the preview\n     * GL surface.\n     */\n    public void resetPicSphere() {\n        // Reset the normal renderer\n        setGLRenderer(mCamManager.getRenderer());\n\n        // Tear down PicSphere capture system\n        if (mPicSphereManager != null) {\n            mPicSphereManager.tearDown();\n        }\n        setCaptureTransformer(null);\n\n        if (mPicSphereUndo != null) {\n            mPicSphereUndo.setVisibility(View.GONE);\n        }\n    }\n\n    /**\n     * Initializes the panorama (mosaic) subsystem\n     */\n    public void initializePanorama() {\n        mMosaicProxy = new MosaicProxy(this);\n        setCaptureTransformer(mMosaicProxy);\n        mCamManager.setRenderToTexture(null);\n        updateRingsVisibility();\n    }\n\n    /**\n     * Turns off the panorama (mosaic) subsystem\n     */\n    public void resetPanorama() {\n        if (mMosaicProxy != null) {\n            mMosaicProxy.tearDown();\n        }\n        setGLRenderer(mCamManager.getRenderer());\n    }\n\n    public void setGLRenderer(GLSurfaceView.Renderer renderer) {\n        final ViewGroup container = ((ViewGroup) findViewById(R.id.gl_renderer_container));\n        // Delete the previous GL Surface View (if any)\n        if (mGLSurfaceView != null) {\n            container.removeView(mGLSurfaceView);\n            mGLSurfaceView = null;\n        }\n\n        // Make a new GL view using the provided renderer\n        mGLSurfaceView = new GLSurfaceView(this);\n        mGLSurfaceView.setEGLContextClientVersion(2);\n        mGLSurfaceView.setRenderer(renderer);\n\n        container.addView(mGLSurfaceView);\n    }\n\n    /**\n     * Toggles the fullscreen shutter that lets user take pictures by tapping on the screen\n     */\n    public void toggleFullscreenShutter() {\n        if (mIsFullscreenShutter) {\n            mIsFullscreenShutter = false;\n            mShutterButton.animate().translationY(0).setDuration(400).start();\n        } else {\n            mIsFullscreenShutter = true;\n            mShutterButton.animate().translationY(mShutterButton.getHeight()).setDuration(400).start();\n            notify(getString(R.string.fullscreen_shutter_info), 2000);\n        }\n        updateRingsVisibility();\n    }\n\n    /**\n     * Show a persistent helper text that indicates the user a required action\n     * @param text The text to show, or empty/null to hide\n     */\n    public void setHelperText(final CharSequence text) {\n        setHelperText(text, false);\n    }\n\n    /**\n     * Show a persistent helper text that indicates the user a required action\n     * @param text The text to show, or empty/null to hide\n     * @param beware Show the text in red\n     */\n    public void setHelperText(final CharSequence text, final boolean beware) {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (text == null || text.equals(\"\")) {\n                    // Hide it\n                    Util.fadeOut(mHelperText);\n                } else {\n                    mHelperText.setText(text);\n                    if (beware) {\n                        mHelperText.setTextColor(getResources().getColor(R.color.clock_red));\n                    } else {\n                        mHelperText.setTextColor(0xFFFFFFFF);\n                    }\n                    Util.fadeIn(mHelperText);\n                }\n            }\n        });\n    }\n\n    public void setPicSphereUndoVisible(final boolean visible) {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (visible) {\n                    mPicSphereUndo.setVisibility(View.VISIBLE);\n                    mPicSphereUndo.setAlpha(1.0f);\n                } else {\n                    mPicSphereUndo.animate().alpha(0.0f).setDuration(200).start();\n                }\n            }\n        });\n    }\n\n    /**\n     * Recursively rotates the Views of ViewGroups\n     *\n     * @param vg       the root ViewGroup\n     * @param rotation the angle to which rotate the views\n     */\n    public static void setViewGroupRotation(ViewGroup vg, float rotation) {\n        final int childCount = vg.getChildCount();\n\n        for (int i = 0; i < childCount; i++) {\n            View child = vg.getChildAt(i);\n\n            if (child instanceof ViewGroup) {\n                setViewGroupRotation((ViewGroup) child, rotation);\n            } else {\n                setViewRotation(child, rotation);\n            }\n        }\n    }\n\n    public static void setViewRotation(View v, float rotation) {\n        v.animate().rotation(rotation).setDuration(200)\n                .setInterpolator(new DecelerateInterpolator()).start();\n    }\n\n    @Override\n    public void onShowcaseViewHide(ShowcaseView showcaseView) {\n        switch (mShowcaseIndex) {\n            case SHOWCASE_INDEX_WELCOME_1:\n                mShowcaseIndex = SHOWCASE_INDEX_WELCOME_2;\n\n                Point size = new Point();\n                getWindowManager().getDefaultDisplay().getSize(size);\n\n                ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions();\n                co.hideOnClickOutside = true;\n                mShowcaseView = ShowcaseView.insertShowcaseView(size.x / 2,\n                        size.y - Util.dpToPx(this, 16), this,\n                        getString(R.string.showcase_welcome_2_title),\n                        getString(R.string.showcase_welcome_2_body), co);\n\n                // animate gesture\n                mShowcaseView.animateGesture(size.x / 2,\n                        size.y - Util.dpToPx(this, 16), size.x / 2, size.y / 2);\n                mShowcaseView.setOnShowcaseEventListener(this);\n\n                // ping the button\n                mSwitchRingPad.animateHint();\n\n                break;\n        }\n    }\n\n    @Override\n    public void onShowcaseViewShow(ShowcaseView showcaseView) {\n        // Do nothing here\n    }\n\n    /**\n     * Listener that is called when the preview pauses or resumes\n     */\n    private class CameraPreviewListener implements CameraManager.PreviewPauseListener {\n        @Override\n        public void onPreviewPause() {\n            // XXX: Do a little animation\n        }\n\n        @Override\n        public void onPreviewResume() {\n            // XXX: Do a little animation\n        }\n    }\n\n    /**\n     * Listener that is called when a ring pad button is activated (finger release above)\n     */\n    private class MainRingPadListener implements SwitchRingPad.RingPadListener {\n        @Override\n        public void onButtonActivated(int eventId) {\n            switch (eventId) {\n                case SwitchRingPad.BUTTON_CAMERA:\n                    setCameraMode(CAMERA_MODE_PHOTO);\n                    break;\n                case SwitchRingPad.BUTTON_PANO:\n                    setCameraMode(CAMERA_MODE_PANO);\n                    break;\n                case SwitchRingPad.BUTTON_VIDEO:\n                    setCameraMode(CAMERA_MODE_VIDEO);\n                    break;\n                case SwitchRingPad.BUTTON_PICSPHERE:\n                    setCameraMode(CAMERA_MODE_PICSPHERE);\n                    break;\n                case SwitchRingPad.BUTTON_SWITCHCAM:\n                    mIsCamSwitching = true;\n                    if (mCamManager.getCurrentFacing() == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n                        mCamManager.open(Camera.CameraInfo.CAMERA_FACING_BACK);\n                    } else {\n                        mCamManager.open(Camera.CameraInfo.CAMERA_FACING_FRONT);\n                    }\n\n                    break;\n            }\n        }\n    }\n\n    /**\n     * Listener that is called when shutter button is slided, to open ring pad view\n     */\n    private class MainShutterSlideListener implements ShutterButton.ShutterSlideListener {\n        @Override\n        public void onSlideOpen() {\n            mSwitchRingPad.animateOpen();\n\n            // Tapping the shutter button locked exposure/WB, so we unlock it if we slide our finger\n            mCamManager.setLockSetup(false);\n\n            // Cancel long-press action\n            mIsShutterLongClicked = false;\n        }\n\n        @Override\n        public void onSlideClose() {\n            mSwitchRingPad.animateClose();\n        }\n\n        @Override\n        public boolean onMotionEvent(MotionEvent ev) {\n            return mSwitchRingPad.onTouchEvent(ev);\n        }\n\n        @Override\n        public void onShutterButtonPressed() {\n            // Animate the ring pad\n            mSwitchRingPad.animateHint();\n\n            // Make the review drawer super translucent if it is open\n            mReviewDrawer.setTemporaryHide(true);\n\n            // Lock automatic settings\n            mCamManager.setLockSetup(true);\n\n            // Turn on stabilization\n            mCamManager.setStabilization(true);\n        }\n    }\n\n    /**\n     * When the shutter button is pressed\n     */\n    public class MainShutterClickListener implements OnClickListener,\n            View.OnLongClickListener, View.OnTouchListener {\n\n\n        @Override\n        public void onClick(View v) {\n            mHandler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    mReviewDrawer.setTemporaryHide(false);\n                }\n            }, 500);\n\n            if (mSnapshotManager == null) return;\n\n            // If we have a capture transformer, apply it, otherwise use the default\n            // behavior.\n            if (mCaptureTransformer != null) {\n                mCaptureTransformer.onShutterButtonClicked(mShutterButton);\n            } else if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n                mSnapshotManager.queueSnapshot(true, 0);\n            } else if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n                if (!mSnapshotManager.isRecording()) {\n                    mSnapshotManager.startVideo();\n                    mShutterButton.setImageDrawable(getResources()\n                            .getDrawable(R.drawable.btn_shutter_stop));\n                } else {\n                    mSnapshotManager.stopVideo();\n                    mShutterButton.setImageDrawable(getResources()\n                            .getDrawable(R.drawable.btn_shutter_video));\n                }\n            } else {\n                Log.e(TAG, \"Unknown Camera Mode: \" + mCameraMode + \" ; No capture transformer\");\n            }\n        }\n\n        @Override\n        public boolean onLongClick(View view) {\n            if (mCaptureTransformer != null) {\n                mCaptureTransformer.onShutterButtonLongPressed(mShutterButton);\n            } else {\n                mIsShutterLongClicked = true;\n                if (mFocusManager != null) {\n                    mFocusManager.checkFocus();\n                }\n            }\n            return true;\n        }\n\n        @Override\n        public boolean onTouch(View view, MotionEvent motionEvent) {\n            // If we long-press the shutter button and no capture transformer handles it, we\n            // will just have nothing happening. We register the long click event in here, and\n            // trigger a snapshot once it's released.\n            if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP && mIsShutterLongClicked) {\n                mIsShutterLongClicked = false;\n                onClick(view);\n            }\n\n            return view.onTouchEvent(motionEvent);\n        }\n    }\n\n\n    /**\n     * Focus listener to animate the focus HUD ring from FocusManager events\n     */\n    private class MainFocusListener implements FocusManager.FocusListener {\n        @Override\n        public void onFocusStart(final boolean smallAdjust) {\n            mIsFocusing = true;\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mFocusHudRing.animateWorking(smallAdjust ? 200 : 1500);\n                }\n            });\n        }\n\n        @Override\n        public void onFocusReturns(final boolean smallAdjust, final boolean success) {\n            mIsFocusing = false;\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mFocusHudRing.animatePressUp();\n\n                    if (!smallAdjust) {\n                        mFocusHudRing.setFocusImage(success);\n                    } else {\n                        mFocusHudRing.setFocusImage(true);\n                    }\n                }\n            });\n        }\n    }\n\n    /**\n     * Snapshot listener for when snapshots are taken, in SnapshotManager\n     */\n    private class MainSnapshotListener implements SnapshotManager.SnapshotListener {\n        private long mRecordingStartTimestamp;\n        private TextView mTimerTv;\n        private boolean mIsRecording;\n\n        private Runnable mUpdateTimer = new Runnable() {\n            @Override\n            public void run() {\n                long recordingDurationMs = System.currentTimeMillis() - mRecordingStartTimestamp;\n                int minutes = (int) Math.floor(recordingDurationMs / 60000.0);\n                int seconds = (int) recordingDurationMs / 1000 - minutes * 60;\n\n                mTimerTv.setText(String.format(\"%02d:%02d\", minutes, seconds));\n\n                // Loop infinitely until recording stops\n                if (mIsRecording) {\n                    mHandler.postDelayed(this, 500);\n                }\n            }\n        };\n\n        @Override\n        public void onSnapshotShutter(final SnapshotManager.SnapshotInfo info) {\n            final FrameLayout layout = (FrameLayout) findViewById(R.id.thumb_flinger_container);\n\n            // Fling the preview\n            final ThumbnailFlinger flinger = new ThumbnailFlinger(CameraActivity.this);\n            mHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    layout.addView(flinger);\n                    flinger.setRotation(90);\n                    flinger.setImageBitmap(info.mThumbnail);\n                    flinger.doAnimation();\n                }\n            });\n\n            // Unlock camera auto settings\n            mCamManager.setLockSetup(false);\n            mCamManager.setStabilization(false);\n        }\n\n        @Override\n        public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n            // Do nothing here\n        }\n\n        @Override\n        public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n            runOnUiThread(new Runnable() {\n                public void run() {\n                    if (mSavePinger != null) {\n                        mSavePinger.setPingMode(SavePinger.PING_MODE_ENHANCER);\n                        mSavePinger.startSaving();\n                    }\n                }\n            });\n        }\n\n        @Override\n        public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n            String uriStr = info.mUri.toString();\n\n            // Add the new image to the gallery and the review drawer\n            int originalImageId = Integer.parseInt(uriStr.substring(uriStr\n                    .lastIndexOf(\"/\") + 1, uriStr.length()));\n            Log.v(TAG, \"Adding snapshot to gallery: \" + originalImageId);\n            mReviewDrawer.addImageToList(originalImageId);\n            mReviewDrawer.scrollToLatestImage();\n        }\n\n        @Override\n        public void onMediaSavingStart() {\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mSavePinger.setPingMode(SavePinger.PING_MODE_SAVE);\n                    mSavePinger.startSaving();\n                }\n            });\n        }\n\n        @Override\n        public void onMediaSavingDone() {\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mSavePinger.stopSaving();\n                }\n            });\n        }\n\n        @Override\n        public void onVideoRecordingStart() {\n            mTimerTv = (TextView) findViewById(R.id.recording_timer_text);\n            mRecordingStartTimestamp = System.currentTimeMillis();\n            mIsRecording = true;\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mHandler.post(mUpdateTimer);\n                    mRecTimerContainer.setVisibility(View.VISIBLE);\n                }\n            });\n        }\n\n        @Override\n        public void onVideoRecordingStop() {\n            mIsRecording = false;\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    mRecTimerContainer.setVisibility(View.GONE);\n                }\n            });\n        }\n    }\n\n    /**\n     * Handles the orientation changes without turning the actual activity\n     */\n    private class CameraOrientationEventListener extends OrientationEventListener {\n        public CameraOrientationEventListener(Context context) {\n            super(context);\n        }\n\n        @Override\n        public void onOrientationChanged(int orientation) {\n            // We keep the last known orientation. So if the user first orient\n            // the camera then point the camera to floor or sky, we still have\n            // the correct orientation.\n            if (orientation == ORIENTATION_UNKNOWN) {\n                return;\n            }\n            mOrientation = Util.roundOrientation(orientation, mOrientation);\n\n            // Notify camera of the raw orientation\n            mCamManager.setOrientation(mOrientation);\n\n            // Adjust orientationCompensation for the native orientation of the device.\n            Configuration config = getResources().getConfiguration();\n            int rotation = getWindowManager().getDefaultDisplay().getRotation();\n            Util.getDisplayRotation(CameraActivity.this);\n\n            boolean nativeLandscape = false;\n\n            if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)\n                    && config.orientation == Configuration.ORIENTATION_LANDSCAPE)\n                    || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270)\n                    && config.orientation == Configuration.ORIENTATION_PORTRAIT)) {\n                nativeLandscape = true;\n            }\n\n            int orientationCompensation = mOrientation; // + (nativeLandscape ? 0 : 90);\n            if (orientationCompensation == 90) {\n                orientationCompensation += 180;\n            } else if (orientationCompensation == 270) {\n                orientationCompensation -= 180;\n            }\n\n            // Avoid turning all around\n            float angleDelta = orientationCompensation - mOrientationCompensation;\n            if (angleDelta >= 270) {\n                orientationCompensation -= 360;\n            }\n\n            if (mOrientationCompensation != orientationCompensation) {\n                mOrientationCompensation = orientationCompensation;\n                updateInterfaceOrientation();\n            }\n        }\n    }\n\n    /**\n     * Handles the swipe and tap gestures on the lower layer of the screen\n     * (ie. the preview surface)\n     *\n     * @note Remember that the default orientation of the screen is landscape, thus\n     * the side bar is at the BOTTOM of the screen, and is swiped UP/DOWN.\n     */\n    public class GestureListener extends GestureDetector.SimpleOnGestureListener {\n        private static final int SWIPE_MIN_DISTANCE = 10;\n        private final float DRAG_MIN_DISTANCE = Util.dpToPx(CameraActivity.this, 5.0f);\n        private static final int SWIPE_MAX_OFF_PATH = 80;\n        private static final int SWIPE_THRESHOLD_VELOCITY = 800;\n\n        // Allow to drag the side bar up to half of the screen\n        private static final int SIDEBAR_THRESHOLD_FACTOR = 2;\n\n        private boolean mCancelSwipe = false;\n\n        @Override\n        public boolean onSingleTapConfirmed(MotionEvent e) {\n            if (mPaused) return false;\n\n            // A single tap equals to touch-to-focus in photo/video\n            if ((mCameraMode == CAMERA_MODE_PHOTO && !mIsFullscreenShutter)\n                    || mCameraMode == CAMERA_MODE_VIDEO) {\n                if (mFocusManager != null) {\n                    mFocusHudRing.setPosition(e.getRawX(), e.getRawY());\n                    mFocusManager.refocus();\n                }\n            } else if (mCameraMode == CAMERA_MODE_PHOTO && mIsFullscreenShutter) {\n                // We are in fullscreen shutter mode, so just take a picture\n                mSnapshotManager.queueSnapshot(true, 0);\n            }\n\n            return super.onSingleTapConfirmed(e);\n        }\n\n        @Override\n        public boolean onDoubleTap(MotionEvent e) {\n            // In VIDEO mode, a double tap snapshots (or volume up)\n            if (mCameraMode == CAMERA_MODE_VIDEO) {\n                mSnapshotManager.queueSnapshot(true, 0);\n            } else if (mCameraMode == CAMERA_MODE_PHOTO) {\n                // Toggle fullscreen shutter\n                toggleFullscreenShutter();\n            }\n\n            return super.onDoubleTap(e);\n        }\n\n        @Override\n        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\n            if (e1 == null || e2 == null) {\n                return false;\n            }\n\n            // Detect drag of the side bar or review drawer\n            if (Math.abs(e1.getY() - e2.getY()) < SWIPE_MAX_OFF_PATH) {\n                if (e1.getRawX() < Util.getScreenSize(CameraActivity.this)\n                        .x / SIDEBAR_THRESHOLD_FACTOR) {\n                    if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE ||\n                            e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE) {\n                        mSideBar.slide(-distanceX);\n                        mWidgetRenderer.notifySidebarSlideStatus(-distanceX);\n                        mCancelSwipe = true;\n                        mCancelSideBarClose = true;\n                    }\n\n                    return true;\n                }\n            } else if (Math.abs(e1.getY() - e2.getY()) > DRAG_MIN_DISTANCE) {\n                mReviewDrawer.slide(-distanceY);\n            }\n\n            return true;\n        }\n\n        @Override\n        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n            try {\n                if (Math.abs(e1.getY() - e2.getY()) < SWIPE_MAX_OFF_PATH) {\n                    // swipes to open/close the sidebar and/or hide/restore the widgets\n                    if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE\n                            && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {\n                        if (mWidgetRenderer.isHidden() && mWidgetRenderer.getWidgetsCount() > 0) {\n                            mWidgetRenderer.restoreWidgets();\n                        } else {\n                            mSideBar.slideOpen();\n                            mWidgetRenderer.notifySidebarSlideOpen();\n                            mCancelSideBarClose = true;\n                        }\n                    } else if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE\n                            && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {\n                        if (mSideBar.isOpen()) {\n                            mSideBar.slideClose();\n                            mWidgetRenderer.notifySidebarSlideClose();\n                            mCancelSideBarClose = true;\n                        } else if (!mWidgetRenderer.isHidden()\n                                && mWidgetRenderer.getWidgetsCount() > 0\n                                && !mCancelSwipe) {\n                            mWidgetRenderer.hideWidgets();\n                        }\n                    }\n                }\n\n                if (Math.abs(e1.getX() - e2.getX()) < SWIPE_MAX_OFF_PATH) {\n                    // swipes up/down to open/close the review drawer\n                    if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE\n                            && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {\n                        mReviewDrawer.close();\n                    } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE\n                            && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {\n                        mReviewDrawer.open();\n                    }\n                }\n            } catch (Exception e) {\n                // Do nothing here\n            }\n\n            mCancelSwipe = false;\n            return true;\n        }\n    }\n\n\n    /**\n     * Handles the pinch-to-zoom gesture\n     */\n    private class ZoomGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {\n        @Override\n        public boolean onScale(ScaleGestureDetector detector) {\n            Camera.Parameters params = mCamManager.getParameters();\n\n            if (params == null) return false;\n\n            if (!mIsFocusing) {\n                if (detector.getScaleFactor() > 1.0f) {\n                    params.setZoom(Math.min(params.getZoom() + 1, params.getMaxZoom()));\n                } else if (detector.getScaleFactor() < 1.0f) {\n                    params.setZoom(Math.max(params.getZoom() - 1, 0));\n                } else {\n                    return false;\n                }\n\n                mHasPinchZoomed = true;\n                mCamManager.setParameters(params);\n            }\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/CameraApplication.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.app.Application;\nimport android.util.Log;\n\n/**\n * Manages the application itself (on top of the activity), mainly to force Camera getting\n * closed in case of crash.\n */\npublic class CameraApplication extends Application {\n    private final static String TAG = \"FocalApp\";\n    private Thread.UncaughtExceptionHandler mDefaultExHandler;\n    private CameraManager mCamManager;\n\n    private Thread.UncaughtExceptionHandler mExHandler = new Thread.UncaughtExceptionHandler() {\n        public void uncaughtException(Thread thread, Throwable ex) {\n            if (mCamManager != null) {\n                Log.e(TAG, \"Uncaught exception! Closing down camera safely firsthand\");\n                mCamManager.forceCloseCamera();\n            }\n\n            mDefaultExHandler.uncaughtException(thread, ex);\n        }\n    };\n\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        mDefaultExHandler = Thread.getDefaultUncaughtExceptionHandler();\n        Thread.setDefaultUncaughtExceptionHandler(mExHandler);\n    }\n\n    public void setCameraManager(CameraManager camMan) {\n        mCamManager = camMan;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/CameraButtonIntentReceiver.java",
    "content": "/*\n * Copyright (C) 2007 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\n/**\n * {@code CameraButtonIntentReceiver} is invoked when the camera button is\n * long-pressed.\n *\n * It is declared in {@code AndroidManifest.xml} to receive the\n * {@code android.intent.action.CAMERA_BUTTON} intent.\n *\n */\npublic class CameraButtonIntentReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        Intent i = new Intent(Intent.ACTION_MAIN);\n        i.setClass(context, CameraActivity.class);\n        i.addCategory(Intent.CATEGORY_LAUNCHER);\n        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK\n                | Intent.FLAG_ACTIVITY_CLEAR_TOP);\n        context.startActivity(i);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/CameraCapabilities.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.hardware.Camera;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.cyanogenmod.focal.widgets.*;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This class holds all the possible widgets of the\n * sidebar. It checks for support prior to adding them\n * effectively in the sidebar.\n */\npublic class CameraCapabilities {\n    private List<WidgetBase> mWidgets;\n\n    /**\n     * Default constructor, initializes all the widgets. They will\n     * then be sorted by populateSidebar.\n     *\n     * @param context The CameraActivity context\n     */\n    public CameraCapabilities(CameraActivity context) {\n        mWidgets = new ArrayList<WidgetBase>();\n        CameraManager cam = context.getCamManager();\n\n        // Populate the list of widgets.\n        // Basically, if we add a new widget, we just put it here.\n        // They will populate the sidebar in the same order as here.\n        mWidgets.add(new FlashWidget(cam, context));\n        mWidgets.add(new WhiteBalanceWidget(cam, context));\n        mWidgets.add(new SceneModeWidget(cam, context));\n        mWidgets.add(new HdrWidget(cam, context));\n        mWidgets.add(new SoftwareHdrWidget(context));\n        mWidgets.add(new VideoHdrWidget(cam, context));\n        mWidgets.add(new EffectWidget(cam, context));\n        mWidgets.add(new ExposureCompensationWidget(cam, context));\n        mWidgets.add(new EnhancementsWidget(cam, context));\n        mWidgets.add(new AutoExposureWidget(cam, context));\n        mWidgets.add(new IsoWidget(cam, context));\n        mWidgets.add(new ShutterSpeedWidget(cam, context));\n        mWidgets.add(new BurstModeWidget(context));\n        mWidgets.add(new TimerModeWidget(context));\n        mWidgets.add(new VideoFrWidget(cam, context));\n        mWidgets.add(new SettingsWidget(context, this));\n    }\n\n    /**\n     * @return The list of currently enabled/capable widgets\n     */\n    public List<WidgetBase> getWidgets() {\n        return mWidgets;\n    }\n\n    /**\n     * Populates the sidebar (through sideBarContainer) with the widgets actually\n     * compatible with the device.\n     *\n     * @param params           The Camera parameters returned from the HAL for compatibility check\n     * @param sideBarContainer The side bar layout that will contain all the toggle buttons\n     * @param homeContainer    The viewgroup containing home shortcuts\n     * @param widgetsContainer The container of the final rendered widgets\n     */\n    public void populateSidebar(Camera.Parameters params, ViewGroup sideBarContainer,\n                                ViewGroup homeContainer, ViewGroup widgetsContainer) {\n        List<WidgetBase> unsupported = new ArrayList<WidgetBase>();\n\n        for (int i = 0; i < mWidgets.size(); i++) {\n            final WidgetBase widget = mWidgets.get(i);\n\n            // Add the widget to the sidebar if it is supported by the device.\n            // The compatibility is determined by widgets themselves.\n            if (widget.isSupported(params)) {\n                widgetsContainer.addView(widget.getWidget());\n                homeContainer.addView(widget.getShortcutButton());\n                sideBarContainer.addView(widget.getToggleButton());\n\n                // If the widget is pinned, show it on the main screen, otherwise in the bar\n                if (SettingsStorage.getShortcutSetting(widget.getWidget().getContext(),\n                        widget.getClass().getCanonicalName())) {\n                    widget.getShortcutButton().setVisibility(View.VISIBLE);\n                    widget.getToggleButton().setVisibility(View.GONE);\n                } else {\n                    widget.getShortcutButton().setVisibility(View.GONE);\n                    widget.getToggleButton().setVisibility(View.VISIBLE);\n                }\n            } else {\n                unsupported.add(widget);\n            }\n        }\n\n        for (int i = 0; i < unsupported.size(); i++) {\n            mWidgets.remove(unsupported.get(i));\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/CameraManager.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Matrix;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.graphics.SurfaceTexture;\nimport android.hardware.Camera;\nimport android.hardware.Camera.AutoFocusCallback;\nimport android.hardware.Camera.AutoFocusMoveCallback;\nimport android.media.CamcorderProfile;\nimport android.media.MediaRecorder;\nimport android.opengl.GLES11Ext;\nimport android.opengl.GLES20;\nimport android.opengl.GLSurfaceView;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.util.Log;\n\nimport org.apache.http.NameValuePair;\nimport org.apache.http.message.BasicNameValuePair;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.FloatBuffer;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.StringTokenizer;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\nimport fr.xplod.focal.R;\n\n/**\n * This class is responsible for interacting with the Camera HAL.\n * It provides easy open/close, helper methods to set parameters or\n * toggle features, etc. in an asynchronous fashion.\n */\npublic class CameraManager {\n    private final static String TAG = \"CameraManager\";\n\n    private final static int FOCUS_WIDTH = 80;\n    private final static int FOCUS_HEIGHT = 80;\n\n    private final static boolean DEBUG_LOG_PARAMS = false;\n    private final static boolean DEBUG_PROFILER = false;\n\n    private CameraPreview mPreview;\n    private Camera mCamera;\n    private boolean mCameraReady;\n    private int mCurrentFacing;\n    private Point mTargetSize;\n    private AutoFocusMoveCallback mAutoFocusMoveCallback;\n    private Camera.Parameters mParameters;\n    private int mOrientation;\n    private MediaRecorder mMediaRecorder;\n    private PreviewPauseListener mPreviewPauseListener;\n    private CameraReadyListener mCameraReadyListener;\n    private Handler mHandler;\n    private Activity mContext;\n    private boolean mIsModeSwitching;\n    private List<NameValuePair> mPendingParameters;\n    private boolean mIsResuming;\n    private CameraRenderer mRenderer;\n    private boolean mIsRecordingHint;\n    private boolean mIsPreviewStarted;\n    private boolean mParametersBatch;\n\n    public interface PreviewPauseListener {\n        /**\n         * This method is called when the preview is about to pause.\n         * This allows the CameraActivity to display an animation when the preview\n         * has to stop.\n         */\n        public void onPreviewPause();\n\n        /**\n         * This method is called when the preview resumes\n         */\n        public void onPreviewResume();\n    }\n\n    public interface CameraReadyListener {\n        /**\n         * Called when a camera has been successfully opened. This allows the\n         * main activity to continue setup operations while the camera\n         * sets up in a different thread.\n         */\n        public void onCameraReady();\n\n        /**\n         * Called when the camera failed to initialize\n         */\n        public void onCameraFailed();\n    }\n\n    private class ParametersThread extends Thread {\n        public void run() {\n            while (true) {\n                synchronized (this) {\n                    try {\n                        wait();\n                    } catch (InterruptedException e) {\n                        // Do nothing here\n                        return;\n                    }\n\n                    Log.v(TAG, \"Batch parameter setting starting.\");\n\n                    String existingParameters = getParameters().flatten();\n\n                    // If the camera died, just forget about this.\n                    if (existingParameters == null) continue;\n\n                    List<NameValuePair> copy = new ArrayList<NameValuePair>(mPendingParameters);\n                    mPendingParameters.clear();\n\n                    Camera.Parameters params = getParameters();\n\n                    for (NameValuePair pair : copy) {\n                        String key = pair.getName();\n                        String val = pair.getValue();\n                        Log.v(TAG, \"Setting parameter \" + key+ \" to \" + val);\n\n                        params.set(key, val);\n                    }\n\n\n                    try {\n                        mCamera.setParameters(params);\n                    } catch (RuntimeException e) {\n                        Log.e(TAG, \"Could not set parameters batch\", e);\n                    }\n\n                    // Read them from sensor\n                    mParameters = null;// getParameters();\n                }\n            }\n        }\n    }\n\n    private ParametersThread mParametersThread = null;\n    final Object mParametersSync = new Object();\n\n    public CameraManager(Activity context) {\n        mPreview = new CameraPreview();\n        mMediaRecorder = new MediaRecorder();\n        mCameraReady = true;\n        mHandler = new Handler();\n        mIsModeSwitching = false;\n        mContext = context;\n        mPendingParameters = new ArrayList<NameValuePair>();\n        mParametersThread = new ParametersThread();\n        mParametersThread.start();\n        mIsResuming = false;\n        mIsRecordingHint = false;\n        mRenderer = new CameraRenderer();\n        mIsPreviewStarted = false;\n    }\n\n    /**\n     * Opens the camera and show its preview in the preview\n     *\n     * @param cameraId The facing of the camera\n     * @return true if the operation succeeded, false otherwise\n     */\n    public boolean open(final int cameraId) {\n        if (mCamera != null) {\n            if (mPreviewPauseListener != null) {\n                mPreviewPauseListener.onPreviewPause();\n            }\n\n            // Close the previous camera\n            releaseCamera();\n        }\n\n        mCameraReady = false;\n\n        // Try to open the camera\n        new Thread() {\n            public void run() {\n                try {\n                    if (DEBUG_PROFILER) Profiler.getDefault().start(\"CameraOpen\");\n                    if (mCamera != null) {\n                        Log.e(TAG, \"Previous camera not closed! Not opening\");\n                        return;\n                    }\n\n                    mCamera = Camera.open(cameraId);\n                    Log.v(TAG, \"Camera is open\");\n\n                    if (Build.VERSION.SDK_INT >= 17) {\n                        mCamera.enableShutterSound(false);\n                    }\n                    mCamera.setPreviewCallback(mPreview);\n                    mCurrentFacing = cameraId;\n                    mParameters = mCamera.getParameters();\n\n                    if (DEBUG_LOG_PARAMS) {\n                        String params = mCamera.getParameters().flatten();\n                        final int step = params.length() > 256 ? 256 : params.length();\n                        for (int i = 0; i < params.length(); i += step) {\n                            Log.d(TAG, params);\n                            params = params.substring(step);\n                        }\n                    }\n\n                    // Mako hack to raise FPS\n                    if (Build.DEVICE.equals(\"mako\")) {\n                        Camera.Size maxSize = mParameters.getSupportedPictureSizes().get(0);\n                        mParameters.setPictureSize(maxSize.width, maxSize.height);\n                    }\n\n                    if (mAutoFocusMoveCallback != null) {\n                        setAutoFocusMoveCallback(mAutoFocusMoveCallback);\n                    }\n                } catch (Exception e) {\n                    Log.e(TAG, \"Error while opening cameras: \" + e.getMessage(), e);\n\n                    if (mCameraReadyListener != null) {\n                        mCameraReadyListener.onCameraFailed();\n                    }\n\n                    return;\n                }\n\n                // Update the preview surface holder with the new opened camera\n                mPreview.notifyCameraChanged(false);\n\n                if (mCameraReadyListener != null) {\n                    mCameraReadyListener.onCameraReady();\n                }\n                if (mPreviewPauseListener != null) {\n                    mPreviewPauseListener.onPreviewResume();\n                }\n\n                mPreview.setPauseCopyFrame(false);\n\n                mCameraReady = true;\n                if (DEBUG_PROFILER) Profiler.getDefault().logProfile(\"CameraOpen\");\n            }\n        }.start();\n\n        return true;\n    }\n\n    public void setPreviewPauseListener(PreviewPauseListener listener) {\n        mPreviewPauseListener = listener;\n    }\n\n    public void setCameraReadyListener(CameraReadyListener listener) {\n        mCameraReadyListener = listener;\n    }\n\n    /**\n     * Returns the preview surface used to display the Camera's preview\n     *\n     * @return CameraPreview\n     */\n    public CameraPreview getPreviewSurface() {\n        return mPreview;\n    }\n\n    /**\n     * @return The GLES20-compatible renderer for the camera preview\n     */\n    public CameraRenderer getRenderer() {\n        return mRenderer;\n    }\n\n    /**\n     * @return The facing of the current open camera\n     */\n    public int getCurrentFacing() {\n        return mCurrentFacing;\n    }\n\n    /**\n     * Returns the parameters structure of the current running camera\n     *\n     * @return Camera.Parameters\n     */\n    public Camera.Parameters getParameters() {\n        synchronized (mParametersSync) {\n            if (mCamera == null) {\n                Log.w(TAG, \"getParameters when camera is null\");\n                return null;\n            }\n\n            int tries = 0;\n            while (mParameters == null) {\n                try {\n                    mParameters = mCamera.getParameters();\n                    break;\n                } catch (RuntimeException e) {\n                    Log.e(TAG, \"Error while getting parameters: \", e);\n                    if (tries < 3) {\n                        tries++;\n                        try {\n                            Thread.sleep(100);\n                        } catch (InterruptedException e1) {\n                            e1.printStackTrace();\n                        }\n                    } else {\n                        Log.e(TAG, \"Failed to get parameters after 3 tries\");\n                        break;\n                    }\n                }\n            }\n        }\n\n        return mParameters;\n    }\n\n    public void pause() {\n        mPreview.setPauseCopyFrame(true);\n        releaseCamera();\n        mParametersThread.interrupt();\n        mParametersThread = null;\n    }\n\n    public void resume() {\n        mIsResuming = true;\n        reconnectToCamera();\n        mParametersThread = new ParametersThread();\n        mParametersThread.start();\n    }\n\n    /**\n     * Used by CameraApplication safeguard to release the camera when the app crashes.\n     */\n    public void forceCloseCamera() {\n        if (mCamera != null) {\n            try {\n                mCamera.release();\n                mCamera = null;\n                mParameters = null;\n            } catch (Exception e) {\n                // Do nothing\n            }\n        }\n    }\n\n    private void releaseCamera() {\n        if (mCamera != null && mCameraReady) {\n            Log.v(TAG, \"Releasing camera facing \" + mCurrentFacing);\n            mCamera.release();\n            mCamera = null;\n            mParameters = null;\n            mPreview.notifyCameraChanged(false);\n            mCameraReady = true;\n        }\n    }\n\n    public void reconnectToCamera() {\n        if (mCameraReady) {\n            open(mCurrentFacing);\n        } else {\n            Log.e(TAG, \"reconnectToCamera but camera not ready!\");\n        }\n    }\n    \n    public void setVideoSize(int width, int height){\n        Log.v(TAG, \"setVideoSize \" + width + \"x\" + height);\n        Camera.Parameters params = getParameters();\n        params.set(\"video-size\", \"\" + width +\"x\" + height);\n        // TODO: maybe need to set picture-size here too for\n        // video snapshots\n        \n        List<Camera.Size> sizes = params.getSupportedPreviewSizes();\n        // TODO: support of preferred preview size\n        // this is currently breaking camera if preview\n        // size != video-size\n        Camera.Size preferred = params.getPreferredPreviewSizeForVideo();\n        if (preferred == null) {\n            preferred = sizes.get(0);\n        }\n        \n        Camera.Size optimalPreview = null;\n        int product = preferred.width * preferred.height;\n        Iterator<Camera.Size> it = sizes.iterator();\n        // Remove the preview sizes that are not preferred.\n        while (it.hasNext()) {\n            Camera.Size size = it.next();\n            if (size.width * size.height > product) {\n                it.remove();\n                continue;   \n            }\n            // TODO: workaround for now to choose same size then video\n            if (size.width == width && size.height == height){\n                optimalPreview = size;\n                break;\n            }\n        }\n\n        if (optimalPreview == null){\n            // TODO: support of preview size different to video size\n            // right now this is crashing e.g. oppo has an preferred\n            // video preview of 1920x1080\n            optimalPreview = Util.getOptimalPreviewSize((Activity) mContext, sizes,\n                        (double) width / height);\n        }\n        setPreviewSize(optimalPreview.width, optimalPreview.height);\n    }\n    \n    public void setPreviewSize(int width, int height) {\n        mTargetSize = new Point(width, height);\n\n        if (mCamera != null) {\n            Camera.Parameters params = getParameters();\n            params.setPreviewSize(width, height);\n\n            Log.v(TAG, \"Preview size is \" + width + \"x\" + height);\n\n            if (!mIsModeSwitching) {\n                synchronized (mParametersSync) {\n                    try {\n                        safeStopPreview();\n                        mParameters = params;\n                        mCamera.setParameters(mParameters);\n                        // TODO: preview aspect ratio is wrong in video mode\n                        mPreview.notifyPreviewSize(width, height);\n\n                        // TODO: why dont restart preview here?\n                        // setPreviewSize is called on video mode switching too\n                        //if (mIsResuming) {\n                            updateDisplayOrientation();\n                            safeStartPreview();\n                            //mIsResuming = false;\n                        //}\n\n                        mPreview.setPauseCopyFrame(false);\n                    } catch (RuntimeException ex) {\n                        Log.e(TAG, \"Unable to set Preview Size\", ex);\n                    }\n                }\n\n                Log.d(TAG, \"setPreviewSize - stop\");\n            }\n        }\n    }\n\n    private void safeStartPreview() {\n        if (!mIsPreviewStarted && mCamera != null) {\n            Log.d(TAG, \"safeStartPreview\");\n            mCamera.startPreview();\n            mIsPreviewStarted = true;\n        }\n    }\n\n    private void safeStopPreview() {\n        if (mIsPreviewStarted && mCamera != null) {\n            Log.d(TAG, \"safeStopPreview\");\n            mCamera.stopPreview();\n            mIsPreviewStarted = false;\n        }\n    }\n\n    public void startParametersBatch() {\n        mParametersBatch = true;\n    }\n\n    public void stopParametersBatch() {\n        mParametersBatch = false;\n        if (mParametersThread == null) return;\n        synchronized (mParametersThread) {\n            mParametersThread.notifyAll();\n        }\n    }\n\n    public void setParameterAsync(String key, String value) {\n        if (mParametersThread == null) return;\n\n        synchronized (mParametersThread) {\n            mPendingParameters.add(new BasicNameValuePair(key, value));\n            if (!mParametersBatch) {\n                mParametersThread.notifyAll();\n            }\n        }\n    }\n\n    /**\n     * Sets a parameters class in a synchronous way. Use with caution, prefer setParameterAsync.\n     * @param params Parameters\n     */\n    public void setParameters(Camera.Parameters params) {\n        synchronized (mParametersSync) {\n            mCamera.setParameters(params);\n        }\n    }\n\n    /**\n     * Locks the automatic settings of the camera device, like White balance and\n     * exposure.\n     *\n     * @param lock true to lock, false to unlock\n     */\n    public void setLockSetup(boolean lock) {\n        final Camera.Parameters params = getParameters();\n\n        if (params == null) {\n            // Params might be null if we pressed or swipe the shutter button\n            // while the camera is not ready\n            return;\n        }\n\n        if (params.isAutoExposureLockSupported()) {\n            params.setAutoExposureLock(lock);\n        }\n\n        if (params.isAutoWhiteBalanceLockSupported()) {\n            params.setAutoWhiteBalanceLock(lock);\n        }\n\n        new Thread() {\n            public void run() {\n                synchronized (mParametersSync) {\n                    try {\n                        mCamera.setParameters(params);\n                    } catch (RuntimeException e) {\n                        // Do nothing here\n                    }\n                }\n            }\n        }.start();\n    }\n\n    /**\n     * Returns the last frame of the preview surface\n     *\n     * @return Bitmap\n     */\n    public Bitmap getLastPreviewFrame() {\n        // Decode the last frame bytes\n        byte[] data = mPreview.getLastFrameBytes();\n        Camera.Parameters params = getParameters();\n\n        if (params == null) {\n            return null;\n        }\n\n        Camera.Size previewSize = params.getPreviewSize();\n        if (previewSize == null) {\n            return null;\n        }\n\n        int previewWidth = previewSize.width;\n        int previewHeight = previewSize.height;\n\n        // Convert YUV420SP preview data to RGB\n        try {\n            if (data != null && data.length > 8) {\n                Bitmap bitmap = Util.decodeYUV420SP(mContext, data, previewWidth, previewHeight);\n                if (mCurrentFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n                    // Frontcam has the image flipped, flip it back to not look weird in portrait\n                    Matrix m = new Matrix();\n                    m.preScale(-1, 1);\n                    Bitmap dst = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),\n                            bitmap.getHeight(), m, false);\n                    bitmap.recycle();\n                    bitmap = dst;\n                }\n\n                return bitmap;\n            } else {\n                return null;\n            }\n        } catch (ArrayIndexOutOfBoundsException e) {\n            // TODO: FIXME: On some devices, the resolution of the preview might abruptly change,\n            // thus the YUV420SP data is not the size we expect, causing OOB exception\n            return null;\n        }\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    /**\n     * Defines a new size for the recorded picture\n     * @param sz The size string in format widthxheight\n     */\n    public void setPictureSize(String sz) {\n        String[] splat = sz.split(\"x\");\n        int width = Integer.parseInt(splat[0]);\n        int height = Integer.parseInt(splat[1]);\n\n        Log.v(TAG, \"setPictureSize \" + width + \"x\" + height);\n        Camera.Parameters params = getParameters();\n        params.setPictureSize(width, height);\n        \n        // set optimal preview - needs preview restart\n        Camera.Size optimalPreview = Util.getOptimalPreviewSize(mContext, params.getSupportedPreviewSizes(),\n            ((float) width / (float) height));\n        setPreviewSize(optimalPreview.width, optimalPreview.height);\n    }\n\n    /**\n     * Takes a snapshot\n     */\n    public void takeSnapshot(final Camera.ShutterCallback shutterCallback,\n                             final Camera.PictureCallback raw, final Camera.PictureCallback jpeg) {\n        Log.v(TAG, \"takePicture\");\n        if (Util.deviceNeedsStopPreviewToShoot()) {\n            safeStopPreview();\n        }\n\n        SoundManager.getSingleton().play(SoundManager.SOUND_SHUTTER);\n\n        if (mCamera != null) {\n            new Thread() {\n                public void run() {\n                    try {\n                        mCamera.takePicture(shutterCallback, raw, jpeg);\n                    } catch (RuntimeException e) {\n                        Log.e(TAG, \"Unable to take picture\", e);\n                        CameraActivity.notify(\"Unable to take picture\", 1000);\n                    }\n                }\n            }.start();\n        }\n    }\n\n    /**\n     * Prepares the MediaRecorder to record a video. This must be called before\n     * startVideoRecording to setup the recording environment.\n     *\n     * @param fileName Target file path\n     * @param profile  Target profile (quality)\n     */\n    public void prepareVideoRecording(String fileName, CamcorderProfile profile) {\n        // Unlock the camera for use with MediaRecorder\n        mCamera.unlock();\n\n        mMediaRecorder.setCamera(mCamera);\n        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);\n        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);\n\n\n        mMediaRecorder.setProfile(profile);\n        mMediaRecorder.setOutputFile(fileName);\n        // Set maximum file size.\n        long maxFileSize = Storage.getStorage().getAvailableSpace()\n                - Storage.LOW_STORAGE_THRESHOLD;\n        mMediaRecorder.setMaxFileSize(maxFileSize);\n        mMediaRecorder.setMaxDuration(0); // infinite\n\n        try {\n            mMediaRecorder.prepare();\n        } catch (IllegalStateException e) {\n            Log.e(TAG, \"Cannot prepare MediaRecorder\", e);\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot prepare MediaRecorder\", e);\n        }\n\n        mPreview.postCallbackBuffer();\n    }\n\n    public void startVideoRecording() {\n        Log.v(TAG, \"startVideoRecording\");\n        try {\n            mMediaRecorder.start();\n        } catch (Exception e) {\n            Log.e(TAG, \"Unable to start recording\", e);\n            CameraActivity.notify(\"Error while starting recording\", 1000);\n        }\n        mPreview.postCallbackBuffer();\n    }\n\n    public void stopVideoRecording() {\n        Log.v(TAG, \"stopVideoRecording\");\n        try {\n            mMediaRecorder.stop();\n        } catch (Exception e) {\n            Log.e(TAG, \"Cannot stop MediaRecorder\", e);\n        }\n        mCamera.lock();\n        mMediaRecorder.reset();\n        mPreview.postCallbackBuffer();\n    }\n\n    /**\n     * @return The orientation of the device\n     */\n    public int getOrientation() {\n        return mOrientation;\n    }\n\n    /**\n     * Sets the current orientation of the device\n     * @param orientation The orientation, in degrees\n     */\n    public void setOrientation(int orientation) {\n        orientation += 90;\n        if (mOrientation == orientation) return;\n\n        mOrientation = orientation;\n\n        // Rotate the pictures accordingly (display is kept at 90 degrees)\n        Camera.CameraInfo info =\n                new android.hardware.Camera.CameraInfo();\n        Camera.getCameraInfo(mCurrentFacing, info);\n        //orientation = (360 - orientation + 45) / 90 * 90;\n        int rotation = 0;\n        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n            rotation = (info.orientation - orientation + 360) % 360;\n        } else {  // back-facing camera\n            rotation = (info.orientation + orientation) % 360;\n        }\n\n        //setParameterAsync(\"rotation\", Integer.toString(rotation));\n    }\n\n    public void restartPreviewIfNeeded() {\n        new Thread() {\n            public void run() {\n                synchronized (mParametersSync) {\n                    try {\n                        // Normally, we should use safeStartPreview everywhere. However, some\n                        // cameras implicitly stops preview, and we don't know. So we just force\n                        // it here.\n                        mCamera.startPreview();\n                        mPreview.setPauseCopyFrame(false);\n                    } catch (Exception e) {\n                        // ignore\n                    }\n\n                    mIsPreviewStarted = true;\n                }\n            }\n        }.start();\n    }\n\n    public void setCameraMode(final int mode) {\n        if (mPreviewPauseListener != null) {\n            mPreviewPauseListener.onPreviewPause();\n        }\n\n        // Unlock any exposure/stab lock that was caused by\n        // swiping the ring\n        setLockSetup(false);\n\n        new Thread() {\n            public void run() {\n                synchronized (mParametersSync) {\n                    Log.d(TAG, \"setCameraMode -- start \"  + mode);\n                    mIsModeSwitching = true;\n                    Camera.Parameters params = getParameters();\n\n                    if (params == null) {\n                        // We're likely in the middle of a transient state.\n                        // Just do that again shortly when the camera will\n                        // be available.\n                        return;\n                    }\n\n                    boolean shouldStartPreview = false;\n\n                    if (mode == CameraActivity.CAMERA_MODE_VIDEO) {\n                        if (!mIsRecordingHint) {\n                            params.setRecordingHint(true);\n                            mIsRecordingHint = true;\n                            safeStopPreview();\n                            shouldStartPreview = true;\n                        }\n                    } else {\n                        if (mIsRecordingHint) {\n                            params.setRecordingHint(false);\n                            mIsRecordingHint = false;\n                            safeStopPreview();\n                            shouldStartPreview = true;\n                        }\n                    }\n\n                    if (mode == CameraActivity.CAMERA_MODE_PANO) {\n                        // Apply special settings for panorama mode\n                        initializePanoramaMode();\n                    } else {\n                        // Make sure the Infinity mode from panorama is gone\n                        params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);\n                    }\n\n                    if (mode == CameraActivity.CAMERA_MODE_PICSPHERE) {\n                        // If we are in PicSphere mode, we look for a correct 4:3 resolution. We\n                        // default the preview size to 640x480 however, as we don't need anything\n                        // bigger than that. We prefer to have a smaller resolution in case our\n                        // recommended resolution isn't available, as it will be faster to render.\n                        Point size = Util.findBestPicSpherePictureSize(params.getSupportedPictureSizes(), true);\n                        params.setPictureSize(size.x, size.y);\n                        params.setPreviewSize(640, 480);\n\n                        // Set focus mode to infinity\n                        setInfinityFocus(params);\n                    } else {\n                        setPreviewSize(mTargetSize.x, mTargetSize.y);\n                    }\n\n                    try {\n                        mCamera.setParameters(params);\n                    } catch (Exception e) {\n                        Log.e(TAG, \"Unable to set parameters\", e);\n                    }\n                    mParameters = mCamera.getParameters();\n\n                    if (shouldStartPreview) {\n                        updateDisplayOrientation();\n                        safeStartPreview();\n                    }\n                }\n\n                mPreview.setPauseCopyFrame(false);\n                mIsModeSwitching = false;\n\n                if (mPreviewPauseListener != null) {\n                    mPreviewPauseListener.onPreviewResume();\n                }\n            }\n        }.start();\n    }\n\n    /**\n     * Updates the orientation of the display\n     */\n    public void updateDisplayOrientation() {\n        android.hardware.Camera.CameraInfo info =\n                new android.hardware.Camera.CameraInfo();\n        android.hardware.Camera.getCameraInfo(mCurrentFacing, info);\n        int degrees = 0; //Util.getDisplayRotation(null);\n\n        int result;\n        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n            result = (info.orientation + degrees) % 360;\n            result = (360 - result) % 360;  // compensate the mirror\n        } else {  // back-facing\n            result = (info.orientation - degrees + 360) % 360;\n        }\n        mCamera.setDisplayOrientation(result);\n    }\n\n    /**\n     * Initializes the Panorama (mosaic) mode\n     */\n    private void initializePanoramaMode() {\n        Camera.Parameters parameters = getParameters();\n\n        int pixels = mContext.getResources().getInteger(R.integer.config_panoramaDefaultWidth)\n                * mContext.getResources().getInteger(R.integer.config_panoramaDefaultHeight);\n\n        List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();\n        Point previewSize = Util.findBestPanoPreviewSize(supportedSizes, false, false, pixels);\n\n        Log.v(TAG, \"preview h = \" + previewSize.y + \" , w = \" + previewSize.x);\n        parameters.setPreviewSize(previewSize.x, previewSize.y);\n        mTargetSize = previewSize;\n\n        List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();\n        if (frameRates != null) {\n            int last = frameRates.size() - 1;\n            int minFps = (frameRates.get(last))[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];\n            int maxFps = (frameRates.get(last))[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];\n            parameters.setPreviewFpsRange(minFps, maxFps);\n            Log.v(TAG, \"preview fps: \" + minFps + \", \" + maxFps);\n        }\n\n        setInfinityFocus(parameters);\n\n        parameters.setRecordingHint(false);\n        mParameters = parameters;\n    }\n\n    private void setInfinityFocus(Camera.Parameters parameters) {\n        List<String> supportedFocusModes = parameters.getSupportedFocusModes();\n        if (supportedFocusModes != null\n                && supportedFocusModes.indexOf(Camera.Parameters.FOCUS_MODE_INFINITY) >= 0) {\n            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);\n        } else {\n            // Use the default focus mode and log a message\n            Log.w(TAG, \"Cannot set the focus mode to \"\n                    + Camera.Parameters.FOCUS_MODE_INFINITY\n                    + \" because the mode is not supported.\");\n        }\n    }\n\n    /**\n     * Trigger the autofocus function of the device\n     *\n     * @param cb The AF callback\n     * @return true if we could start the AF, false otherwise\n     */\n    public boolean doAutofocus(final AutoFocusCallback cb) {\n        if (mCamera != null) {\n            try {\n                // Make sure our auto settings aren't locked\n                setLockSetup(false);\n\n                // Trigger af\n                mCamera.cancelAutoFocus();\n\n                mHandler.post(new Runnable() {\n                    public void run() {\n                        try {\n                            mCamera.autoFocus(cb);\n                        } catch (Exception e) {\n                            // Do nothing here\n                        }\n                    }\n                });\n\n            } catch (Exception e) {\n                return false;\n            }\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Returns whether or not the current open camera device supports\n     * focus area (focus ring)\n     *\n     * @return true if supported\n     */\n    public boolean isFocusAreaSupported() {\n        if (mCamera != null) {\n            try {\n                return (getParameters().getMaxNumFocusAreas() > 0);\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Returns whether or not the current open camera device supports\n     * exposure metering area (exposure ring)\n     *\n     * @return true if supported\n     */\n    public boolean isExposureAreaSupported() {\n        if (mCamera != null) {\n            try {\n                return (getParameters().getMaxNumMeteringAreas() > 0);\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Defines the main focus point of the camera to the provided x and y values.\n     * Those values must between -1000 and 1000, where -1000 is the top/left, and 1000 the bottom/right\n     *\n     * @param x The X position of the focus point\n     * @param y The Y position of the focus point\n     */\n    public void setFocusPoint(int x, int y) {\n        if (x < -1000 || x > 1000 || y < -1000 || y > 1000) {\n            Log.e(TAG, \"setFocusPoint: values are not ideal \" + \"x= \" + x + \" y= \" + y);\n            return;\n        }\n        Camera.Parameters params = getParameters();\n\n        if (params != null && params.getMaxNumFocusAreas() > 0) {\n            List<Camera.Area> focusArea = new ArrayList<Camera.Area>();\n            focusArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000));\n\n            params.setFocusAreas(focusArea);\n\n            try {\n                mCamera.setParameters(params);\n            } catch (Exception e) {\n                // Ignore, we might be setting it too\n                // fast since previous attempt\n            }\n        }\n    }\n\n    /**\n     * Defines the exposure metering point of the camera to the provided x and y values.\n     * Those values must between -1000 and 1000, where -1000 is the top/left, and 1000 the bottom/right\n     *\n     * @param x The X position of the exposure metering point\n     * @param y The Y position of the exposure metering point\n     */\n    public void setExposurePoint(int x, int y) {\n        Camera.Parameters params = getParameters();\n\n        if (params != null && params.getMaxNumMeteringAreas() > 0) {\n            List<Camera.Area> exposureArea = new ArrayList<Camera.Area>();\n            exposureArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000));\n\n            params.setMeteringAreas(exposureArea);\n\n            try {\n                mCamera.setParameters(params);\n            } catch (Exception e) {\n                // Ignore, we might be setting it too\n                // fast since previous attempt\n            }\n        }\n    }\n\n    public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {\n        mAutoFocusMoveCallback = cb;\n\n        List<String> focusModes = mParameters.getSupportedFocusModes();\n        if (mCamera != null && focusModes != null && focusModes.contains(\n                Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {\n            try {\n                mCamera.setAutoFocusMoveCallback(cb);\n            } catch (RuntimeException e) {\n                Log.e(TAG, \"Unable to set AutoFocusMoveCallback\", e);\n            }\n        }\n    }\n\n    /**\n     * Enable the device image stabilization system.\n     * @param enabled True to stabilize\n     */\n    public void setStabilization(boolean enabled) {\n        Camera.Parameters params = getParameters();\n        if (params == null) {\n            return;\n        }\n\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n            // In wrappers: sony has sony-is, HTC has ois_mode, etc.\n            if (params.get(\"image-stabilization\") != null) {\n                params.set(\"image-stabilization\", enabled ? \"on\" : \"off\");\n            }\n        } else if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            if (params.isVideoStabilizationSupported()) {\n                params.setVideoStabilization(enabled);\n            }\n        }\n\n        try {\n            mCamera.setParameters(params);\n        } catch (Exception e) {\n            // Do nothing here\n        }\n    }\n\n    /**\n     * Sets the camera device to render to a texture rather than a SurfaceHolder\n     * @param texture The texture to which render\n     */\n    public void setRenderToTexture(SurfaceTexture texture) {\n        mPreview.setRenderToTexture(texture);\n        Log.i(TAG, \"Needs to render to texture, rebooting preview\");\n        mPreview.notifyCameraChanged(true);\n    }\n\n    /**\n     * The CameraPreview class handles the Camera preview feed\n     * and setting the surface holder.\n     */\n    public class CameraPreview implements Camera.PreviewCallback {\n        private final static String TAG = \"CameraManager.CameraPreview\";\n\n        private SurfaceTexture mTexture;\n        private byte[] mLastFrameBytes;\n        private boolean mPauseCopyFrame;\n\n        public CameraPreview() {\n\n        }\n\n        /**\n         * Sets that the camera preview should rather render to a texture than the default\n         * SurfaceHolder.\n         * Note that you have to restart camera preview manually after setting this.\n         * Set to null to reset render to the SurfaceHolder.\n         *\n         * @param texture The target texture\n         */\n        public void setRenderToTexture(SurfaceTexture texture) {\n            mTexture = texture;\n        }\n\n        public void setPauseCopyFrame(boolean pause) {\n            mPauseCopyFrame = pause;\n\n            if (!pause && mCamera != null) {\n                postCallbackBuffer();\n            }\n        }\n\n        public void notifyPreviewSize(int width, int height) {\n            mLastFrameBytes = new byte[2048000];\n\n            // Update preview aspect ratio\n            mRenderer.updateRatio(width, height);\n        }\n\n        public byte[] getLastFrameBytes() {\n            return mLastFrameBytes;\n        }\n\n        public void notifyCameraChanged(boolean startPreview) {\n            synchronized (mParametersSync) {\n                if (mCamera != null) {\n                    if (startPreview) {\n                        safeStopPreview();\n                    }\n\n                    setupCamera();\n\n                    try {\n                        mCamera.setPreviewTexture(mTexture);\n\n                        if (startPreview) {\n                            updateDisplayOrientation();\n                            safeStartPreview();\n                            postCallbackBuffer();\n                        }\n                    } catch (RuntimeException e) {\n                        Log.e(TAG, \"Cannot set preview texture\", e);\n                    } catch (IOException e) {\n                        Log.e(TAG, \"Error setting camera preview\", e);\n                    }\n                }\n            }\n        }\n\n        public void restartPreview() {\n            synchronized (mParametersSync) {\n                if (mCamera != null) {\n                    try {\n                        safeStopPreview();\n                        mCamera.setParameters(mParameters);\n                    \n                        updateDisplayOrientation();\n                        safeStartPreview();\n                        setPauseCopyFrame(false);\n\n                    } catch (RuntimeException e) {\n                        Log.e(TAG, \"Cannot set preview texture\", e);\n                    }\n                }\n            }\n        }\n        public void postCallbackBuffer() {\n            if (mCamera != null && !mPauseCopyFrame) {\n                mCamera.addCallbackBuffer(mLastFrameBytes);\n                mCamera.setPreviewCallbackWithBuffer(CameraPreview.this);\n            }\n        }\n\n        private void setupCamera() {\n            // Set device-specifics here\n            try {\n                Resources res = mContext.getResources();\n\n                if (res != null) {\n                    if (res.getBoolean(R.bool.config_qualcommZslCameraMode) &&\n                            !Util.deviceNeedsDisableZSL()) {\n                        if (res.getBoolean(R.bool.config_useSamsungZSL)) {\n                            //mCamera.sendRawCommand(1508, 0, 0);\n                        }\n                        mParameters.set(\"camera-mode\", 1);\n                    }\n                }\n                mCamera.setDisplayOrientation(90);\n                mCamera.setParameters(mParameters);\n\n                postCallbackBuffer();\n            } catch (Exception e) {\n                Log.e(TAG, \"Could not set device specifics\");\n\n            }\n        }\n\n        @Override\n        public void onPreviewFrame(byte[] data, Camera camera) {\n            if (mCamera != null && !mPauseCopyFrame) {\n                mCamera.addCallbackBuffer(mLastFrameBytes);\n            }\n        }\n    }\n\n    public class CameraRenderer implements GLSurfaceView.Renderer {\n        private final float[] mTransformMatrix;\n        int mTexture;\n        private SurfaceTexture mSurface;\n\n        private final static String VERTEX_SHADER =\n                \"attribute vec4 vPosition;\\n\" +\n                        \"attribute vec2 a_texCoord;\\n\" +\n                        \"varying vec2 v_texCoord;\\n\" +\n                        \"uniform mat4 u_xform;\\n\" +\n                        \"void main() {\\n\" +\n                        \"  gl_Position = vPosition;\\n\" +\n                        \"  v_texCoord = vec2(u_xform * vec4(a_texCoord, 1.0, 1.0));\\n\" +\n                        \"}\\n\";\n\n        private final static String FRAGMENT_SHADER =\n                \"#extension GL_OES_EGL_image_external : require\\n\" +\n                        \"precision mediump float;\\n\" +\n                        \"uniform samplerExternalOES s_texture;\\n\" +\n                        \"varying vec2 v_texCoord;\\n\" +\n                        \"void main() {\\n\" +\n                        \"  gl_FragColor = texture2D(s_texture, v_texCoord);\\n\" +\n                        \"}\\n\";\n\n        private FloatBuffer mVertexBuffer, mTextureVerticesBuffer;\n        private int mProgram;\n        private int mPositionHandle;\n        private int mTextureCoordHandle;\n        private int mTransformHandle;\n        private int mWidth;\n        private int mNaturalWidth;\n        private int mHeight;\n        private int mNaturalHeight;\n        private float mNaturalRatio;\n        private float mRatio = 4.0f/3.0f;\n        private float mUpdateRatioTo = -1;\n        private Object fSync = new Object();\n\n        // Number of coordinates per vertex in this array\n        static final int COORDS_PER_VERTEX = 2;\n\n        private float mSquareVertices[];\n\n        private final float mTextureVertices[] =\n                { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f };\n\n        public CameraRenderer() {\n            mTransformMatrix = new float[16];\n        }\n\n        public void updateRatio(int width, int height) {\n            synchronized(fSync){\n                mUpdateRatioTo = 1;\n                                    \n                float ratio = (float) width/(float) height;\n\n                if (ratio != mNaturalRatio){\n                    float widthRatio = (float) mNaturalWidth/(float) width;\n                    float heightRatio = (float) mNaturalHeight/(float) height;\n                    mRatio = widthRatio / heightRatio;\n                } else {\n                    mRatio = 1;\n                }\n                Log.d(TAG, \"updateRatio \" + width+\"x\"+height + \" mRatio=\"+mRatio);\n            }\n        }\n\n        public void onSurfaceCreated(GL10 unused, EGLConfig config) {\n            mTexture = createTexture();\n            mSurface = new SurfaceTexture(mTexture);\n            GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);\n\n            setRenderToTexture(mSurface);\n\n            ByteBuffer bb2 = ByteBuffer.allocateDirect(mTextureVertices.length * 4);\n            bb2.order(ByteOrder.nativeOrder());\n            mTextureVerticesBuffer = bb2.asFloatBuffer();\n            mTextureVerticesBuffer.put(mTextureVertices);\n            mTextureVerticesBuffer.position(0);\n\n            // Load shaders\n            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);\n            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);\n\n            mProgram = GLES20.glCreateProgram();             // create empty OpenGL ES Program\n            GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program\n            GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program\n            GLES20.glLinkProgram(mProgram);\n\n            // Since we only use one program/texture/vertex array, we bind them once here\n            // and then we only draw what we need in onDrawFrame\n            GLES20.glUseProgram(mProgram);\n\n            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);\n            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexture);\n\n            // Setup vertex buffer. Use a default 4:3 ratio, this will get updated once we have\n            // a preview aspect ratio.\n            // Regenerate vertexes\n            mSquareVertices = new float[]{ 1.0f * 1.0f,\n                    1.0f, -1.0f,\n                    1.0f, -1.0f,\n                    -1.0f,\n                    1.0f * 1.0f, -1.0f };\n\n            ByteBuffer bb = ByteBuffer.allocateDirect(mSquareVertices.length * 4);\n            bb.order(ByteOrder.nativeOrder());\n            mVertexBuffer = bb.asFloatBuffer();\n            mVertexBuffer.put(mSquareVertices);\n            mVertexBuffer.position(0);\n\n            mPositionHandle = GLES20.glGetAttribLocation(mProgram, \"vPosition\");\n            GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,\n                    false, 0, mVertexBuffer);\n            GLES20.glEnableVertexAttribArray(mPositionHandle);\n\n            mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, \"a_texCoord\");\n            GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,\n                    false, 0, mTextureVerticesBuffer);\n            GLES20.glEnableVertexAttribArray(mTextureCoordHandle);\n\n            mTransformHandle = GLES20.glGetUniformLocation(mProgram, \"u_xform\");\n        }\n\n        public void onDrawFrame(GL10 unused) {\n            synchronized(fSync){\n                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);\n\n                if (mUpdateRatioTo > 0) {\n                    Log.d(TAG, \"onDrawFrame \" + \" mRatio=\"+mRatio);\n                    int deltaWidth = (int) Math.abs(mWidth - mWidth * mRatio);\n                    GLES20.glViewport(-deltaWidth / 2, 0,\n                            (int) (mWidth * mRatio + deltaWidth / 2.0f), mHeight);\n                    mUpdateRatioTo = -1;\n                }\n\n            if (mSurface != null) {\n                mSurface.updateTexImage();\n                mSurface.getTransformMatrix(mTransformMatrix);\n                GLES20.glUniformMatrix4fv(mTransformHandle, 1, false, mTransformMatrix, 0);\n            }\n\n            if (mUpdateRatioTo > 0) {\n                // TODO mUpdateRatioTo would be the new ratio but still mRatio is used here\n                GLES20.glViewport(0, 0, (int) (mWidth*mRatio), mHeight);\n                mUpdateRatioTo = -1;\n            }\n\n                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);\n            }\n        }\n\n        public void onSurfaceChanged(GL10 unused, int width, int height) {\n            mWidth = width;\n            mHeight = height;\n\n            if (mWidth > mHeight){\n                mNaturalWidth = mWidth;\n                mNaturalHeight = mHeight;\n            } else {\n                mNaturalWidth = mHeight;\n                mNaturalHeight = mWidth;\n            }\n            mNaturalRatio = (float) mNaturalWidth/(float) mNaturalHeight;\n            mRatio = mNaturalRatio;\n            GLES20.glViewport(0, 0, width, height);\n            Log.d(TAG, \"onSurfaceChanged \" + width+\"x\"+height + \" mNaturalRatio=\"+mNaturalRatio);\n        }\n\n        public int loadShader(int type, String shaderCode) {\n            int shader = GLES20.glCreateShader(type);\n\n            GLES20.glShaderSource(shader, shaderCode);\n            GLES20.glCompileShader(shader);\n\n            return shader;\n        }\n\n        private int createTexture() {\n            int[] texture = new int[1];\n\n            GLES20.glGenTextures(1,texture, 0);\n            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);\n\n            return texture[0];\n        }\n    }\n    \n\t// TODO: just added for debugging\n    public static void dumpParameter(Camera.Parameters parameters) {\n        String flattened = parameters.flatten();\n        StringTokenizer tokenizer = new StringTokenizer(flattened, \";\");\n        Log.d(TAG, \"Dump all camera parameters:\");\n        while (tokenizer.hasMoreElements()) {\n            Log.d(TAG, tokenizer.nextToken());\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/DisableCameraReceiver.java",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.hardware.Camera.CameraInfo;\nimport android.util.Log;\n\n// We want to disable camera-related activities if there is no camera. This\n// receiver runs when BOOT_COMPLETED intent is received. After running once\n// this receiver will be disabled, so it will not run again.\npublic class DisableCameraReceiver extends BroadcastReceiver {\n    private static final String TAG = \"FocalDisableCameraReceiver\";\n    private static final String ACTIVITIES[] = {\n        \"org.cyanogenmod.focal.CameraActivity\",\n    };\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        // Disable camera-related activities if there is no camera.\n        boolean needCameraActivity = (android.hardware.Camera.getNumberOfCameras() > 0);\n\n        if (!needCameraActivity) {\n            Log.i(TAG, \"no camera detected: disable all focal activities\");\n            for (int i = 0; i < ACTIVITIES.length; i++) {\n                disableComponent(context, ACTIVITIES[i]);\n            }\n        }\n\n        // Disable this receiver so it won't run again.\n        disableComponent(context, \"org.cyanogenmod.focal.DisableCameraReceiver\");\n    }\n\n    private void disableComponent(Context context, String klass) {\n        ComponentName name = new ComponentName(context, klass);\n        PackageManager pm = context.getPackageManager();\n\n        // We need the DONT_KILL_APP flag, otherwise we will be killed\n        // immediately because we are in the same app.\n        pm.setComponentEnabledSetting(name,\n            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,\n            PackageManager.DONT_KILL_APP);\n    }\n}\n\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/Exif.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2010 The Android Open Source Project\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.util.Log;\n\npublic class Exif {\n    private static final String TAG = \"CameraExif\";\n\n    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.\n    public static int getOrientation(byte[] jpeg) {\n        if (jpeg == null) {\n            return 0;\n        }\n\n        int offset = 0;\n        int length = 0;\n\n        // ISO/IEC 10918-1:1993(E)\n        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {\n            int marker = jpeg[offset] & 0xFF;\n\n            // Check if the marker is a padding.\n            if (marker == 0xFF) {\n                continue;\n            }\n            offset++;\n\n            // Check if the marker is SOI or TEM.\n            if (marker == 0xD8 || marker == 0x01) {\n                continue;\n            }\n            // Check if the marker is EOI or SOS.\n            if (marker == 0xD9 || marker == 0xDA) {\n                break;\n            }\n\n            // Get the length and check if it is reasonable.\n            length = pack(jpeg, offset, 2, false);\n            if (length < 2 || offset + length > jpeg.length) {\n                Log.e(TAG, \"Invalid length\");\n                return 0;\n            }\n\n            // Break if the marker is EXIF in APP1.\n            if (marker == 0xE1 && length >= 8 &&\n                    pack(jpeg, offset + 2, 4, false) == 0x45786966 &&\n                    pack(jpeg, offset + 6, 2, false) == 0) {\n                offset += 8;\n                length -= 8;\n                break;\n            }\n\n            // Skip other markers.\n            offset += length;\n            length = 0;\n        }\n\n        // JEITA CP-3451 Exif Version 2.2\n        if (length > 8) {\n            // Identify the byte order.\n            int tag = pack(jpeg, offset, 4, false);\n            if (tag != 0x49492A00 && tag != 0x4D4D002A) {\n                Log.e(TAG, \"Invalid byte order\");\n                return 0;\n            }\n            boolean littleEndian = (tag == 0x49492A00);\n\n            // Get the offset and check if it is reasonable.\n            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;\n            if (count < 10 || count > length) {\n                Log.e(TAG, \"Invalid offset\");\n                return 0;\n            }\n            offset += count;\n            length -= count;\n\n            // Get the count and go through all the elements.\n            count = pack(jpeg, offset - 2, 2, littleEndian);\n            while (count-- > 0 && length >= 12) {\n                // Get the tag and check if it is orientation.\n                tag = pack(jpeg, offset, 2, littleEndian);\n                if (tag == 0x0112) {\n                    // We do not really care about type and count, do we?\n                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);\n                    switch (orientation) {\n                        case 1:\n                            return 0;\n                        case 3:\n                            return 180;\n                        case 6:\n                            return 90;\n                        case 8:\n                            return 270;\n                    }\n                    Log.i(TAG, \"Unsupported orientation\");\n                    return 0;\n                }\n                offset += 12;\n                length -= 12;\n            }\n        }\n\n        Log.i(TAG, \"Orientation not found\");\n        return 0;\n    }\n\n    private static int pack(byte[] bytes, int offset, int length,\n            boolean littleEndian) {\n        int step = 1;\n        if (littleEndian) {\n            offset += length - 1;\n            step = -1;\n        }\n\n        int value = 0;\n        while (length-- > 0) {\n            value = (value << 8) | (bytes[offset] & 0xFF);\n            offset += step;\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/FocusManager.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.hardware.Camera;\nimport android.hardware.Camera.AutoFocusCallback;\nimport android.hardware.Camera.AutoFocusMoveCallback;\nimport android.os.Handler;\n\npublic class FocusManager implements AutoFocusCallback, AutoFocusMoveCallback {\n    public final static String TAG = \"FocusManager\";\n\n    public interface FocusListener {\n        /**\n         * This method is called when the focus operation starts (whether through touch to focus,\n         * or via continuous-picture focus mode).\n         *\n         * @param smallAdjust If this parameter is set to true, the focus is being done because of\n         *                    continuous focus mode and thus only make a small adjustment\n         */\n        public void onFocusStart(boolean smallAdjust);\n\n        /**\n         * This method is called when the focus operation ends\n         *\n         * @param smallAdjust If this parameter is set to true, the focus is being done because of\n         *                    continuous focus mode and made only a small adjustment\n         * @param success     If the focus operation was successful. Note that if smallAdjust is true,\n         *                    this parameter will always be false.\n         */\n        public void onFocusReturns(boolean smallAdjust, boolean success);\n    }\n\n    // Miliseconds during which we assume the focus is good\n    private int mFocusKeepTimeMs = 3000;\n    private long mLastFocusTimestamp = 0;\n\n    private Handler mHandler;\n    private CameraManager mCamManager;\n    private FocusListener mListener;\n    private boolean mIsFocusing;\n\n    public FocusManager(CameraManager cam) {\n        mHandler = new Handler();\n        mCamManager = cam;\n        mIsFocusing = false;\n\n        mCamManager.setAutoFocusMoveCallback(this);\n        Camera.Parameters params = mCamManager.getParameters();\n        if (params.getSupportedFocusModes().contains(\"auto\")) {\n            params.setFocusMode(\"auto\");\n        }\n\n        // Do a first focus after 1 second\n        mHandler.postDelayed(new Runnable() {\n            public void run() {\n                checkFocus();\n            }\n        }, 1000);\n    }\n\n    public void setListener(FocusListener listener) {\n        mListener = listener;\n    }\n\n    public void checkFocus() {\n        long time = System.currentTimeMillis();\n\n        if (time - mLastFocusTimestamp > mFocusKeepTimeMs && !mIsFocusing) {\n            refocus();\n        } else if (time - mLastFocusTimestamp > mFocusKeepTimeMs * 2) {\n            // Force a refocus after 2 times focus failed\n            refocus();\n        }\n    }\n\n    /**\n     * Sets the time during which the focus is considered valid\n     * before refocusing\n     *\n     * @param timeMs Time in miliseconds\n     */\n    public void setFocusKeepTime(int timeMs) {\n        mFocusKeepTimeMs = timeMs;\n    }\n\n    public void refocus() {\n        if (mCamManager.doAutofocus(this)) {\n            mIsFocusing = true;\n\n            if (mListener != null) {\n                mListener.onFocusStart(false);\n            }\n        }\n    }\n\n    @Override\n    public void onAutoFocus(boolean success, Camera cam) {\n        mLastFocusTimestamp = System.currentTimeMillis();\n        mIsFocusing = false;\n\n        if (mListener != null) {\n            mListener.onFocusReturns(false, success);\n        }\n    }\n\n    @Override\n    public void onAutoFocusMoving(boolean start, Camera cam) {\n        if (mIsFocusing && !start) {\n            // We were focusing, but we stopped, notify time of last focus\n            mLastFocusTimestamp = System.currentTimeMillis();\n        }\n\n        if (start) {\n            if (mListener != null) {\n                mListener.onFocusStart(true);\n            }\n        } else {\n            if (mListener != null) {\n                mListener.onFocusReturns(true, false);\n            }\n        }\n        mIsFocusing = start;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/PopenHelper.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\n/**\n * Helper class calling a JNI native popen method to run system\n * commands\n */\npublic class PopenHelper {\n    static {\n        System.loadLibrary(\"xmptoolkit\");\n        System.loadLibrary(\"popen_helper_jni\");\n    }\n\n    /**\n     * Runs the specified command-line program in the command parameter.\n     * Note that this command is ran with the shell interpreter (/system/bin/sh), thus\n     * a shell script or a bunch of commands can be called in one method call.\n     * The output of the commands (stdout/stderr) is sent out to the logcat with the\n     * INFORMATION level.\n     *\n     * @params command The command to run\n     * @return The call return code\n     */\n    public static native int run(String command);\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/Profiler.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.os.SystemClock;\nimport android.util.Log;\n\nimport java.util.HashMap;\n\n/**\n * Simple profiler to help debug app speed\n */\npublic class Profiler {\n    private static final String TAG = \"Profiler\";\n\n    private static Profiler sDefault = null;\n    private HashMap<String, Long> mTimestamps;\n\n    public static Profiler getDefault() {\n        if (sDefault == null) sDefault = new Profiler();\n        return sDefault;\n    }\n\n    private Profiler() {\n        mTimestamps = new HashMap<String, Long>();\n    }\n\n    public void start(String name) {\n        mTimestamps.put(name, SystemClock.uptimeMillis());\n    }\n\n    public void logProfile(String name) {\n        long time = mTimestamps.get(name);\n        long delta = SystemClock.uptimeMillis() - time;\n\n        Log.d(TAG, \"Time for '\" + name + \"': \" + delta + \"ms\");\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/SettingsStorage.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\n/**\n * Manages the storage of all the settings\n */\npublic class SettingsStorage {\n    private final static String PREFS_CAMERA = \"nemesis-camera\";\n    private final static String PREFS_APP = \"nemesis-app\";\n    private final static String PREFS_VISIBILITY = \"nemesis-visibility\";\n    private final static String PREFS_SHORTCUTS = \"nemesis-shortcuts\";\n    public final static String TAG = \"SettingsStorage\";\n\n    private static void store(Context context, String prefsName, String key, String value) {\n        SharedPreferences prefs = context.getSharedPreferences(prefsName, 0);\n        SharedPreferences.Editor editor = prefs.edit();\n        editor.putString(key, value);\n        editor.commit();\n    }\n\n    private static String retrieve(Context context, String prefsName, String key, String def) {\n        SharedPreferences prefs = context.getSharedPreferences(prefsName, 0);\n        return prefs.getString(key, def);\n    }\n\n    /**\n     * Stores a setting of the camera parameters array\n     * @param context\n     * @param key\n     * @param value\n     */\n    public static void storeCameraSetting(Context context, int facing, String key, String value) {\n        store(context, PREFS_CAMERA, Integer.toString(facing) + \":\" + key, value);\n    }\n\n    /**\n     * Returns a setting of the camera\n     * @param context\n     * @param key\n     * @param def Default if key doesn't exist\n     * @return\n     */\n    public static String getCameraSetting(Context context, int facing, String key, String def) {\n        return retrieve(context, PREFS_CAMERA, Integer.toString(facing) + \":\" + key, def);\n    }\n\n    /**\n     * Stores a setting of the camera parameters array\n     * @param context\n     * @param key\n     * @param value\n     */\n    public static void storeAppSetting(Context context, String key, String value) {\n        store(context, PREFS_APP, key, value);\n    }\n\n    /**\n     * Returns a setting of the app\n     * @param context\n     * @param key\n     * @param def Default if key doesn't exist\n     * @return\n     */\n    public static String getAppSetting(Context context, String key, String def) {\n        return retrieve(context, PREFS_APP, key, def);\n    }\n\n    /**\n     * Stores a setting of the widgets visibility\n     * @param context\n     * @param key\n     * @param visible\n     */\n    public static void storeVisibilitySetting(Context context, String key, boolean visible) {\n        store(context, PREFS_VISIBILITY, key, visible ? \"true\" : \"false\");\n    }\n\n    /**\n     * Returns a setting of the app (always defaults to visible)\n     * @param context\n     * @param key\n     * @return\n     */\n    public static boolean getVisibilitySetting(Context context, String key) {\n        return retrieve(context, PREFS_VISIBILITY, key, \"true\").equals(\"true\");\n    }\n\n    /**\n     * Stores a setting of the widgets shortcut\n     * @param context\n     * @param key\n     * @param pinned\n     */\n    public static void storeShortcutSetting(Context context, String key, boolean pinned) {\n        store(context, PREFS_SHORTCUTS, key, pinned ? \"true\" : \"false\");\n    }\n\n    /**\n     * Returns a setting of the widgets shortcut\n     * @param context\n     * @param key\n     * @return\n     */\n    public static boolean getShortcutSetting(Context context, String key) {\n        return retrieve(context, PREFS_SHORTCUTS, key, \"false\").equals(\"true\");\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/SnapshotManager.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.database.CursorIndexOutOfBoundsException;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.hardware.Camera;\nimport android.location.Location;\nimport android.media.CamcorderProfile;\nimport android.media.ExifInterface;\nimport android.media.MediaRecorder;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.ParcelFileDescriptor;\nimport android.os.SystemClock;\nimport android.provider.MediaStore;\nimport android.util.Log;\n\nimport com.drew.imaging.jpeg.JpegMetadataReader;\nimport com.drew.imaging.jpeg.JpegProcessingException;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.Tag;\n\nimport org.cyanogenmod.focal.feats.AutoPictureEnhancer;\nimport org.cyanogenmod.focal.feats.PixelBuffer;\nimport org.cyanogenmod.focal.widgets.SimpleToggleWidget;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport fr.xplod.focal.R;\n\n/**\n * This class manages taking snapshots and videos from Camera\n */\npublic class SnapshotManager {\n    public final static String TAG = \"SnapshotManager\";\n    private boolean mPaused;\n\n    public interface SnapshotListener {\n        /**\n         * This callback is called when a snapshot is taken (shutter)\n         *\n         * @param info A structure containing information about the snapshot taken\n         */\n        public void onSnapshotShutter(SnapshotInfo info);\n\n        /**\n         * This callback is called when we have a preview for the snapshot\n         *\n         * @param info A structure containing information about the snapshot\n         */\n        public void onSnapshotPreview(SnapshotInfo info);\n\n        /**\n         * This callback is called when a snapshot is being processed (auto color enhancement, etc)\n         *\n         * @param info A structure containing information about the snapshot\n         */\n        public void onSnapshotProcessing(SnapshotInfo info);\n\n        /**\n         * This callback is called when a snapshot has been saved to the internal memory\n         *\n         * @param info A structure containing information about the snapshot\n         */\n        public void onSnapshotSaved(SnapshotInfo info);\n\n        /**\n         * This callback is called when ImageSaver starts a job of saving an image, or\n         * MediaRecorder is storing a video\n         * The primary purpose of this method is to show the SavePinger\n         */\n        public void onMediaSavingStart();\n\n        /**\n         * This callback is called when ImageSaver has done its job of saving an image,\n         * or MediaRecorder is done storing a video\n         * The primary purpose of this method is to hide the SavePinger\n         */\n        public void onMediaSavingDone();\n\n        /**\n         * This callback is called when a video starts recording\n         */\n        public void onVideoRecordingStart();\n\n        /**\n         * This callback is called when a video stops recording\n         */\n        public void onVideoRecordingStop();\n    }\n\n    public class SnapshotInfo {\n        // Whether or not the snapshot has to be saved to internal memory\n        public boolean mSave;\n\n        // The URI of the saved shot, if it has been saved, and if the save is done (mSave = true).\n        // This field will likely be only different than null when onSnapshotSaved is called.\n        public Uri mUri;\n\n        // The exposure compensation value set for the shot, IF a specific\n        // value was needed\n        public int mExposureCompensation;\n\n        // A bitmap containing a thumbnail of the image\n        public Bitmap mThumbnail;\n\n        // Whether or not to bypass image processing (even if user enabled it)\n        public boolean mBypassProcessing;\n    }\n\n    private Context mContext;\n    private CameraManager mCameraManager;\n    private FocusManager mFocusManager;\n    private boolean mBypassProcessing;\n\n    // Photo-related variables\n    private boolean mWaitExposureSettle;\n    private int mResetExposure;\n    private List<SnapshotInfo> mSnapshotsQueue;\n    private int mCurrentShutterQueueIndex;\n    private List<SnapshotListener> mListeners;\n    private Handler mHandler;\n    private ContentResolver mContentResolver;\n    private ImageSaver mImageSaver;\n    private ImageNamer mImageNamer;\n    private PixelBuffer mOffscreenGL;\n    private AutoPictureEnhancer mAutoPicEnhancer;\n    private boolean mImageIsProcessing;\n    private boolean mDoAutoEnhance;\n\n    // Video-related variables\n    private long mRecordingStartTime;\n    private boolean mIsRecording;\n    private VideoNamer mVideoNamer;\n    private CamcorderProfile mProfile;\n\n    // The video file that the hardware camera is about to record into\n    // (or is recording into.)\n    private String mVideoFilename;\n    private ParcelFileDescriptor mVideoFileDescriptor;\n\n    // The video file that has already been recorded, and that is being\n    // examined by the user.\n    private String mCurrentVideoFilename;\n    private Uri mCurrentVideoUri;\n    private ContentValues mCurrentVideoValues;\n\n    private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {\n        @Override\n        public void onShutter() {\n            Log.v(TAG, \"onShutter\");\n\n            Camera.Size s = mCameraManager.getParameters().getPictureSize();\n            mImageNamer.prepareUri(mContentResolver, System.currentTimeMillis(), s.width, s.height, 0);\n\n            // On shutter confirmed, play a small flashing animation\n            final SnapshotInfo snap = mSnapshotsQueue.get(mCurrentShutterQueueIndex);\n\n            for (SnapshotListener listener : mListeners) {\n                listener.onSnapshotShutter(snap);\n            }\n\n            // If we used Samsung HDR, reset exposure\n            if (mContext.getResources().getBoolean(R.bool.config_useSamsungHDR) &&\n                SimpleToggleWidget.isWidgetEnabled(mContext, mCameraManager, \"scene-mode\", \"hdr\")) {\n                mCameraManager.setParameterAsync(\"exposure-compensation\",\n                        Integer.toString(mResetExposure));\n            }\n        }\n    };\n\n    private Camera.PictureCallback mJpegPictureCallback = new Camera.PictureCallback() {\n        @Override\n        public void onPictureTaken(byte[] jpegData, Camera camera) {\n            Log.v(TAG, \"onPicture: Got JPEG data\");\n            mCameraManager.restartPreviewIfNeeded();\n\n            // Calculate the width and the height of the jpeg.\n            final Camera.Size s = mCameraManager.getParameters().getPictureSize();\n            int orientation = CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PANO ? 0 :\n                    mCameraManager.getOrientation();\n            final int width = s.width,\n                    height = s.height;\n\n            if (mSnapshotsQueue.size() == 0) {\n                Log.e(TAG, \"DERP! Why is snapshotqueue empty? Two JPEG callbacks!?\");\n                return;\n            }\n\n            final SnapshotInfo snap = mSnapshotsQueue.get(0);\n\n            // If we have a Samsung HDR, convert from YUV422 to JPEG first\n            if (mContext.getResources().getBoolean(R.bool.config_useSamsungHDR) &&\n                SimpleToggleWidget.isWidgetEnabled(mContext, mCameraManager, \"scene-mode\", \"hdr\")) {\n                ByteArrayOutputStream baos = new ByteArrayOutputStream();\n                Bitmap bm = Util.decodeYUV422P(jpegData, s.width, s.height);\n                // TODO: Replace 90 with real JPEG compression level when we'll have that setting\n                bm.compress(Bitmap.CompressFormat.JPEG, 90, baos);\n                jpegData = baos.toByteArray();\n            }\n\n            // Store the jpeg on internal memory if needed\n            if (snap.mSave) {\n                final Uri uri = mImageNamer.getUri();\n                final String title = mImageNamer.getTitle();\n                snap.mUri = uri;\n\n                // If the orientation is somehow negative, avoid the Gallery crashing dumbly\n                // (see com/android/gallery3d/ui/PhotoView.java line 758 (setTileViewPosition))\n                while (orientation < 0) {\n                    orientation += 360;\n                }\n                orientation = orientation % 360;\n\n                final int correctedOrientation = orientation;\n                final byte[] finalData = jpegData;\n\n                if (!snap.mBypassProcessing && mDoAutoEnhance) {\n                    new Thread() {\n                        public void run() {\n                            mImageIsProcessing = true;\n                            for (SnapshotListener listener : mListeners) {\n                                listener.onSnapshotProcessing(snap);\n                            }\n\n                            // Read EXIF\n                            List<Tag> tagsList = new ArrayList<Tag>();\n                            BufferedInputStream is = new BufferedInputStream(\n                                    new ByteArrayInputStream(finalData));\n                            try {\n                                Metadata metadata = JpegMetadataReader.readMetadata(is, false);\n\n                                for (Directory directory : metadata.getDirectories()) {\n                                    for (Tag tag : directory.getTags()) {\n                                        tagsList.add(tag);\n                                    }\n                                }\n                            } catch (JpegProcessingException e) {\n                                Log.e(TAG, \"Error processing input JPEG\", e);\n                            }\n\n                            // XXX: PixelBuffer has to be created every time because the GL context\n                            // can only be used from its original thread. It's not very intense, but\n                            // ideally we would be re-using the same thread every time.\n                            try {\n                                mOffscreenGL = new PixelBuffer(mContext, s.width, s.height);\n                                mAutoPicEnhancer = new AutoPictureEnhancer(mContext);\n                                mOffscreenGL.setRenderer(mAutoPicEnhancer);\n                                mAutoPicEnhancer.setTexture(BitmapFactory.decodeByteArray(finalData,\n                                        0, finalData.length));\n\n                                ByteArrayOutputStream baos = new ByteArrayOutputStream();\n                                mOffscreenGL.getBitmap().compress(Bitmap.CompressFormat.JPEG, 90, baos);\n\n                                if (mImageSaver != null) {\n                                    mImageSaver.addImage(baos.toByteArray(), uri, title, null,\n                                            width, height, correctedOrientation, tagsList, snap);\n                                } else {\n                                    Log.e(TAG, \"ImageSaver was null: couldn't save image!\");\n                                }\n\n                                if (mPaused && mImageSaver != null) {\n                                    // We were paused, stop the saver now\n                                    mImageSaver.finish();\n                                    mImageSaver = null;\n                                }\n\n                                mImageIsProcessing = false;\n                            }\n                            catch (Exception e) {\n                                // The rendering failed, the device might not be compatible for\n                                // whatever reason. We just save the original file.\n                                if (mImageSaver != null) {\n                                    mImageSaver.addImage(finalData, uri, title, null,\n                                            width, height, correctedOrientation, snap);\n                                }\n\n                                CameraActivity.notify(\"Auto-enhance failed: Original shot saved\", 2000);\n                            }\n                            catch (OutOfMemoryError e) {\n                                // The rendering failed, the device might not be compatible for\n                                // whatever reason. We just save the original file.\n                                if (mImageSaver != null) {\n                                    mImageSaver.addImage(finalData, uri, title, null,\n                                            width, height, correctedOrientation, snap);\n                                }\n\n                                CameraActivity.notify(\"Error: Out of memory. Original shot saved\", 2000);\n                            }\n                        }\n                    }.start();\n                } else {\n                    // Just save it as is\n                    mImageSaver.addImage(finalData, uri, title, null,\n                            width, height, correctedOrientation, snap);\n                }\n            }\n\n            // Camera is ready to take another shot, doit\n            if (mSnapshotsQueue.size() > mCurrentShutterQueueIndex + 1) {\n                mCurrentShutterQueueIndex++;\n                mHandler.post(mCaptureRunnable);\n            }\n\n            // We're done with our shot here!\n            mCurrentShutterQueueIndex--;\n            mSnapshotsQueue.remove(0);\n        }\n    };\n\n    private Runnable mCaptureRunnable = new Runnable() {\n        @Override\n        public void run() {\n            if (mWaitExposureSettle) {\n                try {\n                    Thread.sleep(200);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n            mCameraManager.takeSnapshot(mShutterCallback, null, mJpegPictureCallback);\n        }\n    };\n\n    private Runnable mPreviewCaptureRunnable = new Runnable() {\n        @Override\n        public void run() {\n            Camera.Size s = mCameraManager.getParameters().getPreviewSize();\n            mImageNamer.prepareUri(mContentResolver, System.currentTimeMillis(),\n                    s.width, s.height, 0);\n\n            SnapshotInfo info = new SnapshotInfo();\n            info.mSave = true;\n            info.mExposureCompensation = 0;\n            info.mThumbnail = mCameraManager.getLastPreviewFrame();\n\n            for (SnapshotListener listener : mListeners) {\n                listener.onSnapshotShutter(info);\n            }\n\n            ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            info.mThumbnail.compress(Bitmap.CompressFormat.JPEG, 80, baos);\n            byte[] jpegData = baos.toByteArray();\n\n            // Calculate the width and the height of the jpeg.\n            int orientation = Exif.getOrientation(jpegData) - mCameraManager.getOrientation();\n            int width, height;\n            width = s.width;\n            height = s.height;\n\n            Uri uri = mImageNamer.getUri();\n            String title = mImageNamer.getTitle();\n            info.mUri = uri;\n\n            mImageSaver.addImage(jpegData, uri, title, null,\n                    width, height, orientation);\n\n            for (SnapshotListener listener : mListeners) {\n                listener.onSnapshotSaved(info);\n            }\n        }\n    };\n\n    public SnapshotManager(CameraManager man, FocusManager focusMan, Context ctx) {\n        mContext = ctx;\n        mCameraManager = man;\n        mFocusManager = focusMan;\n        mSnapshotsQueue = new ArrayList<SnapshotInfo>();\n        mListeners = new ArrayList<SnapshotListener>();\n        mHandler = new Handler();\n        mImageSaver = new ImageSaver();\n        mImageNamer = new ImageNamer();\n        mVideoNamer = new VideoNamer();\n        mContentResolver = ctx.getContentResolver();\n        mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);\n        mPaused = false;\n        mImageIsProcessing = false;\n    }\n\n    public void addListener(SnapshotListener listener) {\n        if (!mListeners.contains(listener)) {\n            mListeners.add(listener);\n        }\n    }\n\n    public void removeListener(SnapshotListener listener) {\n        mListeners.remove(listener);\n    }\n\n    /**\n     * Sets whether or not to bypass image processing (for burst shot or hdr for instance)\n     * This value is reset after each snapshot queued!\n     * @param bypass\n     */\n    public void setBypassProcessing(boolean bypass) {\n        mBypassProcessing = bypass;\n    }\n\n    public void setAutoEnhance(boolean enhance) {\n        mDoAutoEnhance = enhance;\n    }\n\n    public boolean getAutoEnhance() {\n        return mDoAutoEnhance;\n    }\n\n    public void prepareNamerUri(int width, int height) {\n        if (mImageNamer == null) {\n            // ImageNamer can be dead if the user exitted the app.\n            // We restart it temporarily.\n            mImageNamer = new ImageNamer();\n        }\n        mImageNamer.prepareUri(mContentResolver,\n                System.currentTimeMillis(), width, height, 0);\n    }\n\n    public Uri getNamerUri() {\n        if (mImageNamer == null) {\n            // ImageNamer can be dead if the user exitted the app.\n            // We restart it temporarily.\n            mImageNamer = new ImageNamer();\n        }\n        return mImageNamer.getUri();\n    }\n\n    public String getNamerTitle() {\n        if (mImageNamer == null) {\n            // ImageNamer can be dead if the user exitted the app.\n            // We restart it temporarily.\n            mImageNamer = new ImageNamer();\n        }\n        return mImageNamer.getTitle();\n    }\n\n    public void saveImage(Uri uri, String title, int width, int height,\n                          int orientation, byte[] jpegData) {\n        if (mImageSaver == null) {\n            // ImageSaver can be dead if the user exitted the app.\n            // We restart it temporarily.\n            mImageSaver = new ImageSaver();\n        }\n        mImageSaver.addImage(jpegData, uri, title, null,\n                width, height, orientation);\n        mImageSaver.waitDone();\n        mImageSaver.finish();\n    }\n\n    /**\n     * Queues a snapshot that will be taken as soon as possible\n     *\n     * @param save                 Whether or not to save the snapshot in gallery\n     *                             (for example, software HDR doesn't need all the shots to\n     *                             be saved)\n     * @param exposureCompensation If the shot has to be taken at a different\n     *                             exposure value, otherwise set it to 0\n     */\n    public void queueSnapshot(boolean save, int exposureCompensation) {\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO\n                && mContext.getResources().getBoolean(R.bool.config_usePreviewForVideoSnapshot)) {\n            // We use the preview data for the video snapshot, instead of queueing a normal snapshot\n            new Thread(mPreviewCaptureRunnable).start();\n            return;\n        }\n\n        if (mSnapshotsQueue.size() == 2) return; // No more than 2 shots at a time\n\n        // If we use Samsung HDR, we must set exposure level, as it corresponds to the HDR bracket\n        if (mContext.getResources().getBoolean(R.bool.config_useSamsungHDR) &&\n            SimpleToggleWidget.isWidgetEnabled(mContext, mCameraManager, \"scene-mode\", \"hdr\")) {\n            exposureCompensation = mCameraManager.getParameters().getMaxExposureCompensation();\n            mResetExposure = mCameraManager.getParameters().getExposureCompensation();\n        }\n\n        SnapshotInfo info = new SnapshotInfo();\n        info.mSave = save;\n        info.mExposureCompensation = exposureCompensation;\n        info.mThumbnail = mCameraManager.getLastPreviewFrame();\n        info.mBypassProcessing = mBypassProcessing;\n\n        Camera.Parameters params = mCameraManager.getParameters();\n        if (params != null && params.getExposureCompensation() != exposureCompensation) {\n            mCameraManager.setParameterAsync(\"exposure-compensation\",\n                    Integer.toString(exposureCompensation));\n            mWaitExposureSettle = true;\n        }\n        mSnapshotsQueue.add(info);\n\n        // Reset bypass in any case\n        mBypassProcessing = false;\n\n        if (mSnapshotsQueue.size() == 1) {\n            // We had no other snapshot queued so far, so start things up\n            Log.v(TAG, \"No snapshot queued, starting runnable\");\n\n            mCurrentShutterQueueIndex = 0;\n            new Thread(mCaptureRunnable).start();\n        }\n    }\n\n    public ImageNamer getImageNamer() {\n        return mImageNamer;\n    }\n\n    public ImageSaver getImageSaver() {\n        return mImageSaver;\n    }\n\n    public CamcorderProfile getVideoProfile(){\n        return mProfile;\n    }\n    \n    public void setVideoProfile(final CamcorderProfile profile) {\n        mProfile = profile;\n\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            // TODO: setVideoSize is handling preview changing\n            mCameraManager.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);\n        }\n    }\n\n    /**\n     * Starts recording a video with the current settings\n     */\n    public void startVideo() {\n        Log.v(TAG, \"startVideo\");\n\n        // Setup output file\n        generateVideoFilename(mProfile.fileFormat);\n        mCameraManager.prepareVideoRecording(mVideoFilename, mProfile);\n\n        mCameraManager.startVideoRecording();\n        mIsRecording = true;\n        mRecordingStartTime = SystemClock.uptimeMillis();\n\n        for (SnapshotListener listener : mListeners) {\n            listener.onVideoRecordingStart();\n        }\n    }\n\n    /**\n     * Stops the current recording video, if any\n     */\n    public void stopVideo() {\n        Log.v(TAG, \"stopVideo\");\n        if (mIsRecording) {\n            // Stop the video in a thread to not stall the UI thread\n            new Thread() {\n                public void run() {\n                    mCameraManager.stopVideoRecording();\n\n                    mHandler.post(new Runnable() {\n                        @Override\n                        public void run() {\n                            for (SnapshotListener listener : mListeners) {\n                                listener.onVideoRecordingStop();\n                                listener.onMediaSavingDone();\n                            }\n                        }\n                    });\n                }\n            }.start();\n\n            mCurrentVideoFilename = mVideoFilename;\n\n            mIsRecording = false;\n\n            for (SnapshotListener listener : mListeners) {\n                listener.onVideoRecordingStop();\n                listener.onMediaSavingStart();\n            }\n\n            addVideoToMediaStore();\n        }\n    }\n\n    /**\n     * Returns whether or not a video is recording\n     */\n    public boolean isRecording() {\n        return mIsRecording;\n    }\n\n    /**\n     * Add the last recorded video to the MediaStore\n     *\n     * @return True if operation succeeded\n     */\n    private boolean addVideoToMediaStore() {\n        boolean fail = false;\n        if (mVideoFileDescriptor == null) {\n            mCurrentVideoValues.put(MediaStore.Video.Media.SIZE,\n                    new File(mCurrentVideoFilename).length());\n            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;\n            if (duration > 0) {\n                mCurrentVideoValues.put(MediaStore.Video.Media.DURATION, duration);\n            } else {\n                Log.w(TAG, \"Video duration <= 0 : \" + duration);\n            }\n            try {\n                mCurrentVideoUri = mVideoNamer.getUri();\n\n                // Rename the video file to the final name. This avoids other\n                // apps reading incomplete data.  We need to do it after the\n                // above mVideoNamer.getUri() call, so we are certain that the\n                // previous insert to MediaProvider is completed.\n                String finalName = mCurrentVideoValues.getAsString(\n                        MediaStore.Video.Media.DATA);\n                if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {\n                    mCurrentVideoFilename = finalName;\n                }\n\n                mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues\n                        , null, null);\n                mContext.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO,\n                        mCurrentVideoUri));\n            } catch (Exception e) {\n                // We failed to insert into the database. This can happen if\n                // the SD card is unmounted.\n                Log.e(TAG, \"failed to add video to media store\", e);\n                mCurrentVideoUri = null;\n                mCurrentVideoFilename = null;\n                fail = true;\n            } finally {\n                Log.v(TAG, \"Current video URI: \" + mCurrentVideoUri);\n            }\n        }\n        mCurrentVideoValues = null;\n        return fail;\n    }\n\n    /**\n     * Generates a filename for the next video to record\n     *\n     * @param outputFileFormat The file format of the video\n     */\n    private void generateVideoFilename(int outputFileFormat) {\n        long dateTaken = System.currentTimeMillis();\n        String title = Util.createVideoName(dateTaken);\n        // Used when emailing.\n        String filename = title + convertOutputFormatToFileExt(outputFileFormat);\n        String mime = convertOutputFormatToMimeType(outputFileFormat);\n        String path = Storage.getStorage().generateDirectory() + '/' + filename;\n        String tmpPath = path + \".tmp\";\n        mCurrentVideoValues = new ContentValues(7);\n        mCurrentVideoValues.put(MediaStore.Video.Media.TITLE, title);\n        mCurrentVideoValues.put(MediaStore.Video.Media.DISPLAY_NAME, filename);\n        mCurrentVideoValues.put(MediaStore.Video.Media.DATE_TAKEN, dateTaken);\n        mCurrentVideoValues.put(MediaStore.Video.Media.MIME_TYPE, mime);\n        mCurrentVideoValues.put(MediaStore.Video.Media.DATA, path);\n        mCurrentVideoValues.put(MediaStore.Video.Media.RESOLUTION,\n                Integer.toString(mProfile.videoFrameWidth) + \"x\" +\n                        Integer.toString(mProfile.videoFrameHeight));\n        Location loc = null; // TODO: mLocationManager.getCurrentLocation();\n        if (loc != null) {\n            mCurrentVideoValues.put(MediaStore.Video.Media.LATITUDE, loc.getLatitude());\n            mCurrentVideoValues.put(MediaStore.Video.Media.LONGITUDE, loc.getLongitude());\n        }\n        mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);\n        mVideoFilename = tmpPath;\n        Log.v(TAG, \"New video filename: \" + mVideoFilename);\n    }\n\n    private String convertOutputFormatToMimeType(int outputFileFormat) {\n        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {\n            return \"video/mp4\";\n        }\n        return \"video/3gpp\";\n    }\n\n    private String convertOutputFormatToFileExt(int outputFileFormat) {\n        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {\n            return \".mp4\";\n        }\n        return \".3gp\";\n    }\n\n    public void onPause() {\n        mPaused = true;\n\n        if (!mImageIsProcessing && mImageSaver != null) {\n            // We wait until the last processing image was saved\n            mImageSaver.finish();\n        }\n        mImageNamer.finish();\n        mVideoNamer.finish();\n        mImageSaver = null;\n        mImageNamer = null;\n        mVideoNamer = null;\n    }\n\n    public void onResume() {\n        mPaused = false;\n\n        // Restore threads if needed\n        if (mImageSaver == null) {\n            mImageSaver = new ImageSaver();\n        }\n\n        if (mImageNamer == null) {\n            mImageNamer = new ImageNamer();\n        }\n\n        if (mVideoNamer == null) {\n            mVideoNamer = new VideoNamer();\n        }\n    }\n\n    // Each SaveRequest remembers the data needed to save an image.\n    private static class SaveRequest {\n        byte[] data;\n        Uri uri;\n        String title;\n        Location loc;\n        int width, height;\n        int orientation;\n        List<Tag> exifTags;\n        SnapshotInfo snap;\n    }\n\n    // We use a queue to store the SaveRequests that have not been completed\n    // yet. The main thread puts the request into the queue. The saver thread\n    // gets it from the queue, does the work, and removes it from the queue.\n    //\n    // The main thread needs to wait for the saver thread to finish all the work\n    // in the queue, when the activity's onPause() is called, we need to finish\n    // all the work, so other programs (like Gallery) can see all the images.\n    //\n    // If the queue becomes too long, adding a new request will block the main\n    // thread until the queue length drops below the threshold (QUEUE_LIMIT).\n    // If we don't do this, we may face several problems: (1) We may OOM\n    // because we are holding all the jpeg data in memory. (2) We may ANR\n    // when we need to wait for saver thread finishing all the work (in\n    // onPause() or gotoGallery()) because the time to finishing a long queue\n    // of work may be too long.\n    private class ImageSaver extends Thread {\n        private static final int QUEUE_LIMIT = 3;\n\n        private ArrayList<SaveRequest> mQueue;\n        private boolean mStop;\n\n        // Runs in main thread\n        public ImageSaver() {\n            mQueue = new ArrayList<SaveRequest>();\n            start();\n        }\n\n        // Runs in main thread\n        public void addImage(final byte[] data, Uri uri, String title,\n                             Location loc, int width, int height, int orientation, SnapshotInfo snap) {\n            addImage(data, uri, title, loc, width, height, orientation, null, snap);\n        }\n\n        // Runs in main thread\n        public void addImage(final byte[] data, Uri uri, String title,\n                             Location loc, int width, int height, int orientation) {\n           addImage(data, uri, title, loc, width, height, orientation, null, null);\n        }\n\n        // Runs in main thread\n        public void addImage(final byte[] data, Uri uri, String title,\n                             Location loc, int width, int height, int orientation,\n                             List<Tag> exifTags, SnapshotInfo snap) {\n            SaveRequest r = new SaveRequest();\n            r.data = data;\n            r.uri = uri;\n            r.title = title;\n            r.loc = (loc == null) ? null : new Location(loc);  // make a copy\n            r.width = width;\n            r.height = height;\n            r.orientation = orientation;\n            r.exifTags = exifTags;\n            r.snap = snap;\n            synchronized (this) {\n                while (mQueue.size() >= QUEUE_LIMIT) {\n                    try {\n                        wait();\n                    } catch (InterruptedException ex) {\n                        // ignore.\n                    }\n                }\n                mQueue.add(r);\n                notifyAll();  // Tell saver thread there is new work to do.\n            }\n        }\n\n        // Runs in saver thread\n        @Override\n        public void run() {\n            while (true) {\n                SaveRequest r;\n                synchronized (this) {\n                    if (mQueue.isEmpty()) {\n                        notifyAll();  // notify main thread in waitDone\n\n                        // Note that we can only stop after we saved all images\n                        // in the queue.\n                        if (mStop) break;\n\n                        try {\n                            wait();\n                        } catch (InterruptedException ex) {\n                            // ignore.\n                        }\n                        continue;\n                    }\n                    r = mQueue.get(0);\n                    for (SnapshotListener listener : mListeners) {\n                        listener.onMediaSavingStart();\n                    }\n                }\n                storeImage(r.data, r.uri, r.title, r.loc, r.width, r.height,\n                        r.orientation, r.exifTags, r.snap);\n                synchronized (this) {\n                    mQueue.remove(0);\n                    for (SnapshotListener listener : mListeners) {\n                        listener.onMediaSavingDone();\n                    }\n                    notifyAll();  // the main thread may wait in addImage\n                }\n            }\n        }\n\n        // Runs in main thread\n        public void waitDone() {\n            synchronized (this) {\n                while (!mQueue.isEmpty()) {\n                    try {\n                        wait();\n                    } catch (InterruptedException ex) {\n                        // ignore.\n                    }\n                }\n            }\n        }\n\n        // Runs in main thread\n        public void finish() {\n            waitDone();\n            synchronized (this) {\n                mStop = true;\n                notifyAll();\n            }\n            try {\n                join();\n            } catch (InterruptedException ex) {\n                // ignore.\n            }\n        }\n\n        // Runs in saver thread\n        private void storeImage(final byte[] data, Uri uri, String title,\n                                Location loc, int width, int height, int orientation,\n                                List<Tag> exifTags, SnapshotInfo snap) {\n            boolean ok = Storage.getStorage().updateImage(mContentResolver, uri, title, loc,\n                    orientation, data, width, height);\n\n            if (ok) {\n                if (exifTags != null && exifTags.size() > 0) {\n                    // Write exif tags to final picture\n                    try {\n                        ExifInterface exifIf = new ExifInterface(Util.getRealPathFromURI(mContext, uri));\n\n                        for (Tag tag : exifTags) {\n                            // move along\n                            String[] hack = tag.toString().split(\"\\\\]\");\n                            hack = hack[1].split(\"-\");\n                            exifIf.setAttribute(tag.getTagName(), hack[1].trim());\n                        }\n\n                        exifIf.saveAttributes();\n                    } catch (IOException e) {\n                        Log.e(TAG, \"Couldn't write exif\", e);\n                    } catch (CursorIndexOutOfBoundsException e) {\n                        Log.e(TAG, \"Couldn't find original picture\", e);\n                    }\n                }\n\n                Util.broadcastNewPicture(mContext, uri);\n            }\n\n            if (snap != null) {\n                for (SnapshotListener listener : mListeners) {\n                    listener.onSnapshotSaved(snap);\n                }\n            }\n        }\n    }\n\n    private static class ImageNamer extends Thread {\n        private boolean mRequestPending;\n        private ContentResolver mResolver;\n        private long mDateTaken;\n        private int mWidth, mHeight;\n        private boolean mStop;\n        private Uri mUri;\n        private String mTitle;\n\n        // Runs in main thread\n        public ImageNamer() {\n            start();\n        }\n\n        // Runs in main thread\n        public synchronized void prepareUri(ContentResolver resolver,\n                long dateTaken, int width, int height, int rotation) {\n            if (rotation % 180 != 0) {\n                int tmp = width;\n                width = height;\n                height = tmp;\n            }\n            mRequestPending = true;\n            mResolver = resolver;\n            mDateTaken = dateTaken;\n            mWidth = width;\n            mHeight = height;\n            notifyAll();\n        }\n\n        // Runs in main thread\n        public synchronized Uri getUri() {\n            // wait until the request is done.\n            while (mRequestPending) {\n                try {\n                    wait();\n                } catch (InterruptedException ex) {\n                    // Do nothing here\n                }\n            }\n\n            // Return the uri generated\n            Uri uri = mUri;\n            mUri = null;\n            return uri;\n        }\n\n        // Runs in main thread, should be called after getUri().\n        public synchronized String getTitle() {\n            return mTitle;\n        }\n\n        // Runs in namer thread\n        @Override\n        public synchronized void run() {\n            while (true) {\n                if (mStop) break;\n                if (!mRequestPending) {\n                    try {\n                        wait();\n                    } catch (InterruptedException ex) {\n                        // ignore.\n                    }\n                    continue;\n                }\n                cleanOldUri();\n                generateUri();\n                mRequestPending = false;\n                notifyAll();\n            }\n            cleanOldUri();\n        }\n\n        // Runs in main thread\n        public synchronized void finish() {\n            mStop = true;\n            notifyAll();\n        }\n\n        // Runs in namer thread\n        private void generateUri() {\n            mTitle = Util.createJpegName(mDateTaken);\n            mUri = Storage.getStorage().newImage(mResolver, mTitle, mDateTaken, mWidth, mHeight);\n        }\n\n        // Runs in namer thread\n        private void cleanOldUri() {\n            if (mUri == null) return;\n            Storage.getStorage().deleteImage(mResolver, mUri);\n            mUri = null;\n        }\n    }\n\n\n    private static class VideoNamer extends Thread {\n        private boolean mRequestPending;\n        private ContentResolver mResolver;\n        private ContentValues mValues;\n        private boolean mStop;\n        private Uri mUri;\n\n        // Runs in main thread\n        public VideoNamer() {\n            start();\n        }\n\n        // Runs in main thread\n        public synchronized void prepareUri(\n                ContentResolver resolver, ContentValues values) {\n            mRequestPending = true;\n            mResolver = resolver;\n            mValues = new ContentValues(values);\n            notifyAll();\n        }\n\n        // Runs in main thread\n        public synchronized Uri getUri() {\n            // wait until the request is done.\n            while (mRequestPending) {\n                try {\n                    wait();\n                } catch (InterruptedException ex) {\n                    // ignore.\n                }\n            }\n            Uri uri = mUri;\n            mUri = null;\n            return uri;\n        }\n\n        // Runs in namer thread\n        @Override\n        public synchronized void run() {\n            while (true) {\n                if (mStop) break;\n                if (!mRequestPending) {\n                    try {\n                        wait();\n                    } catch (InterruptedException ex) {\n                        // Do nothing here\n                    }\n                    continue;\n                }\n                cleanOldUri();\n                generateUri();\n                mRequestPending = false;\n                notifyAll();\n            }\n            cleanOldUri();\n        }\n\n        // Runs in main thread\n        public synchronized void finish() {\n            mStop = true;\n            notifyAll();\n        }\n\n        // Runs in namer thread\n        private void generateUri() {\n            Uri videoTable = Uri.parse(\"content://media/external/video/media\");\n            mUri = mResolver.insert(videoTable, mValues);\n        }\n\n        // Runs in namer thread\n        private void cleanOldUri() {\n            if (mUri == null) return;\n            mResolver.delete(mUri, null, null);\n            mUri = null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/SoundManager.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.media.SoundPool;\n\nimport fr.xplod.focal.R;\n\n/**\n * Manages sounds played by the app. Since we have a limited\n * set of sounds that can be played, we pre-load them and we use\n * hardcoded values to play them quickly.\n */\npublic class SoundManager {\n    public final static int SOUND_SHUTTER = 0;\n    private final static int SOUND_MAX = 1;\n\n    private static SoundManager mSingleton;\n\n    public static SoundManager getSingleton() {\n        if (mSingleton == null) {\n            mSingleton = new SoundManager();\n        }\n\n        return mSingleton;\n    }\n\n    private SoundPool mSoundPool;\n    private int[] mSoundsFD = new int[SOUND_MAX];\n\n    /**\n     * Default constructor\n     * Creates the sound pool to play the audio files. Make sure to\n     * call preload() before doing anything so the sounds are loaded!\n     */\n    private SoundManager() {\n        mSoundPool = new SoundPool(3, AudioManager.STREAM_NOTIFICATION, 0);\n    }\n\n    public void preload(Context ctx) {\n        mSoundsFD[SOUND_SHUTTER] = mSoundPool.load(ctx, R.raw.snd_capture, 1);\n    }\n\n    /**\n     * Immediately play the specified sound\n     *\n     * @param sound The sound to play, see SoundManager.SOUND_*\n     * @note Make sure preload() was called before doing play!\n     */\n    public void play(int sound) {\n        mSoundPool.play(mSoundsFD[sound], 1.0f, 1.0f, 0, 0, 1.0f);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/Storage.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.location.Location;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.StatFs;\nimport android.provider.MediaStore.Images;\nimport android.provider.MediaStore.Images.ImageColumns;\nimport android.provider.MediaStore.MediaColumns;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\n\npublic class Storage {\n    private static final String TAG = \"CameraStorage\";\n\n    public static final long UNAVAILABLE = -1L;\n    public static final long PREPARING = -2L;\n    public static final long UNKNOWN_SIZE = -3L;\n    public static final long LOW_STORAGE_THRESHOLD = 50000000;\n\n    private String mRoot = Environment.getExternalStorageDirectory().toString();\n    private static Storage sStorage;\n\n    // Singleton\n    private Storage() {\n        // Do nothing here\n    }\n\n    public static Storage getStorage() {\n        if (sStorage == null) {\n            sStorage = new Storage();\n        }\n\n        return sStorage;\n    }\n\n    public void setRoot(String root) {\n        mRoot = root;\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    private static void setImageSize(ContentValues values, int width, int height) {\n        values.put(MediaColumns.WIDTH, width);\n        values.put(MediaColumns.HEIGHT, height);\n    }\n\n    public String writeFile(String title, byte[] data) {\n        String path = generateFilepath(title);\n        FileOutputStream out = null;\n        try {\n            out = new FileOutputStream(path);\n            out.write(data);\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to write data\", e);\n        } finally {\n            try {\n                out.close();\n            } catch (Exception e) {\n            }\n        }\n        return path;\n    }\n\n    // Save the image and add it to media store.\n    public Uri addImage(ContentResolver resolver, String title,\n            long date, Location location, int orientation, byte[] jpeg,\n            int width, int height) {\n        // Save the image.\n        String path = writeFile(title, jpeg);\n        return addImage(resolver, title, date, location, orientation,\n                jpeg.length, path, width, height);\n    }\n\n    // Add the image to media store.\n    public Uri addImage(ContentResolver resolver, String title,\n            long date, Location location, int orientation, int jpegLength,\n            String path, int width, int height) {\n        // Insert into MediaStore.\n        ContentValues values = new ContentValues(9);\n        values.put(ImageColumns.TITLE, title);\n        values.put(ImageColumns.DISPLAY_NAME, title + \".jpg\");\n        values.put(ImageColumns.DATE_TAKEN, date);\n        values.put(ImageColumns.MIME_TYPE, \"image/jpeg\");\n\n        // Clockwise rotation in degrees. 0, 90, 180, or 270.\n        values.put(ImageColumns.ORIENTATION, orientation);\n        values.put(ImageColumns.DATA, path);\n        values.put(ImageColumns.SIZE, jpegLength);\n\n        setImageSize(values, width, height);\n\n        if (location != null) {\n            values.put(ImageColumns.LATITUDE, location.getLatitude());\n            values.put(ImageColumns.LONGITUDE, location.getLongitude());\n        }\n\n        Uri uri = null;\n        try {\n            uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);\n        } catch (Throwable th) {\n            // This can happen when the external volume is already mounted, but\n            // MediaScanner has not notify MediaProvider to add that volume.\n            // The picture is still safe and MediaScanner will find it and\n            // insert it into MediaProvider. The only problem is that the user\n            // cannot click the thumbnail to review the picture.\n            Log.e(TAG, \"Failed to write MediaStore\" + th);\n        }\n        return uri;\n    }\n\n    // nullewImage() and updateImage() together do the same work as\n    // addImage. newImage() is the first step, and it inserts the DATE_TAKEN and\n    // DATA fields into the database.\n    //\n    // We also insert hint values for the WIDTH and HEIGHT fields to give\n    // correct aspect ratio before the real values are updated in updateImage().\n    public Uri newImage(ContentResolver resolver, String title,\n                        long date, int width, int height) {\n        String path = generateFilepath(title);\n\n        // Insert into MediaStore.\n        ContentValues values = new ContentValues(4);\n        values.put(ImageColumns.DATE_TAKEN, date);\n        values.put(ImageColumns.DATA, path);\n\n        setImageSize(values, width, height);\n\n        Uri uri = null;\n        try {\n            uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);\n        } catch (Throwable th) {\n            // This can happen when the external volume is already mounted, but\n            // MediaScanner has not notify MediaProvider to add that volume.\n            // The picture is still safe and MediaScanner will find it and\n            // insert it into MediaProvider. The only problem is that the user\n            // cannot click the thumbnail to review the picture.\n            Log.e(TAG, \"Failed to new image\" + th);\n        }\n        return uri;\n    }\n\n    // This is the second step. It completes the partial data added by\n    // newImage. All columns other than DATE_TAKEN and DATA are inserted\n    // here. This method also save the image data into the file.\n    //\n    // Returns true if the update is successful.\n    public boolean updateImage(ContentResolver resolver, Uri uri,\n            String title, Location location, int orientation, byte[] jpeg,\n            int width, int height) {\n        // Save the image.\n        String path = generateFilepath(title);\n        String tmpPath = path + \".tmp\";\n        FileOutputStream out = null;\n        try {\n            // Write to a temporary file and rename it to the final name. This\n            // avoids other apps reading incomplete data.\n            out = new FileOutputStream(tmpPath);\n            out.write(jpeg);\n            out.close();\n            new File(tmpPath).renameTo(new File(path));\n        } catch (Exception e) {\n            Log.e(TAG, \"Failed to write image\", e);\n            return false;\n        } finally {\n            try {\n                out.close();\n            } catch (Exception e) {\n                // Do nothing here\n            }\n        }\n\n        // Insert into MediaStore.\n        ContentValues values = new ContentValues(9);\n        values.put(ImageColumns.TITLE, title);\n        values.put(ImageColumns.DISPLAY_NAME, title + \".jpg\");\n        values.put(ImageColumns.MIME_TYPE, \"image/jpeg\");\n\n        // Clockwise rotation in degrees. 0, 90, 180, or 270.\n        values.put(ImageColumns.ORIENTATION, orientation);\n        values.put(ImageColumns.SIZE, jpeg.length);\n\n        setImageSize(values, width, height);\n\n        if (location != null) {\n            values.put(ImageColumns.LATITUDE, location.getLatitude());\n            values.put(ImageColumns.LONGITUDE, location.getLongitude());\n        }\n\n        try {\n            resolver.update(uri, values, null, null);\n        } catch (Throwable th) {\n            Log.e(TAG, \"Failed to update image (\" + th + \") ; uri=\" + uri + \" values=\" + values);\n            return false;\n        }\n\n        return true;\n    }\n\n    public void deleteImage(ContentResolver resolver, Uri uri) {\n        try {\n            resolver.delete(uri, null, null);\n        } catch (Throwable th) {\n            Log.e(TAG, \"Failed to delete image: \" + uri);\n        }\n    }\n\n    private String generateDCIM() {\n        return new File(mRoot, Environment.DIRECTORY_DCIM).toString();\n    }\n\n    public String generateDirectory() {\n        return generateDCIM() + \"/Camera\";\n    }\n\n    private String generateFilepath(String title) {\n        return generateDirectory() + '/' + title + \".jpg\";\n    }\n\n    public String generateBucketId() {\n        return String.valueOf(generateDirectory().toLowerCase().hashCode());\n    }\n\n    public int generateBucketIdInt() {\n        return generateDirectory().toLowerCase().hashCode();\n    }\n\n    public long getAvailableSpace() {\n        String state = Environment.getExternalStorageState();\n        Log.d(TAG, \"External storage state=\" + state);\n        if (Environment.MEDIA_CHECKING.equals(state)) {\n            return PREPARING;\n        }\n        if (!Environment.MEDIA_MOUNTED.equals(state)) {\n            return UNAVAILABLE;\n        }\n\n        File dir = new File(generateDirectory());\n        dir.mkdirs();\n        if (!dir.isDirectory() || !dir.canWrite()) {\n            return UNAVAILABLE;\n        }\n\n        try {\n            StatFs stat = new StatFs(generateDirectory());\n            return stat.getAvailableBlocks() * (long) stat.getBlockSize();\n        } catch (Exception e) {\n            Log.i(TAG, \"Fail to access external storage\", e);\n        }\n        return UNKNOWN_SIZE;\n    }\n\n    /**\n     * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be\n     * imported. This is a temporary fix for bug#1655552.\n     */\n    public void ensureOSXCompatible() {\n        File nnnAAAAA = new File(generateDCIM(), \"100ANDRO\");\n        if (!(nnnAAAAA.exists() || nnnAAAAA.mkdirs())) {\n            Log.e(TAG, \"Failed to create \" + nnnAAAAA.getPath());\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/Util.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.app.Activity;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.graphics.Point;\nimport android.hardware.Camera.Size;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport android.renderscript.Allocation;\nimport android.renderscript.Element;\nimport android.renderscript.RSIllegalArgumentException;\nimport android.renderscript.RenderScript;\nimport android.renderscript.ScriptIntrinsicYuvToRGB;\nimport android.renderscript.Type;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.OrientationEventListener;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.view.animation.AlphaAnimation;\nimport android.view.animation.Animation;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\n\npublic class Util {\n    public final static String TAG = \"Nemesis.Util\";\n\n    // Orientation hysteresis amount used in rounding, in degrees\n    public static final int ORIENTATION_HYSTERESIS = 5;\n\n    public static final String REVIEW_ACTION = \"com.android.camera.action.REVIEW\";\n    // See android.hardware.Camera.ACTION_NEW_PICTURE.\n    public static final String ACTION_NEW_PICTURE = \"android.hardware.action.NEW_PICTURE\";\n    // See android.hardware.Camera.ACTION_NEW_VIDEO.\n    public static final String ACTION_NEW_VIDEO = \"android.hardware.action.NEW_VIDEO\";\n\n    // Screen size holder\n    private static Point mScreenSize = new Point();\n    private static int mRotation = 90;\n\n    private static DateFormat mJpegDateFormat = new SimpleDateFormat(\"yyyyMMdd_HHmmssSSS\");\n\n    /**\n     * Returns the orientation of the display\n     * In our case, since we're locked in Landscape, it should always\n     * be 90\n     *\n     * @param activity\n     * @return Orientation angle of the display\n     */\n    public static int getDisplayRotation(Activity activity) {\n        if (activity != null) {\n            int rotation = activity.getWindowManager().getDefaultDisplay()\n                    .getRotation();\n            switch (rotation) {\n                case Surface.ROTATION_0:\n                    mRotation = 0;\n                    break;\n                case Surface.ROTATION_90:\n                    mRotation = 90;\n                    break;\n                case Surface.ROTATION_180:\n                    mRotation = 180;\n                    break;\n                case Surface.ROTATION_270:\n                    mRotation = 270;\n                    break;\n            }\n        }\n\n        return mRotation;\n    }\n\n    /**\n     * Rounds the orientation so that the UI doesn't rotate if the user\n     * holds the device towards the floor or the sky\n     *\n     * @param orientation        New orientation\n     * @param orientationHistory Previous orientation\n     * @return Rounded orientation\n     */\n    public static int roundOrientation(int orientation, int orientationHistory) {\n        boolean changeOrientation = false;\n        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {\n            changeOrientation = true;\n        } else {\n            int dist = Math.abs(orientation - orientationHistory);\n            dist = Math.min(dist, 360 - dist);\n            changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);\n        }\n        if (changeOrientation) {\n            return ((orientation + 45) / 90 * 90) % 360;\n        }\n        return orientationHistory;\n    }\n\n    /**\n     * Returns the size of the screen\n     *\n     * @param activity\n     * @return Point where x=width and y=height\n     */\n    public static Point getScreenSize(Activity activity) {\n        if (activity != null) {\n            WindowManager service =\n                    (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);\n            service.getDefaultDisplay().getSize(mScreenSize);\n        }\n        return mScreenSize;\n    }\n\n    /**\n     * Returns the optimal preview size for photo shots\n     *\n     * @param currentActivity\n     * @param sizes\n     * @param targetRatio\n     * @return\n     */\n    public static Size getOptimalPreviewSize(Activity currentActivity,\n            List<Size> sizes, double targetRatio) {\n        // Use a very small tolerance because we want an exact match.\n        final double ASPECT_TOLERANCE = 0.01;\n        if (sizes == null) {\n            return null;\n        }\n\n        Size optimalSize = null;\n        double minDiff = Double.MAX_VALUE;\n\n        // Because of bugs of overlay and layout, we sometimes will try to\n        // layout the viewfinder in the portrait orientation and thus get the\n        // wrong size of preview surface. When we change the preview size, the\n        // new overlay will be created before the old one closed, which causes\n        // an exception. For now, just get the screen size.\n        Point point = getScreenSize(currentActivity);\n        int targetHeight = Math.min(point.x, point.y);\n        // Try to find an size match aspect ratio and size\n        for (Size size : sizes) {\n            double ratio = (double) size.width / size.height;\n            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {\n                continue;\n            }\n            if (Math.abs(size.height - targetHeight) < minDiff) {\n                optimalSize = size;\n                minDiff = Math.abs(size.height - targetHeight);\n            }\n        }\n        // Cannot find the one match the aspect ratio. This should not happen.\n        // Ignore the requirement.\n        if (optimalSize == null) {\n            Log.w(TAG, \"No preview size match the aspect ratio\");\n            minDiff = Double.MAX_VALUE;\n            for (Size size : sizes) {\n                if (Math.abs(size.height - targetHeight) < minDiff) {\n                    optimalSize = size;\n                    minDiff = Math.abs(size.height - targetHeight);\n                }\n            }\n        }\n        return optimalSize;\n    }\n\n    /**\n     * Fade in a view from startAlpha to endAlpha during duration milliseconds\n     * @param view\n     * @param startAlpha\n     * @param endAlpha\n     * @param duration\n     */\n    public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {\n        if (view.getVisibility() == View.VISIBLE) {\n            return;\n        }\n\n        view.setVisibility(View.VISIBLE);\n        Animation animation = new AlphaAnimation(startAlpha, endAlpha);\n        animation.setDuration(duration);\n        view.startAnimation(animation);\n    }\n\n    /**\n     * Fade in a view with the default time\n     * @param view\n     */\n    public static void fadeIn(View view) {\n        fadeIn(view, 0F, 1F, 400);\n\n        // We disabled the button in fadeOut(), so enable it here.\n        view.setEnabled(true);\n    }\n\n    public static void fadeOut(View view) {\n        if (view.getVisibility() != View.VISIBLE) return;\n\n        // Since the button is still clickable before fade-out animation\n        // ends, we disable the button first to block click.\n        view.setEnabled(false);\n        Animation animation = new AlphaAnimation(1F, 0F);\n        animation.setDuration(400);\n        view.startAnimation(animation);\n        view.setVisibility(View.GONE);\n    }\n\n    /**\n     * Converts the provided byte array from YUV420SP into an RGBA bitmap.\n     * @param context\n     * @param yuv420sp The YUV420SP data\n     * @param width Width of the data's picture\n     * @param height Height of the data's picture\n     * @return A decoded bitmap\n     * @throws NullPointerException\n     * @throws IllegalArgumentException\n     */\n    public static Bitmap decodeYUV420SP(Context context, byte[] yuv420sp, int width, int height)\n            throws NullPointerException, IllegalArgumentException {\n\n        Bitmap bmp = null;\n\n        if (Build.VERSION.SDK_INT >= 17) {\n            final RenderScript rs = RenderScript.create(context);\n            final ScriptIntrinsicYuvToRGB script = ScriptIntrinsicYuvToRGB\n                    .create(rs, Element.RGBA_8888(rs));\n            Type.Builder tb = new Type.Builder(rs, Element.RGBA_8888(rs));\n            tb.setX(width);\n            tb.setY(height);\n\n            Allocation allocationOut = Allocation.createTyped(rs, tb.create());\n            Allocation allocationIn = Allocation.createSized(rs, Element.U8(rs),\n                    (height * width) + ((height) * (width) * 2));\n\n            script.setInput(allocationIn);\n\n            bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n            try {\n                allocationIn.copyFrom(yuv420sp);\n                script.forEach(allocationOut);\n                allocationOut.copyTo(bmp);\n            } catch (RSIllegalArgumentException ex) {\n                Log.e(TAG, \"Cannot copy YUV420SP data\", ex);\n            }\n        } else {\n            final int frameSize = width * height;\n            int[] rgb = new int[frameSize];\n\n            for (int j = 0, yp = 0; j < height; j++) {\n                int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;\n                for (int i = 0; i < width; i++, yp++) {\n                    int y = (0xff & ((int) yuv420sp[yp])) - 16;\n                    if (y < 0) {\n                        y = 0;\n                    }\n                    if ((i & 1) == 0) {\n                        v = (0xff & yuv420sp[uvp++]) - 128;\n                        u = (0xff & yuv420sp[uvp++]) - 128;\n                    }\n\n                    int y1192 = 1192 * y;\n                    int r = (y1192 + 1634 * v);\n                    int g = (y1192 - 833 * v - 400 * u);\n                    int b = (y1192 + 2066 * u);\n\n                    if (r < 0) {\n                        r = 0;\n                    } else if (r > 262143) {\n                        r = 262143;\n                    }\n                    if (g < 0) {\n                        g = 0;\n                    } else if (g > 262143) {\n                        g = 262143;\n                    }\n                    if (b < 0) {\n                        b = 0;\n                    } else if (b > 262143) {\n                        b = 262143;\n                    }\n\n                    rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00)\n                            | ((b >> 10) & 0xff);\n                }\n            }\n\n            bmp = Bitmap.createBitmap(rgb, width, height, Bitmap.Config.ARGB_8888);\n        }\n\n        return bmp;\n    }\n\n    public static Bitmap decodeYUV422P(byte[] yuv422p, int width, int height)\n            throws NullPointerException, IllegalArgumentException {\n        final int frameSize = width * height;\n        int[] rgb = new int[frameSize];\n        for (int j = 0, yp = 0; j < height; j++) {\n            int up = frameSize + (j * (width/2)), u = 0, v = 0;\n            int vp = ((int)(frameSize*1.5) + (j*(width/2)));\n            for (int i = 0; i < width; i++, yp++) {\n                int y = (0xff & ((int) yuv422p[yp])) - 16;\n                if (y < 0) {\n                    y = 0;\n                }\n\n                if ((i & 1) == 0) {\n                    u = (0xff & yuv422p[up++]) - 128;\n                    v = (0xff & yuv422p[vp++]) - 128;\n                }\n\n                int y1192 = 1192 * y;\n                int r = (y1192 + 1634 * v);\n                int g = (y1192 - 833 * v - 400 * u);\n                int b = (y1192 + 2066 * u);\n\n                if (r < 0) {\n                    r = 0;\n                } else if (r > 262143) {\n                    r = 262143;\n                }\n                if (g < 0) {\n                    g = 0;\n                } else if (g > 262143) {\n                    g = 262143;\n                }\n                if (b < 0) {\n                    b = 0;\n                } else if (b > 262143) {\n                    b = 262143;\n                }\n\n                rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00)\n                        | ((b >> 10) & 0xff);\n            }\n        }\n\n        return Bitmap.createBitmap(rgb, width, height, Bitmap.Config.ARGB_8888);\n    }\n\n    public static String createJpegName(long dateTaken) {\n        return \"IMG_\" + mJpegDateFormat.format(new Date(dateTaken));\n    }\n\n    public static String createVideoName(long dateTaken) {\n        return \"VID_\" + mJpegDateFormat.format(new Date(dateTaken));\n    }\n\n    /**\n     * Broadcast an intent to notify of a new picture in the Gallery\n     * @param context\n     * @param uri\n     */\n    public static void broadcastNewPicture(Context context, Uri uri) {\n        context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));\n        // Keep compatibility\n        context.sendBroadcast(new Intent(\"com.android.camera.NEW_PICTURE\", uri));\n    }\n\n    /**\n     * Removes an image from the gallery\n     * @param cr\n     * @param id\n     */\n    public static void removeFromGallery(ContentResolver cr, long id) {\n        cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,\n                BaseColumns._ID + \"=\" + Long.toString(id), null);\n    }\n\n    /**\n     * Converts the specified DP to PIXELS according to current screen density\n     * @param context\n     * @param dp\n     * @return\n     */\n    public static float dpToPx(Context context, float dp) {\n        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();\n        return (int) ((dp * displayMetrics.density) + 0.5);\n    }\n\n    /**\n     * Returns the physical path (on emmc/sd) of the provided URI from MediaGallery\n     * @param context\n     * @param contentURI\n     * @return\n     */\n    public static String getRealPathFromURI(Context context, Uri contentURI) {\n        Cursor cursor = context.getContentResolver().query(contentURI, null, null, null, null);\n        if (cursor == null) { // Source is Dropbox or other similar local file path\n            return contentURI.getPath();\n        } else {\n            cursor.moveToFirst();\n            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);\n            return cursor.getString(idx);\n        }\n    }\n\n\n    /**\n     * Returns the best Panorama preview size\n     * @param supportedSizes\n     * @param need4To3\n     * @param needSmaller\n     * @return\n     */\n    public static Point findBestPanoPreviewSize(List<Size> supportedSizes, boolean need4To3,\n                                                boolean needSmaller, int defaultPixels) {\n        Point output = null;\n        int pixelsDiff = defaultPixels;\n\n        for (Size size : supportedSizes) {\n            int h = size.height;\n            int w = size.width;\n            // we only want 4:3 format.\n            int d = defaultPixels - h * w;\n            if (needSmaller && d < 0) { // no bigger preview than 960x720.\n                continue;\n            }\n            if (need4To3 && (h * 4 != w * 3)) {\n                continue;\n            }\n            d = Math.abs(d);\n            if (d < pixelsDiff) {\n                output = new Point(w, h);\n                pixelsDiff = d;\n            }\n        }\n        return output;\n    }\n\n    /**\n     * Returns the best PicSphere picture size. The reference size is 2048x1536 from Nexus 4.\n     * @param supportedSizes Supported picture size\n     * @param needSmaller If a larger image size is accepted\n     * @return A Point where X and Y corresponds to Width and Height\n     */\n    public static Point findBestPicSpherePictureSize(List<Size> supportedSizes, boolean needSmaller) {\n        Point output = null;\n        final int defaultPixels = 2048*1536;\n\n        int pixelsDiff = defaultPixels;\n\n        for (Size size : supportedSizes) {\n            int h = size.height;\n            int w = size.width;\n            int d = defaultPixels - h * w;\n\n            if (needSmaller && d < 0) { // no bigger preview than 960x720.\n                continue;\n            }\n\n            // we only want 4:3 format.\n            if ((h * 4 != w * 3)) {\n                continue;\n            }\n            d = Math.abs(d);\n            if (d < pixelsDiff) {\n                output = new Point(w, h);\n                pixelsDiff = d;\n            }\n        }\n\n        if (output == null) {\n            // Fail-safe, default to 640x480 is nothing suitable is found\n            output = new Point(640, 480);\n        }\n\n        return output;\n    }\n\n    /**\n     * Older devices need to stop preview before taking a shot\n     * (example: galaxy S, galaxy S2, etc)\n     * @return true if the device is an old one\n     */\n    public static boolean deviceNeedsStopPreviewToShoot() {\n        String[] oldDevices = {\"smdk4210\", \"aries\"};\n\n        boolean needs = Arrays.asList(oldDevices).contains(Build.BOARD);\n\n        Log.e(TAG, \"Device \" + Build.BOARD + (needs ? \" needs \": \" doesn't need \") + \"to stop preview\");\n        return needs;\n    }\n\n    /**\n     * Disable ZSL mode on certain Qualcomm models\n     * @return true if the device needs ZSL disabled\n     */\n    public static boolean deviceNeedsDisableZSL() {\n        String[] noZSL = {\"apexqtmo\", \"expressatt\", \"aegis2vzw\"};\n\n        boolean needs = Arrays.asList(noZSL).contains(Build.PRODUCT);\n\n        Log.e(TAG, \"Device \" + Build.PRODUCT + (needs ? \" needs \": \" doesn't need \") + \"to disable ZSL\");\n        return needs;\n    }\n\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/WidgetProvider.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\nimport android.app.KeyguardManager;\nimport android.app.PendingIntent;\nimport android.appwidget.AppWidgetManager;\nimport android.appwidget.AppWidgetProvider;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.widget.RemoteViews;\n\nimport fr.xplod.focal.R;\n\npublic class WidgetProvider extends AppWidgetProvider {\n    public void onUpdate(final Context context, AppWidgetManager appWidgetManager,\n            int[] appWidgetIds) {\n        final int N = appWidgetIds.length;\n\n        // Perform this loop procedure for each App Widget\n        // that belongs to this provider\n        for (int i=0; i<N; i++) {\n            int appWidgetId = appWidgetIds[i];\n\n            // Create an Intent to launch CameraActivity\n            Intent intent = new Intent(context, CameraActivity.class);\n            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);\n\n            // Get the layout for the App Widget and attach an on-click listener\n            // to the button\n            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);\n            views.setOnClickPendingIntent(R.id.button, pendingIntent);\n\n\n            // Tell the AppWidgetManager to perform an update on the current app widget\n            appWidgetManager.updateAppWidget(appWidgetId, views);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/XMPHelper.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal;\n\n/**\n * XMP Helper to write XMP data to files\n */\npublic class XMPHelper {\n    static {\n        System.loadLibrary(\"xmptoolkit\");\n        System.loadLibrary(\"xmphelper_jni\");\n    }\n\n    /**\n     * Writes the provided XMP Data to the specified fileName. This goes through\n     * Adobe's XMP toolkit library.\n     *\n     * @param fileName The file name to which write the XMP metadata\n     * @param xmpData The RDF-formatted data\n     * @return -1 if error, 0 if everything went fine\n     */\n    public native int writeXmpToFile(String fileName, String xmpData);\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/AutoPictureEnhancer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.media.effect.Effect;\nimport android.media.effect.EffectContext;\nimport android.media.effect.EffectFactory;\nimport android.opengl.GLES20;\nimport android.opengl.GLSurfaceView;\nimport android.opengl.GLUtils;\nimport android.util.Log;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\n/**\n * Automatic photo enhancement\n */\npublic class AutoPictureEnhancer implements GLSurfaceView.Renderer {\n    public final static String TAG = \"AutoPictureEnhancer\";\n\n    private int[] mTextures = new int[2];\n    private EffectContext mEffectContext;\n    private Effect mAutoFixEffect;\n    private Effect mMinMaxEffect;\n    private TextureRenderer mTexRenderer = new TextureRenderer();\n    private int mImageWidth;\n    private int mImageHeight;\n    private Bitmap mBitmapToLoad;\n    private boolean mInitialized = false;\n    private Context mContext;\n\n    public AutoPictureEnhancer(Context context) {\n        mContext = context;\n    }\n\n    public void setTexture(Bitmap bitmap) {\n        mBitmapToLoad = bitmap;\n    }\n\n    public static Bitmap scale(Bitmap bmp, int w, int h) {\n        Bitmap b = null;\n        if (bmp != null) {\n            Log.v(TAG, \"Scaling bitmap from \" + bmp.getWidth() + \"x\"\n                    + bmp.getHeight() + \" to \" + w + \"x\" + h);\n\n            // We're going to need some memory\n            System.gc();\n            Runtime.getRuntime().gc();\n\n            // Scale it!\n            b=Bitmap.createScaledBitmap(bmp, w, h, true);\n\n            // Release original bitmap\n            bmp.recycle();\n        }\n        return b;\n    }\n\n    private void loadTextureImpl(Bitmap bitmap) {\n        // Generate textures\n        GLES20.glGenTextures(1, mTextures, 0);\n\n        final int mMaxTextureSize =\n                mContext.getResources().getInteger(R.integer.config_maxTextureSize);\n\n        // Load input bitmap\n        if (bitmap.getWidth() > mMaxTextureSize || bitmap.getHeight() > mMaxTextureSize) {\n            mImageWidth = mMaxTextureSize;\n            mImageHeight = mMaxTextureSize;\n        } else {\n            mImageWidth = bitmap.getWidth();\n            mImageHeight = bitmap.getHeight();\n        }\n\n        mTexRenderer.updateTextureSize(mImageWidth, mImageHeight);\n\n        // Upload to texture\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);\n        if (mImageWidth != bitmap.getWidth() || mImageHeight != bitmap.getHeight()) {\n            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, scale(bitmap,\n                    mImageWidth, mImageHeight), 0);\n        } else {\n            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);\n        }\n\n        bitmap.recycle();\n        System.gc();\n        Runtime.getRuntime().gc();\n\n        // Set texture parameters\n        GLToolbox.initTexParams();\n    }\n\n    private void initEffects() {\n        EffectFactory effectFactory = mEffectContext.getFactory();\n        if (mAutoFixEffect != null) {\n            mAutoFixEffect.release();\n        }\n\n        mAutoFixEffect = effectFactory.createEffect( EffectFactory.EFFECT_AUTOFIX);\n        mAutoFixEffect.setParameter(\"scale\", 0.4f);\n\n        mMinMaxEffect = effectFactory.createEffect( EffectFactory.EFFECT_BLACKWHITE);\n        mMinMaxEffect.setParameter(\"black\", .1f);\n        mMinMaxEffect.setParameter(\"white\", .8f);\n    }\n\n    private void applyEffects() {\n        mMinMaxEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[0]);\n        mAutoFixEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[0]);\n    }\n\n    private void renderResult() {\n        mTexRenderer.renderTexture(mTextures[0]);\n    }\n\n    @Override\n    public void onDrawFrame(GL10 gl) {\n        if (!mInitialized) {\n            // Only need to do this once\n            mEffectContext = EffectContext.createWithCurrentGlContext();\n            mTexRenderer.init();\n            mInitialized = true;\n        }\n\n        if (mBitmapToLoad != null) {\n            loadTextureImpl(mBitmapToLoad);\n            mBitmapToLoad = null;\n        } else {\n            Log.e(TAG, \"Bitmap to load is null\");\n        }\n\n        // Render the effect\n        initEffects();\n        applyEffects();\n        renderResult();\n    }\n\n    @Override\n    public void onSurfaceChanged(GL10 gl, int width, int height) {\n        final int mMaxTextureSize =\n                mContext.getResources().getInteger(R.integer.config_maxTextureSize);\n        if (mTexRenderer != null) {\n            if (width > mMaxTextureSize || height > mMaxTextureSize) {\n                mTexRenderer.updateViewSize(mMaxTextureSize, mMaxTextureSize);\n            } else {\n                mTexRenderer.updateViewSize(width, height);\n            }\n        }\n    }\n\n    @Override\n    public void onSurfaceCreated(GL10 gl, EGLConfig config) {\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/BurstCapture.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.os.Handler;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\n/**\n * Burst capture mode\n */\npublic class BurstCapture extends CaptureTransformer {\n    public final static String TAG = \"BurstCapture\";\n\n    private int mBurstCount = -1;\n    private int mShotsDone;\n    private boolean mBurstInProgress = false;\n    private Handler mHandler;\n    private CameraActivity mActivity;\n\n    public BurstCapture(CameraActivity activity) {\n        super(activity.getCamManager(), activity.getSnapManager());\n        mHandler = new Handler();\n        mActivity = activity;\n    }\n\n    /**\n     * Set the number of shots to take, or 0 for an infinite shooting\n     * (that will need to be stopped using terminateBurstShot)\n     *\n     * @param count The number of shots\n     */\n    public void setBurstCount(int count) {\n        mBurstCount = count;\n    }\n\n    /**\n     * Starts the burst shooting\n     */\n    public void startBurstShot() {\n        mShotsDone = 0;\n        mBurstInProgress = true;\n        mSnapManager.queueSnapshot(true, 0);\n    }\n\n    public void terminateBurstShot() {\n        mBurstInProgress = false;\n    }\n\n    private void tryTakeShot() {\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                mSnapManager.setBypassProcessing(true);\n                mSnapManager.queueSnapshot(true, 0);\n            }\n        });\n    }\n\n    @Override\n    public void onShutterButtonClicked(ShutterButton button) {\n        if (mBurstInProgress) {\n            terminateBurstShot();\n        } else {\n            startBurstShot();\n        }\n    }\n\n    @Override\n    public void onSnapshotShutter(final SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n        // XXX: Show it in the quick review drawer\n        if (!mBurstInProgress) {\n            return;\n        }\n\n        mShotsDone++;\n        Log.v(TAG, \"Done \" + mShotsDone + \" shots\");\n\n        if (mShotsDone < mBurstCount || mBurstCount == 0) {\n            tryTakeShot();\n        } else {\n            mBurstInProgress = false;\n        }\n    }\n\n    @Override\n    public void onMediaSavingStart() {\n\n    }\n\n    @Override\n    public void onMediaSavingDone() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStart() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStop() {\n\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/CaptureTransformer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\n/**\n * This class is a base class for all the effects/features affecting\n * the camera at capture, such as burst mode, or software HDR. It lets\n * you easily take more shots, process them, or throw them away, all of this\n * in one unique tap on the shutter button by the user.\n */\npublic abstract class CaptureTransformer implements SnapshotManager.SnapshotListener {\n    protected CameraManager mCamManager;\n    protected SnapshotManager mSnapManager;\n\n    public CaptureTransformer(CameraManager camMan, SnapshotManager snapshotMan) {\n        mCamManager = camMan;\n        mSnapManager = snapshotMan;\n    }\n\n    /**\n     * Triggers the logic of the CaptureTransformer, when the user\n     * pressed the shutter button.\n     */\n    public abstract void onShutterButtonClicked(ShutterButton button);\n\n    /**\n     * Triggers a secondary action when the shutter button is long-pressed\n     * (optional)\n     */\n    public void onShutterButtonLongPressed(ShutterButton button) { }\n\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/GLToolbox.java",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.opengl.GLES20;\n\npublic class GLToolbox {\n    public static int loadShader(int shaderType, String source) {\n        int shader = GLES20.glCreateShader(shaderType);\n        if (shader != 0) {\n            GLES20.glShaderSource(shader, source);\n            GLES20.glCompileShader(shader);\n            int[] compiled = new int[1];\n            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);\n            if (compiled[0] == 0) {\n                String info = GLES20.glGetShaderInfoLog(shader);\n                GLES20.glDeleteShader(shader);\n                shader = 0;\n                throw new RuntimeException(\"Could not compile shader \" +\n                shaderType + \":\" + info);\n            }\n        }\n        return shader;\n    }\n\n    public static int createProgram(String vertexSource,\n            String fragmentSource) {\n        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);\n        if (vertexShader == 0) {\n            return 0;\n        }\n        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);\n        if (pixelShader == 0) {\n            return 0;\n        }\n\n        int program = GLES20.glCreateProgram();\n        if (program != 0) {\n            GLES20.glAttachShader(program, vertexShader);\n            checkGlError(\"glAttachShader\");\n            GLES20.glAttachShader(program, pixelShader);\n            checkGlError(\"glAttachShader\");\n            GLES20.glLinkProgram(program);\n            int[] linkStatus = new int[1];\n            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);\n            if (linkStatus[0] != GLES20.GL_TRUE) {\n                String info = GLES20.glGetProgramInfoLog(program);\n                GLES20.glDeleteProgram(program);\n                program = 0;\n                throw new RuntimeException(\"Could not link program: \" + info);\n            }\n        }\n        return program;\n    }\n\n    public static void checkGlError(String op) {\n        int error;\n        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {\n            throw new RuntimeException(op + \": glError \" + error);\n        }\n    }\n\n    public static void initTexParams() {\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,\n                GLES20.GL_CLAMP_TO_EDGE);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,\n                GLES20.GL_CLAMP_TO_EDGE);\n   }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/PixelBuffer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.opengl.GLSurfaceView;\nimport android.util.Log;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\n\nimport java.nio.IntBuffer;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLContext;\nimport javax.microedition.khronos.egl.EGLDisplay;\nimport javax.microedition.khronos.egl.EGLSurface;\nimport javax.microedition.khronos.opengles.GL10;\n\nimport static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;\nimport static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT;\nimport static javax.microedition.khronos.egl.EGL10.EGL_LARGEST_PBUFFER;\nimport static javax.microedition.khronos.egl.EGL10.EGL_NONE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;\nimport static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;\nimport static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS;\nimport static javax.microedition.khronos.egl.EGL10.EGL_WIDTH;\nimport static javax.microedition.khronos.opengles.GL10.GL_RGBA;\nimport static javax.microedition.khronos.opengles.GL10.GL_UNSIGNED_BYTE;\n\n/**\n * Offscreen OpenGL renderer\n */\npublic class PixelBuffer {\n    final static String TAG = \"PixelBuffer\";\n    final static boolean LIST_CONFIGS = false;\n    final static private int EGL_OPENGL_ES2_BIT = 4;\n    final static private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;\n    final static private int EGL_BIND_TO_TEXTURE_RGB = 0x3039;\n    final static private int EGL_BIND_TO_TEXTURE_RGBA = 0x303A;\n    final static private int EGL_TEXTURE_FORMAT = 0x3080;\n    final static private int EGL_TEXTURE_TARGET = 0x3081;\n    final static private int EGL_TEXTURE_RGB = 0x305D;\n    final static private int EGL_TEXTURE_RGBA = 0x305E;\n    final static private int EGL_TEXTURE_2D = 0x305F;\n\n    GLSurfaceView.Renderer mRenderer; // Borrow this interface\n    int mWidth, mHeight;\n    Bitmap mBitmap;\n\n    EGL10 mEGL;\n    EGLDisplay mEGLDisplay;\n    EGLConfig[] mEGLConfigs;\n    EGLConfig mEGLConfig;\n    EGLContext mEGLContext;\n    EGLSurface mEGLSurface;\n    GL10 mGL;\n    Context mContext;\n\n    String mThreadOwner;\n\n    public PixelBuffer(Context context, int width, int height) {\n        mWidth = width;\n        mHeight = height;\n        mContext = context;\n\n        int[] version = new int[2];\n        int[] attribList = null;\n        final int mMaxTextureSize =\n                mContext.getResources().getInteger(R.integer.config_maxTextureSize);\n\n        if (mWidth < mMaxTextureSize && mHeight < mMaxTextureSize) {\n            // The texture is smaller than the maximum supported size, use it directly.\n            attribList = new int[] {\n                    EGL_WIDTH, mWidth,\n                    EGL_HEIGHT, mHeight,\n                    EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,\n                    EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,\n                    EGL_NONE\n            };\n        } else {\n            // Use the maximum supported texture size.\n            attribList = new int[] {\n                    EGL_WIDTH, mMaxTextureSize,\n                    EGL_HEIGHT, mMaxTextureSize,\n                    EGL_LARGEST_PBUFFER, 1,\n                    EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,\n                    EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,\n                    EGL_NONE\n            };\n        }\n\n        // No error checking performed, minimum required code to elucidate logic\n        mEGL = (EGL10) EGLContext.getEGL();\n        mEGLDisplay = mEGL.eglGetDisplay(EGL_DEFAULT_DISPLAY);\n        mEGL.eglInitialize(mEGLDisplay, version);\n        mEGLConfig = chooseConfig(); // Choosing a config is a little more complicated\n\n        // Make sure you run in OpenGL ES 2.0, as everything in Nemesis uses a shader pipeline\n        mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL_NO_CONTEXT, new int[] {\n                EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE\n        });\n        mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,  attribList);\n        mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);\n        mGL = (GL10) mEGLContext.getGL();\n\n        // Record thread owner of OpenGL context\n        mThreadOwner = Thread.currentThread().getName();\n    }\n\n    public void setRenderer(GLSurfaceView.Renderer renderer) {\n        mRenderer = renderer;\n\n        // Does this thread own the OpenGL context?\n        if (!Thread.currentThread().getName().equals(mThreadOwner)) {\n            Log.e(TAG, \"setRenderer: This thread does not own the OpenGL context.\");\n            return;\n        }\n\n        // Call the renderer initialization routines\n        mRenderer.onSurfaceCreated(mGL, mEGLConfig);\n        mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);\n    }\n\n    public Bitmap getBitmap() {\n        // Do we have a renderer?\n        if (mRenderer == null) {\n            Log.e(TAG, \"getBitmap: Renderer was not set.\");\n            return null;\n        }\n\n        // Does this thread own the OpenGL context?\n        if (!Thread.currentThread().getName().equals(mThreadOwner)) {\n            Log.e(TAG, \"getBitmap: This thread does not own the OpenGL context.\");\n            return null;\n        }\n\n        // Call the renderer draw routine\n        mRenderer.onDrawFrame(mGL);\n        convertToBitmap();\n        return mBitmap;\n    }\n\n    private EGLConfig chooseConfig() {\n        int[] attribList = new int[] {\n                EGL_DEPTH_SIZE, 0,\n                EGL_STENCIL_SIZE, 0,\n                EGL_RED_SIZE, 8,\n                EGL_GREEN_SIZE, 8,\n                EGL_BLUE_SIZE, 8,\n                EGL_ALPHA_SIZE, 8,\n                EGL_BIND_TO_TEXTURE_RGBA, 1,\n                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,\n                EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n                EGL_NONE\n        };\n\n        // No error checking performed, minimum required code to elucidate logic\n        // Expand on this logic to be more selective in choosing a configuration\n        int[] numConfig = new int[1];\n        mEGL.eglChooseConfig(mEGLDisplay, attribList, null, 0, numConfig);\n        int configSize = numConfig[0];\n        mEGLConfigs = new EGLConfig[configSize];\n        mEGL.eglChooseConfig(mEGLDisplay, attribList, mEGLConfigs, configSize, numConfig);\n\n        int error = mEGL.eglGetError();\n        if (error != EGL_SUCCESS) {\n            Log.e(TAG, \"eglChooseConfig: \" + error);\n        }\n\n        if (LIST_CONFIGS) {\n            listConfig();\n        }\n\n        return mEGLConfigs[0];  // Best match is probably the first configuration\n    }\n\n    private void listConfig() {\n        Log.i(TAG, \"Config List {\");\n\n        for (EGLConfig config : mEGLConfigs) {\n            int d, s, r, g, b, a;\n\n            // Expand on this logic to dump other attributes\n            d = getConfigAttrib(config, EGL_DEPTH_SIZE);\n            s = getConfigAttrib(config, EGL_STENCIL_SIZE);\n            r = getConfigAttrib(config, EGL_RED_SIZE);\n            g = getConfigAttrib(config, EGL_GREEN_SIZE);\n            b = getConfigAttrib(config, EGL_BLUE_SIZE);\n            a = getConfigAttrib(config, EGL_ALPHA_SIZE);\n            Log.i(TAG, \"    <d,s,r,g,b,a> = <\" + d + \",\" + s + \",\" +\n                    r + \",\" + g + \",\" + b + \",\" + a + \">\");\n        }\n\n        Log.i(TAG, \"}\");\n    }\n\n    private int getConfigAttrib(EGLConfig config, int attribute) {\n        int[] value = new int[1];\n        return mEGL.eglGetConfigAttrib(mEGLDisplay, config,\n                attribute, value)? value[0] : 0;\n    }\n\n    private void convertToBitmap() {\n        System.gc();\n        Runtime.getRuntime().gc();\n\n        final int mMaxTextureSize =\n                mContext.getResources().getInteger(R.integer.config_maxTextureSize);\n        boolean isScaled = (mWidth > mMaxTextureSize || mHeight > mMaxTextureSize);\n\n        int scaledWidth = isScaled ? mMaxTextureSize : mWidth;\n        int scaledHeight = isScaled ? mMaxTextureSize : mHeight;\n\n        IntBuffer ib = IntBuffer.allocate(scaledWidth*scaledHeight);\n        IntBuffer ibt = IntBuffer.allocate(scaledWidth*scaledHeight);\n        mGL.glReadPixels(0, 0, scaledWidth, scaledHeight, GL_RGBA, GL_UNSIGNED_BYTE, ib);\n\n        // Convert upside down mirror-reversed image to right-side up normal image.\n        for (int i = 0; i < scaledHeight; i++) {\n            for (int j = 0; j < scaledWidth; j++) {\n                ibt.put((scaledHeight-i-1)*scaledWidth + j, ib.get(i*scaledWidth + j));\n            }\n        }\n\n        mBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);\n        mBitmap.copyPixelsFromBuffer(ibt);\n\n        // Release IntBuffers memory\n        ibt = null;\n        ib = null;\n\n        if (isScaled) {\n            ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();\n            ActivityManager activityManager =\n                    (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);\n            activityManager.getMemoryInfo(mi);\n            long availableMegs = mi.availMem / 1048576L;\n            Log.d(TAG, \"Available memory: \" + availableMegs + \"MB\");\n            while (availableMegs < 200) {\n                try {\n                    Thread.sleep(100);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n                Log.w(TAG, \"Waiting for more memory! (Free: \" + availableMegs + \"MB)\");\n                // We're going to need some memory\n                System.gc();\n                Runtime.getRuntime().gc();\n\n                activityManager.getMemoryInfo(mi);\n                availableMegs = mi.availMem / 1048576L;\n            }\n\n            // Image was converted to a power of two texture, scale it back\n            Log.v(TAG, \"Image was scaled, scaling back to \" + mWidth + \"x\" + mHeight);\n            Bitmap scaled = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, true);\n            mBitmap.recycle();\n            mBitmap = scaled;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/SoftwareHdrCapture.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Software HDR capture mode\n */\npublic class SoftwareHdrCapture extends CaptureTransformer {\n    public final static String TAG = \"SoftwareHdrCapture\";\n    private final static int SHOTS_COUNT = 3;\n\n    private int mShotsDone;\n    private boolean mBurstInProgress = false;\n    private Handler mHandler;\n    private CameraActivity mActivity;\n    private List<Uri> mPictures;\n    private List<Uri> mPicturesUri;\n    private static SoftwareHdrRenderingService mBoundService;\n    private static boolean mIsBound;\n\n    private static ServiceConnection mServiceConnection = new ServiceConnection() {\n        public void onServiceConnected(ComponentName className, IBinder service) {\n            // This is called when the connection with the service has been\n            // established, giving us the service object we can use to\n            // interact with the service.  Because we have bound to a explicit\n            // service that we know is running in our own process, we can\n            // cast its IBinder to a concrete class and directly access it.\n            mBoundService = ((SoftwareHdrRenderingService.LocalBinder)service).getService();\n        }\n\n        public void onServiceDisconnected(ComponentName className) {\n            // This is called when the connection with the service has been\n            // unexpectedly disconnected -- that is, its process crashed.\n            // Because it is running in our same process, we should never\n            // see this happen.\n            mBoundService = null;\n        }\n    };\n\n    public SoftwareHdrCapture(CameraActivity activity) {\n        super(activity.getCamManager(), activity.getSnapManager());\n        mHandler = new Handler();\n        mPictures = new ArrayList<Uri>();\n        mPicturesUri = new ArrayList<Uri>();\n        mActivity = activity;\n        doBindService();\n    }\n\n    public static ServiceConnection getServiceConnection() {\n        return mServiceConnection;\n    }\n\n    public static boolean isServiceBound() {\n        return mIsBound;\n    }\n\n    void doBindService() {\n        // Establish a connection with the service.  We use an explicit\n        // class name because we want a specific service implementation that\n        // we know will be running in our own process (and thus won't be\n        // supporting component replacement by other applications).\n        Log.v(TAG, \"Binding Software HDR rendering service\");\n        mActivity.bindService(new Intent(mActivity, SoftwareHdrRenderingService.class),\n                mServiceConnection, Context.BIND_AUTO_CREATE);\n        mIsBound = true;\n    }\n\n    public int getShotExposure(int shotId) {\n        if (shotId == 0) {\n            return mCamManager.getParameters().getMinExposureCompensation();\n        } else if (shotId == 1) {\n            return 0;\n        } else if (shotId == 2) {\n            return mCamManager.getParameters().getMaxExposureCompensation();\n        }\n\n        Log.e(TAG, \"Unknown shot exposure ID \" + shotId);\n        return 0;\n    }\n\n    /**\n     * Starts the HDR shooting\n     */\n    public void startBurstShot() {\n        mShotsDone = 0;\n        mBurstInProgress = true;\n        mSnapManager.queueSnapshot(true, getShotExposure(mShotsDone));\n\n        // Open the quick review drawer\n        mActivity.getReviewDrawer().openQuickReview();\n    }\n\n    private void tryTakeShot() {\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                mSnapManager.setBypassProcessing(true);\n                mSnapManager.queueSnapshot(true, getShotExposure(mShotsDone));\n            }\n        });\n    }\n\n    @Override\n    public void onShutterButtonClicked(ShutterButton button) {\n        startBurstShot();\n    }\n\n    @Override\n    public void onSnapshotShutter(final SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n        if (!mBurstInProgress) {\n            return;\n        }\n\n        mPictures.add(Uri.fromFile(new File(Util.getRealPathFromURI(mActivity, info.mUri))));\n        mPicturesUri.add(info.mUri);\n\n        mShotsDone++;\n        Log.v(TAG, \"Done \" + mShotsDone + \" shots\");\n\n        if (mShotsDone < SHOTS_COUNT) {\n            tryTakeShot();\n        } else {\n            // Reset exposure\n            mCamManager.getParameters().setExposureCompensation(0);\n\n            // Render\n            int orientation = (360 - mActivity.getOrientation()) % 360;\n            mBoundService.render(mPictures, mPicturesUri, mActivity.getSnapManager(), orientation);\n\n            mShotsDone = 0;\n        }\n    }\n\n    @Override\n    public void onMediaSavingStart() {\n\n    }\n\n    @Override\n    public void onMediaSavingDone() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStart() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStop() {\n\n    }\n\n    public void tearDown() {\n        if (mIsBound) {\n            // Detach our existing connection.\n            mActivity.unbindService(mServiceConnection);\n            mIsBound = false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/SoftwareHdrProcessor.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.SnapshotManager;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.RandomAccessFile;\nimport java.util.List;\n\n/**\n * Manages the processing of multiple shots into one HDR shot\n */\npublic class SoftwareHdrProcessor {\n    public final static String TAG = \"SoftwareHdr\";\n    private String mPathPrefix;\n    private File mTempPath;\n    private List<Uri> mPictures;\n    private SnapshotManager mSnapManager;\n    private Uri mOutputUri;\n    private String mOutputTitle;\n    private Context mContext;\n    private BufferedReader mProcStdOut;\n    private BufferedReader mProcStdErr;\n\n    private Thread mOutputLogger = new Thread() {\n        public void run() {\n            while (true) {\n                try {\n                    Thread.sleep(10);\n                    consumeProcLogs();\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    };\n\n    public SoftwareHdrProcessor(Context context, SnapshotManager snapMan) {\n        mSnapManager = snapMan;\n        mContext = context;\n    }\n\n    public void setPictures(List<Uri> pictures) {\n        mPictures = pictures;\n    }\n\n    public File getTempPath() {\n        return mTempPath;\n    }\n\n    private void run(String command) throws IOException {\n        Runtime rt = Runtime.getRuntime();\n        Process proc = rt.exec(command, new String[]{\"PATH=\"+mPathPrefix+\":/system/bin\",\n                \"LD_LIBRARY_PATH=\"+mPathPrefix+\":/system/lib\"});\n        mProcStdOut = new BufferedReader(new\n                InputStreamReader(proc.getInputStream()));\n        mProcStdErr = new BufferedReader(new\n                InputStreamReader(proc.getErrorStream()));\n\n        try {\n            proc.waitFor();\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public boolean render(final int orientation) {\n        mOutputLogger.start();\n\n        // Prepare a temporary directory\n        Log.d(TAG, \"Preparing temp dir for Software HDR rendering...\");\n        File appFilesDir = mContext.getFilesDir();\n        mPathPrefix = appFilesDir.getAbsolutePath() + \"/\";\n        String tempPathStr = appFilesDir.getAbsolutePath() + \"/\" + System.currentTimeMillis();\n        mTempPath = new File(tempPathStr);\n        mTempPath.mkdir();\n\n\n        // Process our images\n        try {\n            if (!doAlignImageStack()) {\n                return false;\n            }\n            if (!doEnfuse()) {\n                return false;\n            }\n\n            // Save it to gallery\n            // XXX: This needs opening the output byte array...\n            // Isn't there any way to update gallery data without having to reload/save\n            // the JPEG file? Because we have it already, we could just move it.\n            byte[] jpegData;\n            RandomAccessFile f = new RandomAccessFile(mTempPath+\"/final.jpg\", \"r\");\n            try {\n                // Get and check length\n                long longlength = f.length();\n                int length = (int) longlength;\n                if (length != longlength) {\n                    throw new IOException(\"File size >= 2 GB\");\n                }\n                // Read file and return data\n                jpegData = new byte[length];\n                f.readFully(jpegData);\n            } finally {\n                f.close();\n            }\n\n            mSnapManager.prepareNamerUri(100,100);\n            mOutputUri = mSnapManager.getNamerUri();\n            mOutputTitle = mSnapManager.getNamerTitle();\n            mSnapManager.saveImage(mOutputUri, mOutputTitle, 100, 100, orientation, jpegData);\n        } catch (IOException ex) {\n            Log.e(TAG, \"Unable to process: \", ex);\n            return false;\n        }\n\n        return true;\n    }\n\n    private void consumeProcLogs() {\n        String line;\n        try {\n            if (mProcStdOut != null && mProcStdOut.ready()) {\n                while ((line = mProcStdOut.readLine()) != null) {\n                    Log.i(TAG, line);\n                }\n            }\n\n            if (mProcStdErr != null && mProcStdErr.ready()) {\n                while ((line = mProcStdErr.readLine()) != null) {\n                    Log.e(TAG, line);\n                }\n            }\n        } catch (IOException e) {\n            Log.e(TAG, \"Error while consuming proc logs\", e);\n        }\n    }\n\n    private boolean doAlignImageStack() throws IOException {\n        Log.d(TAG, \"Align Image Stack...\");\n\n        String filesStr = \"\";\n        for (Uri picture : mPictures) {\n            if (new File(picture.getPath()).exists()) {\n                filesStr += \" \" + picture.getPath();\n            }\n        }\n\n        run(\"align_image_stack -v -v -v -C -g 4 -a \"+mTempPath+\"/project \" + filesStr);\n        consumeProcLogs();\n\n        Log.d(TAG, \"Align Image Stack... done\");\n        return true;\n    }\n\n    private boolean doEnfuse() throws IOException {\n        Log.d(TAG, \"Enfuse...\");\n\n        // Build the list of output files. The convention set up by\n        // AlignImageStack is projectXXXX.tif, so we basically build that\n        // list out of the number of shots we fed to align_image_stack\n        String files = \"\";\n        for (int i = 0; i < mPictures.size(); i++) {\n            // Check if file exists, otherwise enfuse will fail\n            String filePath = mTempPath + \"/\" + String.format(\"project%04d.tif\", i);\n            if (new File(filePath).exists()) {\n                files += \" \" + filePath;\n            }\n        }\n        run(\"enfuse -o \"+mTempPath+\"/final.jpg --compression=jpeg \" + files);\n        consumeProcLogs();\n\n        Log.d(TAG, \"Enfuse... done\");\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/SoftwareHdrRenderingService.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.app.Service;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.picsphere.PicSphere;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Service that handles HDR rendering outside\n * the app context (as the rendering thread would get killed)\n */\npublic class SoftwareHdrRenderingService extends Service {\n    private NotificationManager mNM;\n\n    // Unique Identification Number for the Notification.\n    // We use it on Notification start, and to cancel it.\n    private int NOTIFICATION = 4242;\n\n    private boolean mHasFailed = false;\n\n    /**\n     * Class for clients to access.  Because we know this service always\n     * runs in the same process as its clients, we don't need to deal with\n     * IPC.\n     */\n    public class LocalBinder extends Binder {\n        SoftwareHdrRenderingService getService() {\n            return SoftwareHdrRenderingService.this;\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.i(\"LocalService\", \"Received start id \" + startId + \": \" + intent);\n        // We want this service to continue running until it is explicitly\n        // stopped, so return sticky.\n        return START_STICKY;\n    }\n\n    @Override\n    public void onDestroy() {\n        // Cancel the persistent notification.\n        mNM.cancel(NOTIFICATION);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return mBinder;\n    }\n\n    // This is the object that receives interactions from clients.  See\n    // RemoteService for a more complete example.\n    private final IBinder mBinder = new LocalBinder();\n\n    public void render(final List<Uri> pictures, final List<Uri> picturesUri,\n                       final SnapshotManager snapMan, final int orientation) {\n        // Display a notification\n        mNM.notify(NOTIFICATION, buildProgressNotification());\n        mHasFailed = false;\n\n        new Thread() {\n            public void run() {\n                SoftwareHdrProcessor processor =\n                        new SoftwareHdrProcessor(SoftwareHdrRenderingService.this, snapMan);\n                processor.setPictures(pictures);\n\n                if (processor.render(orientation)) {\n                    mNM.cancel(NOTIFICATION);\n                    removeTempFiles(picturesUri, processor.getTempPath());\n                } else {\n                    mHasFailed = true;\n                    mNM.notify(NOTIFICATION, buildFailureNotification(getString(\n                            R.string.software_hdr_failed), getString(\n                            R.string.software_hdr_failed_details)));\n                }\n\n                SoftwareHdrRenderingService.this.stopSelf();\n            }\n        }.start();\n    }\n\n    private void removeTempFiles(List<Uri> pictures, File tempPath) {\n        // Remove source pictures and temporary path\n        for (Uri uri : pictures) {\n            List<String> segments = uri.getPathSegments();\n            Util.removeFromGallery(getContentResolver(),\n                    Integer.parseInt(segments.get(segments.size() - 1)));\n        }\n\n        tempPath.delete();\n    }\n\n    private Notification buildProgressNotification() {\n        Notification.Builder mBuilder =\n                new Notification.Builder(this)\n                        .setSmallIcon(R.drawable.ic_launcher)\n                        .setContentTitle(getString(R.string.software_hdr_notif_title))\n                        .setContentText(getString(R.string.please_wait))\n                        .setOngoing(true);\n\n        return mBuilder.build();\n    }\n\n    private Notification buildFailureNotification(String title, String text) {\n        Notification.Builder mBuilder =\n                new Notification.Builder(this)\n                        .setSmallIcon(R.drawable.ic_launcher)\n                        .setContentTitle(title)\n                        .setContentText(text)\n                        .setOngoing(false);\n\n        return mBuilder.build();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/TextureRenderer.java",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.opengl.GLES20;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.FloatBuffer;\n\npublic class TextureRenderer {\n    private int mProgram;\n    private int mTexSamplerHandle;\n    private int mTexCoordHandle;\n    private int mPosCoordHandle;\n\n    private FloatBuffer mTexVertices;\n    private FloatBuffer mPosVertices;\n\n    private int mViewWidth;\n    private int mViewHeight;\n\n    private int mTexWidth;\n    private int mTexHeight;\n\n    private static final String VERTEX_SHADER =\n            \"attribute vec4 a_position;\\n\" +\n            \"attribute vec2 a_texcoord;\\n\" +\n            \"varying vec2 v_texcoord;\\n\" +\n            \"void main() {\\n\" +\n            \"  gl_Position = a_position;\\n\" +\n            \"  v_texcoord = a_texcoord;\\n\" +\n            \"}\\n\";\n\n    private static final String FRAGMENT_SHADER =\n            \"precision mediump float;\\n\" +\n            \"uniform sampler2D tex_sampler;\\n\" +\n            \"varying vec2 v_texcoord;\\n\" +\n            \"void main() {\\n\" +\n            \"  gl_FragColor = texture2D(tex_sampler, v_texcoord);\\n\" +\n            \"}\\n\";\n\n    private static final float[] TEX_VERTICES = {\n        0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f\n    };\n\n    private static final float[] POS_VERTICES = {\n        -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f\n    };\n\n    private static final int FLOAT_SIZE_BYTES = 4;\n\n    public void init() {\n        // Create program\n        mProgram = GLToolbox.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);\n\n        // Bind attributes and uniforms\n        mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram,\n                \"tex_sampler\");\n        mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, \"a_texcoord\");\n        mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, \"a_position\");\n\n        // Setup coordinate buffers\n        mTexVertices = ByteBuffer.allocateDirect(\n                TEX_VERTICES.length * FLOAT_SIZE_BYTES)\n                .order(ByteOrder.nativeOrder()).asFloatBuffer();\n        mTexVertices.put(TEX_VERTICES).position(0);\n        mPosVertices = ByteBuffer.allocateDirect(\n                POS_VERTICES.length * FLOAT_SIZE_BYTES)\n                .order(ByteOrder.nativeOrder()).asFloatBuffer();\n        mPosVertices.put(POS_VERTICES).position(0);\n    }\n\n    public void tearDown() {\n        GLES20.glDeleteProgram(mProgram);\n    }\n\n    public void updateTextureSize(int texWidth, int texHeight) {\n        mTexWidth = texWidth;\n        mTexHeight = texHeight;\n        computeOutputVertices();\n    }\n\n    public void updateViewSize(int viewWidth, int viewHeight) {\n        mViewWidth = viewWidth;\n        mViewHeight = viewHeight;\n        computeOutputVertices();\n    }\n\n    public void renderTexture(int texId) {\n        // Bind default FBO\n        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);\n\n        // Use our shader program\n        GLES20.glUseProgram(mProgram);\n        GLToolbox.checkGlError(\"glUseProgram\");\n\n        // Set viewport\n        GLES20.glViewport(0, 0, mViewWidth, mViewHeight);\n        GLToolbox.checkGlError(\"glViewport\");\n\n        // Disable blending\n        GLES20.glDisable(GLES20.GL_BLEND);\n\n        // Set the vertex attributes\n        GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false,\n                0, mTexVertices);\n        GLES20.glEnableVertexAttribArray(mTexCoordHandle);\n        GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false,\n                0, mPosVertices);\n        GLES20.glEnableVertexAttribArray(mPosCoordHandle);\n        GLToolbox.checkGlError(\"vertex attribute setup\");\n\n        // Set the input texture\n        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);\n        GLToolbox.checkGlError(\"glActiveTexture\");\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);\n        GLToolbox.checkGlError(\"glBindTexture\");\n        GLES20.glUniform1i(mTexSamplerHandle, 0);\n\n        // Draw\n        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);\n        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);\n    }\n\n    private void computeOutputVertices() {\n        if (mPosVertices != null) {\n            float imgAspectRatio = mTexWidth / (float)mTexHeight;\n            float viewAspectRatio = mViewWidth / (float)mViewHeight;\n            float relativeAspectRatio = viewAspectRatio / imgAspectRatio;\n            float x0, y0, x1, y1;\n            if (relativeAspectRatio > 1.0f) {\n                x0 = -1.0f / relativeAspectRatio;\n                y0 = -1.0f;\n                x1 = 1.0f / relativeAspectRatio;\n                y1 = 1.0f;\n            } else {\n                x0 = -1.0f;\n                y0 = -relativeAspectRatio;\n                x1 = 1.0f;\n                y1 = relativeAspectRatio;\n            }\n            float[] coords = new float[] { x0, y0, x1, y0, x0, y1, x1, y1 };\n            mPosVertices.put(coords).position(0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/feats/TimerCapture.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.feats;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.AudioManager;\nimport android.os.Bundle;\nimport android.os.CountDownTimer;\nimport android.speech.RecognitionListener;\nimport android.speech.RecognizerIntent;\nimport android.speech.SpeechRecognizer;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\nimport java.util.ArrayList;\n\n/**\n * Handles the timer before a capture, or the voice shutter feature\n */\npublic class TimerCapture extends CaptureTransformer implements RecognitionListener {\n    public final static String TAG = \"TimerCapture\";\n    public final static int VOICE_TIMER_VALUE = -1;\n\n    private int mTimer;\n    private CameraActivity mActivity;\n    private SpeechRecognizer mSpeechRecognizer;\n    private Intent mSpeechRecognizerIntent;\n    private AudioManager mAudioManager;\n    private String[] mShutterWords;\n    private boolean mIsMuted;\n    private boolean mIsInitialised;\n    private CountDownTimer mCountDown;\n\n    public TimerCapture(CameraActivity activity) {\n        super(activity.getCamManager(), activity.getSnapManager());\n        mActivity = activity;\n        mIsInitialised = false;\n        mIsMuted = false;\n        mTimer = 5;\n    }\n\n    /**\n     * Sets the amount of seconds before taking a shot, or VOICE_TIMER_VALUE for voice\n     * trigger commands\n     *\n     * @param seconds Number of seconds before taking a shots, or VOICE_TIMER_VALUE\n     */\n    public void setTimer(int seconds) {\n        if (seconds == mTimer) return;\n\n        if (mTimer == VOICE_TIMER_VALUE) {\n            // We were in voice shutter mode, disable it\n            clearVoiceShutter();\n        }\n\n        mTimer = seconds;\n\n        if (seconds == VOICE_TIMER_VALUE) {\n            initializeVoiceShutter();\n        }\n    }\n\n    public int getTimer() {\n        return mTimer;\n    }\n\n    public void clearVoiceShutter() {\n        //Log.d(TAG,\"Stopping speach recog - \" + mSpeechActive + \"/\" + enable);\n        //mSpeechActive = false;\n        if (mIsMuted) {\n            mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, false);\n            mIsMuted = false;\n        }\n\n        //mPhotoModule.updateVoiceShutterIndicator(false);\n        mSpeechRecognizer.cancel();\n    }\n\n    public void initializeVoiceShutter() {\n        if (!mIsInitialised) {\n            Context context =  mCamManager.getContext();\n\n            if (context == null) {\n                Log.e(TAG, \"Could not initialise voice shutter because of null context\");\n            }\n\n            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);\n            mSpeechRecognizer.setRecognitionListener(this);\n\n            mSpeechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);\n            mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,\n                    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);\n            mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,\n                    \"org.cyanogenmod.voiceshutter\");\n            mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 10);\n            mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);\n\n\n            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);\n\n            mShutterWords = context.getResources().getStringArray(\n                    R.array.transformer_timer_voice_words);\n        }\n\n        // Turn it on\n        if (!mIsMuted) {\n            /* Avoid beeps when re-arming the listener */\n            mIsMuted = true;\n            mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, true);\n        }\n\n        //Log.d(TAG,\"Starting speach recog\");\n        // TODO: mPhotoModule.updateVoiceShutterIndicator(true);\n        mSpeechRecognizer.startListening(mSpeechRecognizerIntent);\n    }\n\n    @Override\n    public void onShutterButtonClicked(ShutterButton button) {\n        if (mTimer > 0) {\n            mActivity.startTimerCountdown(mTimer * 1000);\n            mCountDown = new CountDownTimer(mTimer * 1000, 1000) {\n                @Override\n                public void onTick(long l) {\n                    updateTimerIndicator();\n                }\n\n                @Override\n                public void onFinish() {\n                    mSnapManager.queueSnapshot(true, 0);\n                    mActivity.hideTimerCountdown();\n                }\n            };\n\n            mCountDown.start();\n        } else {\n            mSnapManager.queueSnapshot(true, 0);\n        }\n    }\n\n    @Override\n    public void onSnapshotShutter(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onMediaSavingStart() {\n\n    }\n\n    @Override\n    public void onMediaSavingDone() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStart() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStop() {\n\n    }\n\n    @Override\n    public void onReadyForSpeech(Bundle bundle) {\n        Log.d(TAG, \"ON READY FOR SPEECH\");\n        //mIsCountDownOn = true;\n        //mNoSpeechCountDown.start();\n        if (mIsMuted) {\n            mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, false);\n            mIsMuted = false;\n        }\n    }\n\n    @Override\n    public void onBeginningOfSpeech() {\n\n    }\n\n    @Override\n    public void onRmsChanged(float v) {\n\n    }\n\n    @Override\n    public void onBufferReceived(byte[] bytes) {\n\n    }\n\n    @Override\n    public void onEndOfSpeech() {\n\n    }\n\n    @Override\n    public void onError(int error) {\n        Log.e(TAG, \"error \" +  error);\n        mIsInitialised = false;\n        initializeVoiceShutter();\n    }\n\n    @Override\n    public void onResults(Bundle bundle) {\n        onPartialResults(bundle);\n        initializeVoiceShutter();\n    }\n\n    @Override\n    public void onPartialResults(Bundle partialResults) {\n        ArrayList data = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n        if (data == null) {\n            Log.e(TAG, \"Null Partial Results\");\n            return;\n        }\n        String str = \"\";\n        for (int i = 0; i < data.size(); i++) {\n            Log.d(TAG, \"result \" + data.get(i));\n            for (int f = 0; f < mShutterWords.length; f++) {\n                String[] resultWords = data.get(i).toString().split(\" \");\n                for (int g = 0; g < resultWords.length; g++) {\n                    if (mShutterWords[f].equalsIgnoreCase(resultWords[g])) {\n                        Log.d(TAG, \"matched to hotword! FIRE SHUTTER!\");\n                        mSnapManager.queueSnapshot(true, 0);\n                        //mSpeechActive = false;\n                        //enableSpeechRecognition(false, null);\n                    }\n                }\n            }\n            str += data.get(i);\n        }\n    }\n\n    @Override\n    public void onEvent(int i, Bundle bundle) {\n\n    }\n\n    public void updateTimerIndicator() {\n\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/Mosaic.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.pano;\n\n/**\n * The Java interface to JNI calls regarding mosaic stitching.\n *\n * A high-level usage is:\n *\n * Mosaic mosaic = new Mosaic();\n * mosaic.setSourceImageDimensions(width, height);\n * mosaic.reset(blendType);\n *\n * while ((pixels = hasNextImage()) != null) {\n *    mosaic.setSourceImage(pixels);\n * }\n *\n * mosaic.createMosaic(highRes);\n * byte[] result = mosaic.getFinalMosaic();\n *\n */\npublic class Mosaic {\n    /**\n     * In this mode, the images are stitched together in the same spatial arrangement as acquired\n     * i.e. if the user follows a curvy trajectory, the image boundary of the resulting mosaic will\n     * be curved in the same manner. This mode is useful if the user wants to capture a mosaic as\n     * if \"painting\" the scene using the smart-phone device and does not want any corrective warps\n     * to distort the captured images.\n     */\n    public static final int BLENDTYPE_FULL = 0;\n\n    /**\n     * This mode is the same as BLENDTYPE_FULL except that the resulting mosaic is rotated\n     * to balance the first and last images to be approximately at the same vertical offset in the\n     * output mosaic. This is useful when acquiring a mosaic by a typical panning-like motion to\n     * remove a one-sided curve in the mosaic (typically due to the camera not staying horizontal\n     * during the video capture) and convert it to a more symmetrical \"smiley-face\" like output.\n     */\n    public static final int BLENDTYPE_PAN = 1;\n\n    /**\n     * This mode compensates for typical \"smiley-face\" like output in longer mosaics and creates\n     * a rectangular mosaic with minimal black borders (by unwrapping the mosaic onto an imaginary\n     * cylinder). If the user follows a curved trajectory (instead of a perfect panning trajectory),\n     * the resulting mosaic here may suffer from some image distortions in trying to map the\n     * trajectory to a cylinder.\n     */\n    public static final int BLENDTYPE_CYLINDERPAN = 2;\n\n    /**\n     * This mode is basically BLENDTYPE_CYLINDERPAN plus doing a rectangle cropping before returning\n     * the mosaic. The mode is useful for making the resulting mosaic have a rectangle shape.\n     */\n    public static final int BLENDTYPE_HORIZONTAL =3;\n\n    /**\n     * This strip type will use the default thin strips where the strips are\n     * spaced according to the image capture rate.\n     */\n    public static final int STRIPTYPE_THIN = 0;\n\n    /**\n     * This strip type will use wider strips for blending. The strip separation\n     * is controlled by a threshold on the native side. Since the strips are\n     * wider, there is an additional cross-fade blending step to make the seam\n     * boundaries smoother. Since this mode uses lesser image frames, it is\n     * computationally more efficient than the thin strip mode.\n     */\n    public static final int STRIPTYPE_WIDE = 1;\n\n    /**\n     * Return flags returned by createMosaic() are one of the following.\n     */\n    public static final int MOSAIC_RET_OK = 1;\n    public static final int MOSAIC_RET_ERROR = -1;\n    public static final int MOSAIC_RET_CANCELLED = -2;\n    public static final int MOSAIC_RET_LOW_TEXTURE = -3;\n    public static final int MOSAIC_RET_FEW_INLIERS = 2;\n\n\n    static {\n        System.loadLibrary(\"jni_mosaic2\");\n    }\n\n    /**\n     * Allocate memory for the image frames at the given resolution.\n     *\n     * @param width width of the input frames in pixels\n     * @param height height of the input frames in pixels\n     */\n    public native void allocateMosaicMemory(int width, int height);\n\n    /**\n     * Free memory allocated by allocateMosaicMemory.\n     *\n     */\n    public native void freeMosaicMemory();\n\n    /**\n     * Pass the input image frame to the native layer. Each time the a new\n     * source image t is set, the transformation matrix from the first source\n     * image to t is computed and returned.\n     *\n     * @param pixels source image of NV21 format.\n     * @return Float array of length 11; first 9 entries correspond to the 3x3\n     *         transformation matrix between the first frame and the passed frame;\n     *         the 10th entry is the number of the passed frame, where the counting\n     *         starts from 1; and the 11th entry is the returning code, whose value\n     *         is one of those MOSAIC_RET_* returning flags defined above.\n     */\n    public native float[] setSourceImage(byte[] pixels);\n\n    /**\n     * This is an alternative to the setSourceImage function above. This should\n     * be called when the image data is already on the native side in a fixed\n     * byte array. In implementation, this array is filled by the GL thread\n     * using glReadPixels directly from GPU memory (where it is accessed by\n     * an associated SurfaceTexture).\n     *\n     * @return Float array of length 11; first 9 entries correspond to the 3x3\n     *         transformation matrix between the first frame and the passed frame;\n     *         the 10th entry is the number of the passed frame, where the counting\n     *         starts from 1; and the 11th entry is the returning code, whose value\n     *         is one of those MOSAIC_RET_* returning flags defined above.\n     */\n    public native float[] setSourceImageFromGPU();\n\n    /**\n     * Set the type of blending.\n     *\n     * @param type the blending type defined in the class. {BLENDTYPE_FULL,\n     *        BLENDTYPE_PAN, BLENDTYPE_CYLINDERPAN, BLENDTYPE_HORIZONTAL}\n     */\n    public native void setBlendingType(int type);\n\n    /**\n     * Set the type of strips to use for blending.\n     * @param type the blending strip type to use {STRIPTYPE_THIN,\n     * STRIPTYPE_WIDE}.\n     */\n    public native void setStripType(int type);\n\n    /**\n     * Tell the native layer to create the final mosaic after all the input frame\n     * data have been collected.\n     * The case of generating high-resolution mosaic may take dozens of seconds to finish.\n     *\n     * @param value True means generating a high-resolution mosaic -\n     *        which is based on the original images set in setSourceImage().\n     *        False means generating a low-resolution version -\n     *        which is based on 1/4 downscaled images from the original images.\n     * @return Returns a status code suggesting if the mosaic building was\n     *        successful, in error, or was cancelled by the user.\n     */\n    public native int createMosaic(boolean value);\n\n    /**\n     * Get the data for the created mosaic.\n     *\n     * @return Returns an integer array which contains the final mosaic in the ARGB_8888 format.\n     *         The first MosaicWidth*MosaicHeight values contain the image data, followed by 2\n     *         integers corresponding to the values MosaicWidth and MosaicHeight respectively.\n     */\n    public native int[] getFinalMosaic();\n\n    /**\n     * Get the data for the created mosaic.\n     *\n     * @return Returns a byte array which contains the final mosaic in the NV21 format.\n     *         The first MosaicWidth*MosaicHeight*1.5 values contain the image data, followed by\n     *         8 bytes which pack the MosaicWidth and MosaicHeight integers into 4 bytes each\n     *         respectively.\n     */\n    public native byte[] getFinalMosaicNV21();\n\n    /**\n     * Reset the state of the frame arrays which maintain the captured frame data.\n     * Also re-initializes the native mosaic object to make it ready for capturing a new mosaic.\n     */\n    public native void reset();\n\n    /**\n     * Get the progress status of the mosaic computation process.\n     * @param hires Boolean flag to select whether to report progress of the\n     *              low-res or high-res mosaicer.\n     * @param cancelComputation Boolean flag to allow cancelling the\n     *              mosaic computation when needed from the GUI end.\n     * @return Returns a number from 0-100 where 50 denotes that the mosaic\n     *          computation is 50% done.\n     */\n    public native int reportProgress(boolean hires, boolean cancelComputation);\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/MosaicFrameProcessor.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.pano;\n\nimport android.util.Log;\n\n/**\n * Class to handle the processing of each frame by Mosaicer.\n */\npublic class MosaicFrameProcessor {\n    private static final String TAG = \"MosaicFrameProcessor\";\n    private static final int NUM_FRAMES_IN_BUFFER = 2;\n    private static final int MAX_NUMBER_OF_FRAMES = 200;\n    private static final int MOSAIC_RET_CODE_INDEX = 10;\n    private static final int FRAME_COUNT_INDEX = 9;\n    private static final int X_COORD_INDEX = 2;\n    private static final int Y_COORD_INDEX = 5;\n    private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;\n    private static final int WINDOW_SIZE = 3;\n\n    private Mosaic mMosaicer;\n    private boolean mIsMosaicMemoryAllocated = false;\n    private float mTranslationLastX;\n    private float mTranslationLastY;\n\n    private int mFillIn = 0;\n    private int mTotalFrameCount = 0;\n    private int mLastProcessFrameIdx = -1;\n    private int mCurrProcessFrameIdx = -1;\n    private boolean mFirstRun;\n\n    // Panning rate is in unit of percentage of image content translation per\n    // frame. Use moving average to calculate the panning rate.\n    private float mPanningRateX;\n    private float mPanningRateY;\n\n    private float[] mDeltaX = new float[WINDOW_SIZE];\n    private float[] mDeltaY = new float[WINDOW_SIZE];\n    private int mOldestIdx = 0;\n    private float mTotalTranslationX = 0f;\n    private float mTotalTranslationY = 0f;\n\n    private ProgressListener mProgressListener;\n\n    private int mPreviewWidth;\n    private int mPreviewHeight;\n    private int mPreviewBufferSize;\n\n    private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton\n\n    public interface ProgressListener {\n        public void onProgress(boolean isFinished, float panningRateX, float panningRateY,\n                float progressX, float progressY);\n    }\n\n    public static MosaicFrameProcessor getInstance() {\n        if (sMosaicFrameProcessor == null) {\n            sMosaicFrameProcessor = new MosaicFrameProcessor();\n        }\n        return sMosaicFrameProcessor;\n    }\n\n    private MosaicFrameProcessor() {\n        mMosaicer = new Mosaic();\n    }\n\n    public void setProgressListener(ProgressListener listener) {\n        mProgressListener = listener;\n    }\n\n    public int reportProgress(boolean hires, boolean cancel) {\n        return mMosaicer.reportProgress(hires, cancel);\n    }\n\n    public void initialize(int previewWidth, int previewHeight, int bufSize) {\n        mPreviewWidth = previewWidth;\n        mPreviewHeight = previewHeight;\n        mPreviewBufferSize = bufSize;\n        setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);\n        setStripType(Mosaic.STRIPTYPE_WIDE);\n        reset();\n    }\n\n    public void clear() {\n        if (mIsMosaicMemoryAllocated) {\n            mMosaicer.freeMosaicMemory();\n            mIsMosaicMemoryAllocated = false;\n        }\n        synchronized (this) {\n            notify();\n        }\n    }\n\n    public boolean isMosaicMemoryAllocated() {\n        return mIsMosaicMemoryAllocated;\n    }\n\n    public void setStripType(int type) {\n        mMosaicer.setStripType(type);\n    }\n\n    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {\n        Log.v(TAG, \"setupMosaicer w, h=\" + previewWidth + ',' + previewHeight + ',' + bufSize);\n\n        if (mIsMosaicMemoryAllocated) {\n            throw new RuntimeException(\"MosaicFrameProcessor in use!\");\n        }\n        mIsMosaicMemoryAllocated = true;\n        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);\n    }\n\n    public void reset() {\n        // reset() can be called even if MosaicFrameProcessor is not initialized.\n        // Only counters will be changed.\n        mFirstRun = true;\n        mTotalFrameCount = 0;\n        mFillIn = 0;\n        mTotalTranslationX = 0;\n        mTranslationLastX = 0;\n        mTotalTranslationY = 0;\n        mTranslationLastY = 0;\n        mPanningRateX = 0;\n        mPanningRateY = 0;\n        mLastProcessFrameIdx = -1;\n        mCurrProcessFrameIdx = -1;\n        for (int i = 0; i < WINDOW_SIZE; ++i) {\n            mDeltaX[i] = 0f;\n            mDeltaY[i] = 0f;\n        }\n        mMosaicer.reset();\n    }\n\n    public int createMosaic(boolean highRes) {\n        return mMosaicer.createMosaic(highRes);\n    }\n\n    public byte[] getFinalMosaicNV21() {\n        return mMosaicer.getFinalMosaicNV21();\n    }\n\n    // Processes the last filled image frame through the mosaicer and\n    // updates the UI to show progress.\n    // When done, processes and displays the final mosaic.\n    public void processFrame() {\n        if (!mIsMosaicMemoryAllocated) {\n            // clear() is called and buffers are cleared, stop computation.\n            // This can happen when the onPause() is called in the activity, but still some frames\n            // are not processed yet and thus the callback may be invoked.\n            return;\n        }\n\n        mCurrProcessFrameIdx = mFillIn;\n        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);\n\n        // Check that we are trying to process a frame different from the\n        // last one processed (useful if this class was running asynchronously)\n        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {\n            mLastProcessFrameIdx = mCurrProcessFrameIdx;\n\n            // TODO: make the termination condition regarding reaching\n            // MAX_NUMBER_OF_FRAMES solely determined in the library.\n            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {\n                // If we are still collecting new frames for the current mosaic,\n                // process the new frame.\n                calculateTranslationRate();\n\n                // Publish progress of the ongoing processing\n                if (mProgressListener != null) {\n                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,\n                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,\n                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);\n                }\n            } else {\n                if (mProgressListener != null) {\n                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,\n                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,\n                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);\n                }\n            }\n        }\n    }\n\n    public void calculateTranslationRate() {\n        float[] frameData = mMosaicer.setSourceImageFromGPU();\n        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];\n        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];\n        float translationCurrX = frameData[X_COORD_INDEX];\n        float translationCurrY = frameData[Y_COORD_INDEX];\n\n        if (mFirstRun) {\n            // First time: no need to update delta values.\n            mTranslationLastX = translationCurrX;\n            mTranslationLastY = translationCurrY;\n            mFirstRun = false;\n            return;\n        }\n\n        // Moving average: remove the oldest translation/deltaTime and\n        // add the newest translation/deltaTime in\n        int idx = mOldestIdx;\n        mTotalTranslationX -= mDeltaX[idx];\n        mTotalTranslationY -= mDeltaY[idx];\n        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);\n        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);\n        mTotalTranslationX += mDeltaX[idx];\n        mTotalTranslationY += mDeltaY[idx];\n\n        // The panning rate is measured as the rate of the translation percentage in\n        // image width/height. Take the horizontal panning rate for example, the image width\n        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).\n        // To get the horizontal translation percentage, the horizontal translation,\n        // (translationCurrX - mTranslationLastX), is divided by the\n        // image width. We then get the rate by dividing the translation percentage with the\n        // number of frames.\n        mPanningRateX = mTotalTranslationX /\n                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;\n        mPanningRateY = mTotalTranslationY /\n                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;\n\n        mTranslationLastX = translationCurrX;\n        mTranslationLastY = translationCurrY;\n        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/MosaicPreviewRenderer.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.pano;\n\nimport android.graphics.SurfaceTexture;\nimport android.os.ConditionVariable;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.util.Log;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLContext;\nimport javax.microedition.khronos.egl.EGLDisplay;\nimport javax.microedition.khronos.egl.EGLSurface;\nimport javax.microedition.khronos.opengles.GL10;\n\npublic class MosaicPreviewRenderer {\n    private static final String TAG = \"MosaicPreviewRenderer\";\n    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;\n    private static final boolean DEBUG = false;\n\n    private int mWidth; // width of the view in UI\n    private int mHeight; // height of the view in UI\n\n    private boolean mIsLandscape = true;\n    private final float[] mTransformMatrix = new float[16];\n\n    private ConditionVariable mEglThreadBlockVar = new ConditionVariable();\n    private HandlerThread mEglThread;\n    private EGLHandler mEglHandler;\n\n    private EGLConfig mEglConfig;\n    private EGLDisplay mEglDisplay;\n    private EGLContext mEglContext;\n    private EGLSurface mEglSurface;\n    private SurfaceTexture mMosaicOutputSurfaceTexture;\n    private SurfaceTexture mInputSurfaceTexture;\n    private EGL10 mEgl;\n    private GL10 mGl;\n\n    private class EGLHandler extends Handler {\n        public static final int MSG_INIT_EGL_SYNC = 0;\n        public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1;\n        public static final int MSG_SHOW_PREVIEW_FRAME = 2;\n        public static final int MSG_ALIGN_FRAME_SYNC = 3;\n        public static final int MSG_RELEASE = 4;\n\n        public EGLHandler(Looper looper) {\n            super(looper);\n        }\n\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n                case MSG_INIT_EGL_SYNC:\n                    doInitGL();\n                    mEglThreadBlockVar.open();\n                    break;\n                case MSG_SHOW_PREVIEW_FRAME_SYNC:\n                    doShowPreviewFrame();\n                    mEglThreadBlockVar.open();\n                    break;\n                case MSG_SHOW_PREVIEW_FRAME:\n                    doShowPreviewFrame();\n                    break;\n                case MSG_ALIGN_FRAME_SYNC:\n                    doAlignFrame();\n                    mEglThreadBlockVar.open();\n                    break;\n                case MSG_RELEASE:\n                    doRelease();\n                    break;\n            }\n        }\n\n        private void doAlignFrame() {\n            mInputSurfaceTexture.updateTexImage();\n            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);\n\n            MosaicRenderer.setWarping(true);\n            // Call preprocess to render it to low-res and high-res RGB textures.\n            MosaicRenderer.preprocess(mTransformMatrix);\n            // Now, transfer the textures from GPU to CPU memory for processing\n            MosaicRenderer.transferGPUtoCPU();\n            MosaicRenderer.updateMatrix();\n            draw();\n            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);\n        }\n\n        private void doShowPreviewFrame() {\n            mInputSurfaceTexture.updateTexImage();\n            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);\n\n            MosaicRenderer.setWarping(false);\n            // Call preprocess to render it to low-res and high-res RGB textures.\n            MosaicRenderer.preprocess(mTransformMatrix);\n            MosaicRenderer.updateMatrix();\n            draw();\n            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);\n        }\n\n        private void doInitGL() {\n            // These are copied from GLSurfaceView\n            mEgl = (EGL10) EGLContext.getEGL();\n            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);\n            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {\n                throw new RuntimeException(\"eglGetDisplay failed\");\n            }\n            int[] version = new int[2];\n            if (!mEgl.eglInitialize(mEglDisplay, version)) {\n                throw new RuntimeException(\"eglInitialize failed\");\n            } else {\n                Log.v(TAG, \"EGL version: \" + version[0] + '.' + version[1]);\n            }\n            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };\n            mEglConfig = chooseConfig(mEgl, mEglDisplay);\n            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT,\n                    attribList);\n\n            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {\n                throw new RuntimeException(\"failed to createContext\");\n            }\n\n            mEglSurface = mEgl.eglCreateWindowSurface(\n                    mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null);\n            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {\n                throw new RuntimeException(\"failed to createWindowSurface\");\n            }\n\n            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {\n                throw new RuntimeException(\"failed to eglMakeCurrent\");\n            }\n\n            mGl = (GL10) mEglContext.getGL();\n\n            mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init());\n            MosaicRenderer.reset(mWidth, mHeight, true);\n        }\n\n        private void doRelease() {\n            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);\n            mEgl.eglDestroyContext(mEglDisplay, mEglContext);\n            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,\n                    EGL10.EGL_NO_CONTEXT);\n            mEgl.eglTerminate(mEglDisplay);\n            mEglSurface = null;\n            mEglContext = null;\n            mEglDisplay = null;\n            releaseSurfaceTexture(mInputSurfaceTexture);\n            mEglThread.quit();\n        }\n\n        private void releaseSurfaceTexture(SurfaceTexture st) {\n            st.release();\n        }\n\n        // Should be called from other thread.\n        public void sendMessageSync(int msg) {\n            mEglThreadBlockVar.close();\n            sendEmptyMessage(msg);\n            mEglThreadBlockVar.block();\n        }\n\n    }\n\n    public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) {\n        mMosaicOutputSurfaceTexture = tex;\n        mWidth = w;\n        mHeight = h;\n        mIsLandscape = isLandscape;\n\n        mEglThread = new HandlerThread(\"PanoramaRealtimeRenderer\");\n        mEglThread.start();\n        mEglHandler = new EGLHandler(mEglThread.getLooper());\n\n        // We need to sync this because the generation of surface texture for input is\n        // done here and the client will continue with the assumption that the\n        // generation is completed.\n        mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC);\n    }\n\n    public void release() {\n        mEglHandler.sendEmptyMessage(EGLHandler.MSG_RELEASE);\n    }\n\n    public void setLandscape(boolean landscape) {\n        MosaicRenderer.setIsLandscape(landscape);\n    }\n\n    public void showPreviewFrameSync() {\n        mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC);\n    }\n\n    public void showPreviewFrame() {\n        mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME);\n    }\n\n    public void alignFrameSync() {\n        mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC);\n    }\n\n    public SurfaceTexture getInputSurfaceTexture() {\n        return mInputSurfaceTexture;\n    }\n\n    private void draw() {\n        MosaicRenderer.step();\n    }\n\n    private static void checkEglError(String prompt, EGL10 egl) {\n        int error;\n        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {\n            Log.e(TAG, String.format(\"%s: EGL error: 0x%x\", prompt, error));\n        }\n    }\n\n    private static final int EGL_OPENGL_ES2_BIT = 4;\n    private static final int[] CONFIG_SPEC = new int[] {\n            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n            EGL10.EGL_RED_SIZE, 8,\n            EGL10.EGL_GREEN_SIZE, 8,\n            EGL10.EGL_BLUE_SIZE, 8,\n            EGL10.EGL_NONE\n    };\n\n    private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {\n        int[] numConfig = new int[1];\n        if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) {\n            throw new IllegalArgumentException(\"eglChooseConfig failed\");\n        }\n\n        int numConfigs = numConfig[0];\n        if (numConfigs <= 0) {\n            throw new IllegalArgumentException(\"No configs match configSpec\");\n        }\n\n        EGLConfig[] configs = new EGLConfig[numConfigs];\n        if (!egl.eglChooseConfig(\n                display, CONFIG_SPEC, configs, numConfigs, numConfig)) {\n            throw new IllegalArgumentException(\"eglChooseConfig#2 failed\");\n        }\n\n        return configs[0];\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/MosaicProxy.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.pano;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.ImageFormat;\nimport android.graphics.Matrix;\nimport android.graphics.PixelFormat;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.graphics.SurfaceTexture;\nimport android.graphics.YuvImage;\nimport android.hardware.Camera;\nimport android.media.ExifInterface;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.TextureView;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.Storage;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.feats.CaptureTransformer;\nimport org.cyanogenmod.focal.ui.PanoProgressBar;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\n\nimport fr.xplod.focal.R;\n\n/**\n * Nemesis interface to interact with Google's mosaic interface\n * Strongly inspired from AOSP's PanoramaModule\n */\npublic class MosaicProxy extends CaptureTransformer\n        implements SurfaceTexture.OnFrameAvailableListener, TextureView.SurfaceTextureListener {\n    private static final String TAG = \"CAM PanoModule\";\n\n    public static final int DEFAULT_SWEEP_ANGLE = 360;\n    // The unit of speed is degrees per frame.\n    private static final float PANNING_SPEED_THRESHOLD = 2.5f;\n\n    private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;\n    private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2;\n    private static final int MSG_RESET_TO_PREVIEW = 3;\n    private static final int MSG_CLEAR_SCREEN_DELAY = 4;\n\n    private static final int CAPTURE_STATE_VIEWFINDER = 0;\n    private static final int CAPTURE_STATE_MOSAIC = 1;\n    private final int mIndicatorColor;\n    private final int mIndicatorColorFast;\n\n    private Runnable mOnFrameAvailableRunnable;\n    private MosaicFrameProcessor mMosaicFrameProcessor;\n    private MosaicPreviewRenderer mMosaicPreviewRenderer;\n    private boolean mMosaicFrameProcessorInitialized;\n    private float mHorizontalViewAngle;\n    private float mVerticalViewAngle;\n\n    private ShutterButton mShutterButton;\n    private int mCaptureState;\n    private FrameLayout mGLRootView;\n    private TextureView mGLSurfaceView;\n    private CameraActivity mActivity;\n    private Handler mMainHandler;\n    private SurfaceTexture mCameraTexture;\n    private SurfaceTexture mMosaicTexture;\n    private boolean mCancelComputation;\n    private long mTimeTaken;\n    private PanoProgressBar mPanoProgressBar;\n    private Matrix mProgressDirectionMatrix = new Matrix();\n    private float[] mProgressAngle = new float[2];\n    private int mPreviewWidth;\n    private int mPreviewHeight;\n    private boolean mThreadRunning;\n    final private Object mWaitObject = new Object();\n    private int mCurrentOrientation;\n\n    private class MosaicJpeg {\n        public MosaicJpeg(byte[] data, int width, int height) {\n            this.data = data;\n            this.width = width;\n            this.height = height;\n            this.isValid = true;\n        }\n\n        public MosaicJpeg() {\n            this.data = null;\n            this.width = 0;\n            this.height = 0;\n            this.isValid = false;\n        }\n\n        public final byte[] data;\n        public final int width;\n        public final int height;\n        public final boolean isValid;\n    }\n\n    public MosaicProxy(CameraActivity activity) {\n        super(activity.getCamManager(), activity.getSnapManager());\n        mActivity = activity;\n        mCaptureState = CAPTURE_STATE_VIEWFINDER;\n        mPanoProgressBar = activity.getPanoProgressBar();\n\n\n        mGLRootView = (FrameLayout) mActivity.findViewById(R.id.gl_renderer_container);\n        mGLSurfaceView = new TextureView(mActivity);\n        mGLRootView.addView(mGLSurfaceView);\n\n        mGLSurfaceView.setSurfaceTextureListener(this);\n\n        mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();\n        Resources appRes = mActivity.getResources();\n        mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);\n        mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);\n        mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));\n        mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));\n        mPanoProgressBar.setIndicatorColor(mIndicatorColor);\n        mPanoProgressBar.setOnDirectionChangeListener(\n                new PanoProgressBar.OnDirectionChangeListener () {\n                    @Override\n                    public void onDirectionChange(int direction) {\n                        if (mCaptureState == CAPTURE_STATE_MOSAIC) {\n                            showDirectionIndicators(direction);\n                        }\n                    }\n                });\n\n        // This runs in UI thread.\n        mOnFrameAvailableRunnable = new Runnable() {\n            @Override\n            public void run() {\n                // Frames might still be available after the activity is paused.\n                // If we call onFrameAvailable after pausing, the GL thread will crash.\n                // if (mPaused) return;\n\n                if (mGLRootView.getVisibility() != View.VISIBLE) {\n                    mMosaicPreviewRenderer.showPreviewFrameSync();\n                    mGLRootView.setVisibility(View.VISIBLE);\n                } else {\n                    if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {\n                        mMosaicPreviewRenderer.showPreviewFrame();\n                    } else {\n                        mMosaicPreviewRenderer.alignFrameSync();\n                        mMosaicFrameProcessor.processFrame();\n                    }\n                }\n            }\n        };\n\n        mMainHandler = new Handler() {\n            @Override\n            public void handleMessage(Message msg) {\n                switch (msg.what) {\n                    case MSG_LOW_RES_FINAL_MOSAIC_READY:\n                        //onBackgroundThreadFinished();\n                        //showFinalMosaic((Bitmap) msg.obj);\n                        Util.fadeOut(mGLRootView);\n                        mActivity.displayOverlayBitmap((Bitmap) msg.obj);\n                        saveHighResMosaic();\n                        break;\n                    case MSG_GENERATE_FINAL_MOSAIC_ERROR:\n                        CameraActivity.notify(\n                                mActivity.getString(R.string.pano_panorama_rendering_failed), 2000);\n                        resetToPreview();\n                        break;\n                    case MSG_RESET_TO_PREVIEW:\n                        resetToPreview();\n                        mThreadRunning = false;\n                        break;\n                    case MSG_CLEAR_SCREEN_DELAY:\n                        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.\n                                FLAG_KEEP_SCREEN_ON);\n                        break;\n                }\n            }\n        };\n\n        // Initialization\n        Camera.Parameters params = mActivity.getCamManager().getParameters();\n        if (params != null) {\n            mHorizontalViewAngle = params.getHorizontalViewAngle();\n            mVerticalViewAngle = params.getVerticalViewAngle();\n        } else {\n            mHorizontalViewAngle = 50;\n            mHorizontalViewAngle = 30;\n        }\n\n        int pixels = mActivity.getResources().getInteger(R.integer.config_panoramaDefaultWidth)\n                * mActivity.getResources().getInteger(R.integer.config_panoramaDefaultHeight);\n\n        if (params != null) {\n            Point size = Util.findBestPanoPreviewSize(\n                    params.getSupportedPreviewSizes(), true, true, pixels);\n            mPreviewWidth = size.y;\n            mPreviewHeight = size.x;\n        } else {\n            mPreviewWidth = 480;\n            mPreviewHeight = 640;\n        }\n\n        FrameLayout.LayoutParams layoutParams =\n                (FrameLayout.LayoutParams) mGLSurfaceView.getLayoutParams();\n        layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;\n        layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;\n        layoutParams.gravity = Gravity.CENTER;\n        mGLSurfaceView.setLayoutParams(layoutParams);\n\n        layoutParams = (FrameLayout.LayoutParams) mGLRootView.getLayoutParams();\n        layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;\n        layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;\n        layoutParams.gravity = Gravity.CENTER;\n        mGLSurfaceView.setLayoutParams(layoutParams);\n    }\n\n    // This function will be called upon the first camera frame is available.\n    private void resetToPreview() {\n        mCaptureState = CAPTURE_STATE_VIEWFINDER;\n        if (mShutterButton != null) {\n            mShutterButton.setImageResource(R.drawable.btn_shutter_photo);\n        }\n\n        mCamManager.setLockSetup(false);\n        Util.fadeIn(mShutterButton);\n        Util.fadeIn(mGLRootView);\n        mActivity.hideOverlayBitmap();\n        Util.fadeOut(mPanoProgressBar);\n        mMosaicFrameProcessor.reset();\n        mCameraTexture.setOnFrameAvailableListener(this);\n        setupProgressDirectionMatrix();\n        mCamManager.setRenderToTexture(mCameraTexture);\n    }\n\n\n    /**\n     * Call this when you're done using MosaicProxy, to remove views added and shutdown\n     * threads.\n     */\n    public void tearDown() {\n        mGLRootView.removeView(mGLSurfaceView);\n        mMosaicFrameProcessor.clear();\n    }\n\n    private void configMosaicPreview() {\n        boolean isLandscape = false;\n        Log.d(TAG, \"isLandscape ? \" + isLandscape +\n                \" (orientation: \" + mCamManager.getOrientation() + \")\");\n\n        int viewWidth = mGLRootView.getMeasuredWidth();\n        int viewHeight = mGLRootView.getMeasuredHeight();\n\n        mMosaicPreviewRenderer = new MosaicPreviewRenderer(mMosaicTexture, viewWidth,\n                viewHeight, isLandscape);\n\n        mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();\n        mCameraTexture.setDefaultBufferSize(mPreviewWidth, mPreviewHeight);\n        mCameraTexture.setOnFrameAvailableListener(this);\n        mMainHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                mActivity.getCamManager().setRenderToTexture(mCameraTexture);\n            }\n        });\n        mMosaicTexture.setDefaultBufferSize(viewWidth, viewHeight);\n    }\n\n    @Override\n    public void onFrameAvailable(SurfaceTexture surfaceTexture) {\n        /* This function may be called by some random thread,\n         * so let's be safe and jump back to ui thread.\n         * No OpenGL calls can be done here. */\n        mActivity.runOnUiThread(mOnFrameAvailableRunnable);\n    }\n\n    @Override\n    public void onShutterButtonClicked(ShutterButton button) {\n        mShutterButton = button;\n\n        if (mCaptureState == CAPTURE_STATE_MOSAIC) {\n            stopCapture(false);\n            Util.fadeOut(button);\n        } else {\n            startCapture();\n            button.setImageResource(R.drawable.btn_shutter_stop);\n        }\n    }\n\n    @Override\n    public void onSnapshotShutter(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onMediaSavingStart() {\n\n    }\n\n    @Override\n    public void onMediaSavingDone() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStart() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStop() {\n\n    }\n\n    @Override\n    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {\n        mMosaicTexture = surfaceTexture;\n        initMosaicFrameProcessorIfNeeded();\n        configMosaicPreview();\n    }\n\n    public int getPreviewBufSize() {\n        PixelFormat pixelInfo = new PixelFormat();\n        PixelFormat.getPixelFormatInfo(PixelFormat.RGB_888, pixelInfo);\n        // TODO: remove this extra 32 byte after the driver bug is fixed.\n        return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;\n    }\n\n    @Override\n    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {\n\n    }\n\n    @Override\n    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {\n        return false;\n    }\n\n    @Override\n    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {\n\n    }\n\n    public void startCapture() {\n        Log.v(TAG, \"Starting Panorama capture\");\n\n        // Reset values so we can do this again.\n        mCancelComputation = false;\n        mTimeTaken = System.currentTimeMillis();\n        // mShutterButton.setImageResource(R.drawable.btn_shutter_recording);\n        mCaptureState = CAPTURE_STATE_MOSAIC;\n        //mCaptureIndicator.setVisibility(View.VISIBLE);\n        //showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);\n        mPanoProgressBar.setDoneColor(mActivity.getResources().getColor(R.color.pano_progress_done));\n        mPanoProgressBar.setIndicatorColor(mIndicatorColor);\n\n        boolean isLandscape = (mCamManager.getOrientation() != -90);\n        Log.d(TAG, \"isLandscape ? \" + isLandscape + \" (orientation: \"\n                + mCamManager.getOrientation() + \")\");\n        mMosaicPreviewRenderer.setLandscape(isLandscape);\n\n        mCurrentOrientation = mActivity.getCamManager().getOrientation();\n        if (mCurrentOrientation == -90) {\n            mCurrentOrientation = 90;\n        }\n        if (mCurrentOrientation < 0) {\n            mCurrentOrientation += 360;\n        }\n\n        mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {\n            @Override\n            public void onProgress(boolean isFinished, float panningRateX, float panningRateY,\n                    float progressX, float progressY) {\n                float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;\n                float accumulatedVerticalAngle = progressY * mVerticalViewAngle;\n                if (isFinished\n                        || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)\n                        || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {\n                    Util.fadeOut(mShutterButton);\n                    stopCapture(false);\n                } else {\n                    float panningRateXInDegree = panningRateX * mHorizontalViewAngle;\n                    float panningRateYInDegree = panningRateY * mVerticalViewAngle;\n                    updateProgress(panningRateXInDegree, panningRateYInDegree,\n                            accumulatedHorizontalAngle, accumulatedVerticalAngle);\n                }\n            }\n        });\n\n        mPanoProgressBar.reset();\n        // TODO: calculate the indicator width according to different devices to reflect the actual\n        // angle of view of the camera device.\n        mPanoProgressBar.setIndicatorWidth(20);\n        mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE);\n        mPanoProgressBar.setVisibility(View.VISIBLE);\n        Util.fadeIn(mPanoProgressBar);\n        //mDeviceOrientationAtCapture = mDeviceOrientation;\n        //keepScreenOn();\n        //mActivity.getOrientationManager().lockOrientation();\n        setupProgressDirectionMatrix();\n    }\n\n    private void stopCapture(boolean aborted) {\n        mCaptureState = CAPTURE_STATE_VIEWFINDER;\n        //mCaptureIndicator.setVisibility(View.GONE);\n        //hideTooFastIndication();\n        //hideDirectionIndicators();\n\n        mMosaicFrameProcessor.setProgressListener(null);\n        //stopCameraPreview();\n\n        mCameraTexture.setOnFrameAvailableListener(null);\n\n        mPanoProgressBar.setDoneColor(mActivity.getResources().getColor(R.color.pano_saving_done));\n        mPanoProgressBar.setIndicatorColor(mActivity.getResources().getColor(R.color.pano_saving_indication));\n\n        if (!aborted && !mThreadRunning) {\n            //mRotateDialog.showWaitingDialog(mPreparePreviewString);\n            // Hide shutter button, shutter icon, etc when waiting for\n            // panorama to stitch\n            //mActivity.hideUI();\n            runInBackground(new Thread() {\n                @Override\n                public void run() {\n                    MosaicJpeg jpeg = generateFinalMosaic(false);\n\n                    if (jpeg != null && jpeg.isValid) {\n                        Bitmap bitmap = null;\n                        bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);\n                        mMainHandler.sendMessage(mMainHandler.obtainMessage(\n                                MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));\n                    } else {\n                        CameraActivity.notify(\n                                mActivity.getString(R.string.pano_panorama_rendering_failed), 2000);\n                        mMainHandler.sendMessage(mMainHandler.obtainMessage(\n                                MSG_RESET_TO_PREVIEW));\n                    }\n                }\n            });\n        }\n    }\n\n    /**\n     * Generate the final mosaic image.\n     *\n     * @param highRes flag to indicate whether we want to get a high-res version.\n     * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation\n     *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there\n     *         is an error in generating the final mosaic.\n     */\n    public MosaicJpeg generateFinalMosaic(boolean highRes) {\n        int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);\n        if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {\n            return null;\n        } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {\n            return new MosaicJpeg();\n        }\n\n        byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();\n        if (imageData == null) {\n            Log.e(TAG, \"getFinalMosaicNV21() returned null.\");\n            return new MosaicJpeg();\n        }\n\n        int len = imageData.length - 8;\n        int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)\n                + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);\n        int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)\n                + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);\n        Log.v(TAG, \"ImLength = \" + (len) + \", W = \" + width + \", H = \" + height);\n\n        if (width <= 0 || height <= 0) {\n            CameraActivity.notify(\n                    mActivity.getString(R.string.pano_panorama_rendering_failed), 2000);\n            Log.e(TAG, \"width|height <= 0!!, len = \" + (len) + \", W = \" + width + \", H = \" +\n                    height);\n            return new MosaicJpeg();\n        }\n\n        YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);\n        try {\n            out.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Exception in storing final mosaic\", e);\n            CameraActivity.notify(\n                    mActivity.getString(R.string.pano_panorama_rendering_failed), 2000);\n            return new MosaicJpeg();\n        }\n        return new MosaicJpeg(out.toByteArray(), width, height);\n    }\n\n    void setupProgressDirectionMatrix() {\n        int degrees = Util.getDisplayRotation(mActivity);\n        int cameraId = 0; //CameraHolder.instance().getBackCameraId();\n        int orientation = 0; // TODO //Util.getDisplayOrientation(degrees, cameraId);\n        mProgressDirectionMatrix.reset();\n        mProgressDirectionMatrix.postRotate(orientation);\n    }\n\n    public void saveHighResMosaic() {\n        CameraActivity.notify(mActivity.getString(R.string.pano_panorama_rendering), 3000);\n        runInBackground(new Thread() {\n            @Override\n            public void run() {\n                //mPartialWakeLock.acquire();\n                MosaicJpeg jpeg;\n                try {\n                    jpeg = generateFinalMosaic(true);\n                } finally {\n                    //mPartialWakeLock.release();\n                }\n\n                if (jpeg == null) {  // Cancelled by user.\n                    mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);\n                } else if (!jpeg.isValid) {  // Error when generating mosaic.\n                    mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);\n                } else {\n                    Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, mCurrentOrientation);\n                    if (uri != null) {\n                        Util.broadcastNewPicture(mActivity, uri);\n                        mActivity.getReviewDrawer().updateFromGallery(true, 0);\n                    }\n\n                    mMainHandler.sendMessage(\n                            mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW));\n                }\n            }\n        });\n        reportProgress();\n    }\n\n    private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {\n        if (jpegData != null) {\n            String filename = PanoUtil.createName(\n                    mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);\n            String filePath = Storage.getStorage().writeFile(filename, jpegData);\n\n            // Add Exif tags.\n            try {\n                ExifInterface exif = new ExifInterface(filePath);\n                /*exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP,\n                        mGPSDateStampFormat.format(mTimeTaken));\n                exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP,\n                        mGPSTimeStampFormat.format(mTimeTaken));\n                exif.setAttribute(ExifInterface.TAG_DATETIME,\n                        mDateTimeStampFormat.format(mTimeTaken));*/\n                exif.setAttribute(ExifInterface.TAG_ORIENTATION,\n                        getExifOrientation(orientation));\n                exif.saveAttributes();\n            } catch (IOException e) {\n                Log.e(TAG, \"Cannot set EXIF for \" + filePath, e);\n            }\n\n            int jpegLength = (int) (new File(filePath).length());\n            return Storage.getStorage().addImage(mActivity.getContentResolver(), filename,\n                    mTimeTaken, null, orientation, jpegLength, filePath, width, height);\n        }\n        return null;\n    }\n\n    private static String getExifOrientation(int orientation) {\n        orientation = (orientation + 360) % 360;\n\n        switch (orientation) {\n            case 0:\n                return String.valueOf(ExifInterface.ORIENTATION_NORMAL);\n            case 90:\n                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);\n            case 180:\n                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);\n            case 270:\n                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);\n            default:\n                throw new AssertionError(\"invalid: \" + orientation);\n        }\n    }\n\n    private void initMosaicFrameProcessorIfNeeded() {\n        //if (mPaused || mThreadRunning) return;\n        if (!mMosaicFrameProcessorInitialized) {\n            mMosaicFrameProcessor.initialize(\n                    mPreviewWidth, mPreviewHeight, getPreviewBufSize());\n            mMosaicFrameProcessorInitialized = true;\n        }\n    }\n\n    private void updateProgress(float panningRateXInDegree, float panningRateYInDegree,\n            float progressHorizontalAngle, float progressVerticalAngle) {\n        mGLRootView.invalidate();\n\n        if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD)\n                || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) {\n            showTooFastIndication();\n        } else {\n            hideTooFastIndication();\n        }\n\n        // progressHorizontalAngle and progressVerticalAngle are relative to the\n        // camera. Convert them to UI direction.\n        mProgressAngle[0] = progressHorizontalAngle;\n        mProgressAngle[1] = -progressVerticalAngle;\n        mProgressDirectionMatrix.mapPoints(mProgressAngle);\n\n        int angleInMajorDirection =\n                (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))\n                        ? (int) mProgressAngle[0]\n                        : (int) mProgressAngle[1];\n        mPanoProgressBar.setProgress((angleInMajorDirection));\n    }\n\n    /**\n     * Report saving progress\n     */\n    public void reportProgress() {\n        mPanoProgressBar.reset();\n        mPanoProgressBar.setRightIncreasing(true);\n        mPanoProgressBar.setMaxProgress(100);\n        Thread t = new Thread() {\n            @Override\n            public void run() {\n                while (mThreadRunning) {\n                    final int progress = mMosaicFrameProcessor.reportProgress(\n                            true, mCancelComputation);\n\n                    try {\n                        synchronized (mWaitObject) {\n                            mWaitObject.wait(50);\n                        }\n                    } catch (InterruptedException e) {\n                        throw new RuntimeException(\"Panorama reportProgress failed\", e);\n                    }\n                    // Update the progress bar\n                    mActivity.runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n                            mPanoProgressBar.setProgress(progress);\n                        }\n                    });\n                }\n            }\n        };\n        t.start();\n    }\n\n    private void runInBackground(Thread t) {\n        mThreadRunning = true;\n        t.start();\n    }\n\n    private void showTooFastIndication() {\n        //mTooFastPrompt.setVisibility(View.VISIBLE);\n        // The PreviewArea also contains the border for \"too fast\" indication.\n        //mPreviewArea.setVisibility(View.VISIBLE);\n        mPanoProgressBar.setIndicatorColor(mIndicatorColorFast);\n        //mLeftIndicator.setEnabled(true);\n        //mRightIndicator.setEnabled(true);\n    }\n\n    private void hideTooFastIndication() {\n        //mTooFastPrompt.setVisibility(View.GONE);\n        // We set \"INVISIBLE\" instead of \"GONE\" here because we need mPreviewArea to have layout\n        // information so we can know the size and position for mCameraScreenNail.\n        //mPreviewArea.setVisibility(View.INVISIBLE);\n        mPanoProgressBar.setIndicatorColor(mIndicatorColor);\n        //mLeftIndicator.setEnabled(false);\n        //mRightIndicator.setEnabled(false);\n    }\n\n    private void hideDirectionIndicators() {\n        /*mLeftIndicator.setVisibility(View.GONE);\n        mRightIndicator.setVisibility(View.GONE);*/\n    }\n\n    private void showDirectionIndicators(int direction) {\n        /*switch (direction) {\n            case PanoProgressBar.DIRECTION_NONE:\n                mLeftIndicator.setVisibility(View.VISIBLE);\n                mRightIndicator.setVisibility(View.VISIBLE);\n                break;\n            case PanoProgressBar.DIRECTION_LEFT:\n                mLeftIndicator.setVisibility(View.VISIBLE);\n                mRightIndicator.setVisibility(View.GONE);\n                break;\n            case PanoProgressBar.DIRECTION_RIGHT:\n                mLeftIndicator.setVisibility(View.GONE);\n                mRightIndicator.setVisibility(View.VISIBLE);\n                break;\n        }*/\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/MosaicRenderer.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.pano;\n\n/**\n * The Java interface to JNI calls regarding mosaic preview rendering.\n *\n */\npublic class MosaicRenderer {\n    static {\n        System.loadLibrary(\"jni_mosaic2\");\n    }\n\n    /**\n     * Function to be called in onSurfaceCreated() to initialize\n     * the GL context, load and link the shaders and create the\n     * program. Returns a texture ID to be used for SurfaceTexture.\n     *\n     * @return textureID the texture ID of the newly generated texture to\n     *          be assigned to the SurfaceTexture object.\n     */\n    public static native int init();\n\n    /**\n     * Pass the drawing surface's width and height to initialize the\n     * renderer viewports and FBO dimensions.\n     *\n     * @param width width of the drawing surface in pixels.\n     * @param height height of the drawing surface in pixels.\n     * @param isLandscapeOrientation is the orientation of the activity layout in landscape.\n     */\n    public static native void reset(int width, int height, boolean isLandscapeOrientation);\n\n    /**\n     * Changes the orientation of the stitching\n     *\n     * @param isLandscape\n     */\n    public static native void setIsLandscape(boolean isLandscape);\n\n    /**\n     * Calling this function will render the SurfaceTexture to a new 2D texture\n     * using the provided STMatrix.\n     *\n     * @param stMatrix texture coordinate transform matrix obtained from the\n     *        Surface texture\n     */\n    public static native void preprocess(float[] stMatrix);\n\n    /**\n     * This function calls glReadPixels to transfer both the low-res and high-res\n     * data from the GPU memory to the CPU memory for further processing by the\n     * mosaicing library.\n     */\n    public static native void transferGPUtoCPU();\n\n    /**\n     * Function to be called in onDrawFrame() to update the screen with\n     * the new frame data.\n     */\n    public static native void step();\n\n    /**\n     * Call this function when a new low-res frame has been processed by\n     * the mosaicing library. This will tell the renderer library to\n     * update its texture and warping transformation. Any calls to step()\n     * after this call will use the new image frame and transformation data.\n     */\n    public static native void updateMatrix();\n\n    /**\n     * This function allows toggling between showing the input image data\n     * (without applying any warp) and the warped image data. For running\n     * the renderer as a viewfinder, we set the flag to false. To see the\n     * preview mosaic, we set the flag to true.\n     *\n     * @param flag boolean flag to set the warping to true or false.\n     */\n    public static native void setWarping(boolean flag);\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/pano/PanoUtil.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.pano;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class PanoUtil {\n    public static String createName(String format, long dateTaken) {\n        Date date = new Date(dateTaken);\n        SimpleDateFormat dateFormat = new SimpleDateFormat(format);\n        return dateFormat.format(date);\n    }\n\n    // TODO: Add comments about the range of these two arguments.\n    public static double calculateDifferenceBetweenAngles(\n            double firstAngle, double secondAngle) {\n        double difference1 = (secondAngle - firstAngle) % 360;\n        if (difference1 < 0) {\n            difference1 += 360;\n        }\n\n        double difference2 = (firstAngle - secondAngle) % 360;\n        if (difference2 < 0) {\n            difference2 += 360;\n        }\n\n        return Math.min(difference1, difference2);\n    }\n\n    public static void decodeYUV420SPQuarterRes(int[] rgb, byte[] yuv420sp, int width, int height) {\n        final int frameSize = width * height;\n\n        for (int j = 0, ypd = 0; j < height; j += 4) {\n            int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;\n            for (int i = 0; i < width; i += 4, ypd++) {\n                int y = (0xff & (yuv420sp[j * width + i])) - 16;\n                if (y < 0) {\n                    y = 0;\n                }\n                if ((i & 1) == 0) {\n                    v = (0xff & yuv420sp[uvp++]) - 128;\n                    u = (0xff & yuv420sp[uvp++]) - 128;\n                    uvp += 2;  // Skip the UV values for the 4 pixels skipped in between\n                }\n                int y1192 = 1192 * y;\n                int r = (y1192 + 1634 * v);\n                int g = (y1192 - 833 * v - 400 * u);\n                int b = (y1192 + 2066 * u);\n\n                if (r < 0) {\n                    r = 0;\n                } else if (r > 262143) {\n                    r = 262143;\n                }\n                if (g < 0) {\n                    g = 0;\n                } else if (g > 262143) {\n                    g = 262143;\n                }\n                if (b < 0) {\n                    b = 0;\n                } else if (b > 262143) {\n                    b = 262143;\n                }\n\n                rgb[ypd] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) |\n                        ((b >> 10) & 0xff);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/Capture3DRenderer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.SurfaceTexture;\nimport android.opengl.GLES11Ext;\nimport android.opengl.GLES20;\nimport android.opengl.GLSurfaceView;\nimport android.opengl.GLUtils;\nimport android.opengl.Matrix;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.FloatBuffer;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\n/**\n * Manages the 3D rendering of the sphere capture mode, using gyroscope to\n * orientate the camera and displays the preview at the center.\n *\n * TODO: Fallback for non-GLES2 devices?\n */\npublic class Capture3DRenderer implements GLSurfaceView.Renderer {\n    public final static String TAG = \"Capture3DRenderer\";\n\n    private final CameraManager mCamManager;\n\n    private List<Snapshot> mSnapshots;\n    private List<Snapshot> mDots;\n    private ReentrantLock mListBusy;\n    private SensorFusion mSensorFusion;\n    private Quaternion mCameraQuat;\n    private Skybox mSkyBox;\n    private float[] mViewMatrix = new float[16];\n    private float[] mProjectionMatrix = new float[16];\n\n    private FloatBuffer mVertexBuffer;\n    private FloatBuffer m43VertexBuffer;\n    private FloatBuffer mTexCoordBuffer;\n    private final static float SNAPSHOT_SCALE = 65.5f;\n    private final static float RATIO = 4.0f/3.0f;\n    private final static float DISTANCE = 135.0f;\n\n    // x, y,\n    private final float mVertexData[] =\n            {\n                    -SNAPSHOT_SCALE,  -SNAPSHOT_SCALE,\n                    -SNAPSHOT_SCALE, SNAPSHOT_SCALE,\n                    SNAPSHOT_SCALE, SNAPSHOT_SCALE,\n                    SNAPSHOT_SCALE, -SNAPSHOT_SCALE\n            };\n\n    private final float m43VertexData[] =\n            {\n                    -SNAPSHOT_SCALE *RATIO,  -SNAPSHOT_SCALE,\n                    -SNAPSHOT_SCALE *RATIO, SNAPSHOT_SCALE,\n                    SNAPSHOT_SCALE *RATIO, SNAPSHOT_SCALE,\n                    SNAPSHOT_SCALE *RATIO, -SNAPSHOT_SCALE\n            };\n\n    // u,v\n    private final float mTexCoordData[] =\n            {\n                    0.0f, 0.0f,\n                    0.0f, 1.0f,\n                    1.0f, 1.0f,\n                    1.0f, 0.0f\n            };\n\n    private final static int CAMERA = 0;\n    private final static int SNAPSHOT = 1;\n\n    private int[] mProgram = new int[2];\n    private int[] mVertexShader = new int[2];\n    private int[] mFragmentShader = new int[2];\n    private int[] mPositionHandler = new int[2];\n    private int[] mTexCoordHandler = new int[2];\n    private int[] mTextureHandler = new int[2];\n    private int[] mAlphaHandler = new int[2];\n    private int[] mMVPMatrixHandler = new int[2];\n\n    private SurfaceTexture mCameraSurfaceTex;\n    private int mCameraTextureId;\n    private Snapshot mCameraBillboard;\n    private Snapshot mViewfinderBillboard;\n    private Context mContext;\n    private Quaternion mTempQuaternion;\n    private float[] mMVPMatrix = new float[16];\n\n    private class Skybox {\n        private float DIST = SNAPSHOT_SCALE;\n        private Snapshot[] mFaces = new Snapshot[6];\n        private int FACE_NORTH = 0;\n        private int FACE_WEST = 1;\n        private int FACE_SOUTH = 2;\n        private int FACE_EAST = 3;\n        private int FACE_UP = 4;\n        private int FACE_DOWN = 5;\n\n        public Skybox() {\n            mFaces[FACE_NORTH] = new Snapshot(false);\n            mFaces[FACE_NORTH].mModelMatrix = matrixFromEuler(0, 90, 0, 0, 0, DIST);\n            mFaces[FACE_NORTH].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_fr));\n\n            mFaces[FACE_SOUTH] = new Snapshot(false);\n            mFaces[FACE_SOUTH].mModelMatrix = matrixFromEuler(0, 90, 0, 0, 0, -DIST);\n            mFaces[FACE_SOUTH].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_bk));\n\n            mFaces[FACE_WEST] = new Snapshot(false);\n            mFaces[FACE_WEST].mModelMatrix = matrixFromEuler(0, 90, 90, 0, 0, DIST);\n            mFaces[FACE_WEST].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_lt));\n\n            mFaces[FACE_EAST] = new Snapshot(false);\n            mFaces[FACE_EAST].mModelMatrix = matrixFromEuler(0, 90, 270, 0, 0, DIST);\n            mFaces[FACE_EAST].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_rt));\n\n            mFaces[FACE_UP] = new Snapshot(false);\n            mFaces[FACE_UP].mModelMatrix = matrixFromEuler(90, 0, 270, 0, 0, DIST);\n            mFaces[FACE_UP].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_up));\n\n            mFaces[FACE_DOWN] = new Snapshot(false);\n            mFaces[FACE_DOWN].mModelMatrix = matrixFromEuler(-90, 0, 90, 0, 0, DIST);\n            mFaces[FACE_DOWN].setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                    R.drawable.picsphere_sky_dn));\n        }\n\n        public void draw() {\n            for (int i = 0; i < mFaces.length; i++) {\n                if (mFaces[i] != null) {\n                    mFaces[i].draw();\n                }\n            }\n        }\n    }\n\n    /**\n     * Stores the information about each snapshot displayed in the sphere\n     */\n    private class Snapshot {\n        private float[]mModelMatrix;\n        private int mTextureData;\n        private Bitmap mBitmapToLoad;\n        private boolean mIsFourToThree;\n        private int mMode;\n        private boolean mIsVisible = true;\n        private float mAlpha = 1.0f;\n        private float mAutoAlphaX;\n        private float mAutoAlphaY;\n\n        public Snapshot() {\n            mIsFourToThree = true;\n            mMode = SNAPSHOT;\n        }\n\n        public Snapshot(boolean isFourToThree) {\n            mIsFourToThree = isFourToThree;\n            mMode = SNAPSHOT;\n        }\n\n        public void setVisible(boolean visible) {\n            mIsVisible = visible;\n        }\n\n        /**\n         * Sets whether to use the CAMERA shaders or the SNAPSHOT shaders\n         * @param mode CAMERA or SNAPSHOT\n         */\n        public void setMode(int mode) {\n            mMode = mode;\n        }\n\n        public void setTexture(Bitmap tex) {\n            mBitmapToLoad = tex;\n        }\n\n        public void setTextureId(int id) {\n            mTextureData = id;\n        }\n\n        public void setAlpha(float alpha) {\n            mAlpha = alpha;\n        }\n\n        public void setAutoAlphaAngle(float x, float y) {\n            mAutoAlphaX = x;\n            mAutoAlphaY = y;\n        }\n\n        public float getAutoAlphaX() {\n            return mAutoAlphaX;\n        }\n\n        public float getAutoAlphaY() {\n            return mAutoAlphaY;\n        }\n\n        private void loadTexture() {\n            // Load the snapshot bitmap as a texture to bind to our GLES20 program\n            int texture[] = new int[1];\n\n            GLES20.glGenTextures(1, texture, 0);\n            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);\n\n            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                    GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);\n            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                    GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);\n            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                    GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);\n            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,\n                    GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);\n\n            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmapToLoad, 0);\n            //tex.recycle();\n\n            if(texture[0] == 0){\n                Log.e(TAG, \"Unable to attribute texture to quad\");\n            }\n\n            mTextureData = texture[0];\n            mBitmapToLoad = null;\n        }\n\n        public void draw() {\n            if (!mIsVisible) return;\n\n            if (mBitmapToLoad != null) {\n                loadTexture();\n            }\n\n            GLES20.glUseProgram(mProgram[mMode]);\n            if (mIsFourToThree) {\n                m43VertexBuffer.position(0);\n            } else {\n                mVertexBuffer.position(0);\n            }\n            mTexCoordBuffer.position(0);\n\n            GLES20.glEnableVertexAttribArray(mTexCoordHandler[mMode]);\n            GLES20.glEnableVertexAttribArray(mPositionHandler[mMode]);\n\n            if (mIsFourToThree) {\n                GLES20.glVertexAttribPointer(mPositionHandler[mMode],\n                        2, GLES20.GL_FLOAT, false, 8, m43VertexBuffer);\n            } else {\n                GLES20.glVertexAttribPointer(mPositionHandler[mMode],\n                        2, GLES20.GL_FLOAT, false, 8, mVertexBuffer);\n            }\n            GLES20.glVertexAttribPointer(mTexCoordHandler[mMode], 2,\n                    GLES20.GL_FLOAT, false, 8, mTexCoordBuffer);\n\n            // This multiplies the view matrix by the model matrix, and stores the\n            // result in the MVP matrix (which currently contains model * view).\n            Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);\n\n            // This multiplies the modelview matrix by the projection matrix, and stores\n            // the result in the MVP matrix (which now contains model * view * projection).\n            Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);\n\n            // Pass in the combined matrix.\n            GLES20.glUniformMatrix4fv(mMVPMatrixHandler[mMode], 1, false, mMVPMatrix, 0);\n\n            GLES20.glUniform1f(mAlphaHandler[mMode], mAlpha);\n\n            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);\n            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureData);\n\n            GLES20.glUniform1i(mTextureHandler[mMode], 0);\n\n            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);\n        }\n    }\n\n    /**\n     * Initialize the model data.\n     */\n    public Capture3DRenderer(Context context, CameraManager cameraManager) {\n        mSnapshots = new ArrayList<Snapshot>();\n        mDots = new ArrayList<Snapshot>();\n        mCamManager = cameraManager;\n        mListBusy = new ReentrantLock();\n        mSensorFusion = new SensorFusion(context);\n        mCameraQuat = new Quaternion();\n        mContext = context;\n        mTempQuaternion = new Quaternion();\n\n        // Position the dots every 40°\n        for (int x = 0; x < 360; x += 360/12) {\n            for (int y = 0; y < 360; y += 360/12) {\n                createDot(x, y);\n            }\n        }\n\n    }\n\n    private void createDot(float rx, float ry) {\n        Snapshot dot = new Snapshot(false);\n        dot.setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                R.drawable.ic_picsphere_marker));\n        dot.mModelMatrix = matrixFromEuler(rx, 0, ry, 0, 0, 100);\n        Matrix.scaleM(dot.mModelMatrix, 0, 0.1f, 0.1f, 0.1f);\n        dot.setAutoAlphaAngle(rx, ry);\n        mDots.add(dot);\n    }\n\n    private float[] matrixFromEuler(float rx, float ry, float rz, float tx, float ty, float tz) {\n        Quaternion quat = new Quaternion();\n        quat.fromEuler(rx,ry,rz);\n        float[] matrix = quat.getMatrix();\n\n        Matrix.translateM(matrix, 0, tx, ty, tz);\n\n        return matrix;\n    }\n\n\n    @Override\n    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {\n        // Set the background clear color to gray.\n        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);\n\n        // Initialize plane vertex data and texcoords data\n        ByteBuffer bb_data = ByteBuffer.allocateDirect(mVertexData.length * 4);\n        bb_data.order(ByteOrder.nativeOrder());\n        ByteBuffer bb_43data = ByteBuffer.allocateDirect(m43VertexData.length * 4);\n        bb_43data.order(ByteOrder.nativeOrder());\n        ByteBuffer bb_texture = ByteBuffer.allocateDirect(mTexCoordData.length * 4);\n        bb_texture.order(ByteOrder.nativeOrder());\n\n        mVertexBuffer = bb_data.asFloatBuffer();\n        mVertexBuffer.put(mVertexData);\n        mVertexBuffer.position(0);\n        m43VertexBuffer = bb_43data.asFloatBuffer();\n        m43VertexBuffer.put(m43VertexData);\n        m43VertexBuffer.position(0);\n        mTexCoordBuffer = bb_texture.asFloatBuffer();\n        mTexCoordBuffer.put(mTexCoordData);\n        mTexCoordBuffer.position(0);\n\n        // Simple GLSL vertex/fragment, as GLES2 doesn't have the classical fixed pipeline\n        final String vertexShader =\n                \"uniform mat4 u_MVPMatrix; \\n\"\n                        + \"attribute vec4 a_Position;     \\n\"\n                        + \"attribute vec2 a_TexCoordinate;\\n\"\n                        + \"varying vec2 v_TexCoordinate;  \\n\"\n                        + \"void main()                    \\n\"\n                        + \"{                              \\n\"\n                        + \"   v_TexCoordinate = a_TexCoordinate;\\n\"\n                        + \"   gl_Position = u_MVPMatrix * a_Position;   \\n\"\n                        + \"}                              \\n\";\n\n        final String fragmentShader =\n                        \"precision mediump float;       \\n\"\n                        + \"uniform sampler2D u_Texture;   \\n\"\n                        + \"varying vec2 v_TexCoordinate;  \\n\"\n                        + \"uniform float f_Alpha;\\n\"\n                        + \"void main()                    \\n\"\n                        + \"{                              \\n\"\n                        + \"   gl_FragColor = texture2D(u_Texture, v_TexCoordinate);\\n\"\n                        + \"   gl_FragColor.a = gl_FragColor.a * f_Alpha;\"\n                        + \"}                              \\n\";\n\n        // As the camera preview is stored in the OES external slot, we need a different shader\n        final String camPreviewShader = \"#extension GL_OES_EGL_image_external : require\\n\"\n                + \"precision mediump float;       \\n\"\n                + \"uniform samplerExternalOES u_Texture;   \\n\"\n                + \"varying vec2 v_TexCoordinate;  \\n\"\n                + \"uniform float f_Alpha;\\n\"\n                + \"void main()                    \\n\"\n                + \"{                              \\n\"\n                + \"   gl_FragColor = texture2D(u_Texture, v_TexCoordinate);\\n\"\n                + \"   gl_FragColor.a = gl_FragColor.a * f_Alpha;\"\n                + \"}                              \\n\";\n\n\n        mVertexShader[CAMERA] = compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);\n        mFragmentShader[CAMERA] = compileShader(GLES20.GL_FRAGMENT_SHADER, camPreviewShader);\n\n        mVertexShader[SNAPSHOT] = compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);\n        mFragmentShader[SNAPSHOT] = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);\n\n        // create the program and bind the shader attributes\n        for (int i = 0; i < 2; i++) {\n            mProgram[i] = GLES20.glCreateProgram();\n            GLES20.glAttachShader(mProgram[i], mFragmentShader[i]);\n            GLES20.glAttachShader(mProgram[i], mVertexShader[i]);\n            GLES20.glLinkProgram(mProgram[i]);\n\n            int[] linkStatus = new int[1];\n            GLES20.glGetProgramiv(mProgram[i], GLES20.GL_LINK_STATUS, linkStatus, 0);\n\n            if (linkStatus[0] == 0) {\n                throw new RuntimeException(\"Error linking shaders\");\n            }\n            mPositionHandler[i]     = GLES20.glGetAttribLocation(mProgram[i], \"a_Position\");\n            mTexCoordHandler[i]     = GLES20.glGetAttribLocation(mProgram[i], \"a_TexCoordinate\");\n            mMVPMatrixHandler[i]    = GLES20.glGetUniformLocation(mProgram[i], \"u_MVPMatrix\");\n            mTextureHandler[i]      = GLES20.glGetUniformLocation(mProgram[i], \"u_Texture\");\n            mAlphaHandler[i]      = GLES20.glGetUniformLocation(mProgram[i], \"f_Alpha\");\n        }\n\n        mSkyBox = new Skybox();\n\n        initCameraBillboard();\n    }\n\n    private void initCameraBillboard() {\n        int texture[] = new int[1];\n\n        GLES20.glGenTextures(1, texture, 0);\n        mCameraTextureId = texture[0];\n\n        if (mCameraTextureId == 0) {\n            throw new RuntimeException(\"CAMERA TEXTURE ID == 0\");\n        }\n\n        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mCameraTextureId);\n        // Can't do mipmapping with camera source\n        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,\n                GLES20.GL_LINEAR);\n        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,\n                GLES20.GL_LINEAR);\n        // Clamp to edge is the only option\n        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,\n                GLES20.GL_CLAMP_TO_EDGE);\n        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,\n                GLES20.GL_CLAMP_TO_EDGE);\n\n        mCameraSurfaceTex = new SurfaceTexture(mCameraTextureId);\n        mCameraSurfaceTex.setDefaultBufferSize(640, 480);\n\n        mCameraBillboard = new Snapshot();\n        mCameraBillboard.setTextureId(mCameraTextureId);\n        mCameraBillboard.setMode(CAMERA);\n        mCamManager.setRenderToTexture(mCameraSurfaceTex);\n\n        // Setup viewfinder billboard\n        mViewfinderBillboard = new Snapshot(false);\n        mViewfinderBillboard.setTexture(BitmapFactory.decodeResource(mContext.getResources(),\n                R.drawable.ic_picsphere_viewfinder));\n    }\n\n    @Override\n    public void onSurfaceChanged(GL10 glUnused, int width, int height) {\n        // Set the OpenGL viewport to the same size as the surface.\n        GLES20.glViewport(0, 0, width, height);\n\n        // We use here a field of view of 40, which is mostly fine for a camera app representation\n        final float hfov = 90f;\n\n        // Create a new perspective projection matrix. The height will stay the same\n        // while the width will vary as per aspect ratio.\n        final float ratio = 640.0f / 480.0f;\n        final float near = 0.1f;\n        final float far = 1500.0f;\n        final float left = (float) Math.tan(hfov * Math.PI / 360.0f) * near;\n        final float right = -left;\n        final float bottom = ratio * right / 1.0f;\n        final float top = ratio * left / 1.0f;\n\n        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);\n    }\n\n    @Override\n    public void onDrawFrame(GL10 glUnused) {\n        mCameraSurfaceTex.updateTexImage();\n\n        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);\n\n        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);\n        GLES20.glEnable(GLES20.GL_BLEND);\n\n        // Update camera view matrix\n        float[] orientation = mSensorFusion.getFusedOrientation();\n\n        // Convert angles to degrees\n        float rX = (float) (orientation[1] * 180.0f/Math.PI);\n        float rY = (float) (orientation[0] * 180.0f/Math.PI);\n        float rZ = (float) (orientation[2] * 180.0f/Math.PI);\n\n        // Update quaternion from euler angles out of orientation\n        mCameraQuat.fromEuler( rX, 180.0f-rZ, rY);\n        mCameraQuat = mCameraQuat.getConjugate();\n        mCameraQuat.normalise();\n        mViewMatrix = mCameraQuat.getMatrix();\n\n        // Update camera billboard\n        mCameraBillboard.mModelMatrix = mCameraQuat.getMatrix();\n\n        Matrix.invertM(mCameraBillboard.mModelMatrix, 0, mCameraBillboard.mModelMatrix, 0);\n        Matrix.translateM(mCameraBillboard.mModelMatrix, 0, 0.0f, 0.0f, -DISTANCE);\n        Matrix.rotateM(mCameraBillboard.mModelMatrix, 0, -90, 0, 0, 1);\n\n        mViewfinderBillboard.mModelMatrix = Arrays.copyOf(mCameraBillboard.mModelMatrix,\n                mCameraBillboard.mModelMatrix.length);\n        Matrix.scaleM(mViewfinderBillboard.mModelMatrix, 0, 0.25f, 0.25f, 0.25f);\n\n        // Draw all teh things\n        // First the skybox, then the marker dots, then the snapshots\n        mSkyBox.draw();\n\n        mCameraBillboard.draw();\n\n        mListBusy.lock();\n        for (Snapshot snap : mSnapshots) {\n            snap.draw();\n        }\n        mListBusy.unlock();\n\n        for (Snapshot dot : mDots) {\n            // Set alpha based on camera distance to the point\n            float dX = dot.getAutoAlphaX() - (rX + 180.0f);\n            float dY = dot.getAutoAlphaY() - rY;\n            dX = (dX + 180.0f) % 360.0f - 180.0f;\n            dot.setAlpha(1.0f - Math.abs(dX)/180.0f * 8.0f);\n\n            dot.draw();\n        }\n\n        mViewfinderBillboard.draw();\n    }\n\n    public void setCamPreviewVisible(boolean visible) {\n        mCameraBillboard.setVisible(visible);\n    }\n\n    public void onPause() {\n        if (mSensorFusion != null) {\n            mSensorFusion.onPauseOrStop();\n        }\n    }\n\n    public void onResume() {\n        if (mSensorFusion != null) {\n            mSensorFusion.onResume();\n        }\n    }\n\n    public void setCameraOrientation(float rX, float rY, float rZ) {\n        // Convert angles to degrees\n        rX = (float) (rX * 180.0f/Math.PI);\n        rY = (float) (rY * 180.0f/Math.PI);\n\n        // Update quaternion from euler angles out of orientation and set it as view matrix\n        mCameraQuat.fromEuler(rY, 0.0f, rX);\n        mViewMatrix = mCameraQuat.getConjugate().getMatrix();\n    }\n\n    public Vector3 getAngleAsVector() {\n        float[] orientation = mSensorFusion.getFusedOrientation();\n\n        // Convert angles to degrees\n        float rX = (float) (orientation[0] * 180.0f/Math.PI);\n        float rY = (float) (orientation[1] * 180.0f/Math.PI);\n        float rZ = (float) (orientation[2] * 180.0f/Math.PI);\n\n        return new Vector3(rX, rY, rZ);\n    }\n\n    /**\n     * Helper function to compile a shader.\n     *\n     * @param shaderType The shader type.\n     * @param shaderSource The shader source code.\n     * @return An OpenGL handle to the shader.\n     */\n    public static int compileShader(final int shaderType, final String shaderSource) {\n        int shaderHandle = GLES20.glCreateShader(shaderType);\n\n        if (shaderHandle != 0) {\n            // Pass in the shader source.\n            GLES20.glShaderSource(shaderHandle, shaderSource);\n\n            // Compile the shader.\n            GLES20.glCompileShader(shaderHandle);\n\n            // Get the compilation status.\n            final int[] compileStatus = new int[1];\n            GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);\n\n            // If the compilation failed, delete the shader.\n            if (compileStatus[0] == 0) {\n                Log.e(TAG, \"Error compiling shader: \" + GLES20.glGetShaderInfoLog(shaderHandle));\n                GLES20.glDeleteShader(shaderHandle);\n                shaderHandle = 0;\n            }\n        }\n\n        if (shaderHandle == 0) {\n            throw new RuntimeException(\"Error creating shader.\");\n        }\n\n        return shaderHandle;\n    }\n\n    /**\n     * Adds a snapshot to the sphere\n     */\n    public void addSnapshot(final Bitmap image) {\n        Snapshot snap = new Snapshot();\n        snap.mModelMatrix = Arrays.copyOf(mViewMatrix, mViewMatrix.length);\n\n        Matrix.invertM(snap.mModelMatrix, 0, snap.mModelMatrix, 0);\n        Matrix.translateM(snap.mModelMatrix, 0, 0.0f, 0.0f, -DISTANCE);\n        Matrix.rotateM(snap.mModelMatrix, 0, -90, 0, 0, 1);\n\n        snap.setTexture(image);\n\n        mListBusy.lock();\n        mSnapshots.add(snap);\n        mListBusy.unlock();\n    }\n\n    /**\n     * Removes the last taken snapshot\n     */\n    public void removeLastPicture() {\n        mListBusy.lock();\n        if (mSnapshots.size() > 0) {\n            mSnapshots.remove(mSnapshots.size()-1);\n        }\n        mListBusy.unlock();\n    }\n\n    /**\n     * Clear sphere's snapshots\n     */\n    public void clearSnapshots() {\n        mListBusy.lock();\n        mSnapshots.clear();\n        mListBusy.unlock();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/PicSphere.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.provider.MediaStore;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.PopenHelper;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.XMPHelper;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This class handles the data needed for a PicSphere to happen, as well as the current\n * status of the processing.\n *\n * You shouldn't instantiate the awesomeness directly, but rather go through PicSphereManager.\n */\npublic class PicSphere {\n    public final static String TAG = \"PicSphere\";\n\n    private String mPathPrefix;\n    private List<PicSphereImage> mPictures;\n    private Context mContext;\n    private File mTempPath;\n    private String mProjectFile;\n    private SnapshotManager mSnapManager;\n    private Uri mOutputUri;\n    private String mOutputTitle;\n    private List<ProgressListener> mProgressListeners;\n    private int mRenderProgress = 0;\n    private int mOrientation;\n    private float mHorizontalAngle;\n\n    public final static int STEP_PTOGEN = 1;\n    public final static int STEP_PTOVAR = 2;\n    public final static int STEP_CPFIND = 3;\n    public final static int STEP_PTCLEAN = 4;\n    public final static int STEP_AUTOOPTIMISER = 5;\n    public final static int STEP_PANOMODIFY = 6;\n    public final static int STEP_NONA = 7;\n    public final static int STEP_ENBLEND = 8;\n    public final static int STEP_TOTAL = 9;\n\n    private final static int EXIT_SUCCESS = 0;\n\n    private final static String PANO_PROJECT_FILE = \"project.pto\";\n    private final static int PANO_PROJECTION_MODE = 2; // 2=equirectangular, 0=rectilinear\n    private final static int PANO_THREADS = 4;\n    private final static int PANO_CPFIND_SIEVE1SIZE = 10;\n    private final static int PANO_CPFIND_SIEVE2WIDTH = 5;\n    private final static int PANO_CPFIND_SIEVE2HEIGHT = 5;\n    private final static int PANO_CPFIND_MINMATCHES = 3;\n    private final static int PANO_BLEND_COMPRESSION = 90;\n\n\n\n    public interface ProgressListener {\n        public void onRenderStart(PicSphere sphere);\n        public void onStepChange(PicSphere sphere, int newStep);\n        public void onRenderDone(PicSphere sphere);\n    }\n\n    private class PicSphereImage {\n        private Uri mRealPathUri;\n        private Uri mContentUri;\n        private Vector3 mAngle;\n\n        public PicSphereImage(Uri realPath, Uri contentUri, Vector3 angle) {\n            mRealPathUri = realPath;\n            mContentUri = contentUri;\n            mAngle = angle;\n        }\n\n        public Uri getRealPath() {\n            return mRealPathUri;\n        }\n\n        public Uri getUri() {\n            return mContentUri;\n        }\n\n        public Vector3 getAngle() {\n            return mAngle;\n        }\n    }\n\n\n    protected PicSphere(Context context, SnapshotManager snapMan) {\n        mPictures = new ArrayList<PicSphereImage>();\n        mProgressListeners = new ArrayList<ProgressListener>();\n        mContext = context;\n        mSnapManager = snapMan;\n    }\n\n    public void addProgressListener(ProgressListener listener) {\n        mProgressListeners.add(listener);\n    }\n\n    /**\n     * Adds a picture to the sphere\n     * @param pic The URI of the picture\n     */\n    public void addPicture(Uri pic, Vector3 angle) {\n        PicSphereImage image = new PicSphereImage(\n                Uri.fromFile(new File(Util.getRealPathFromURI(mContext, pic))),\n                pic,\n                angle);\n\n        mPictures.add(image);\n    }\n\n    /**\n     * Removes the last picture of the sphere\n     */\n    public void removeLastPicture() {\n        if (mPictures.size() > 0) {\n            mPictures.remove(mPictures.size()-1);\n        }\n    }\n\n    /**\n     * @return The number of pictures in the sphere\n     */\n    public int getPicturesCount() {\n        return mPictures.size();\n    }\n\n    /**\n     * Sets the horizontal angle of the camera. AOSP don't check values are real values, and\n     * OEMs don't seem to care much about returning a real value (sony reports 360°...), so we\n     * pass 45° by default.\n     *\n     * @param angle The horizontal angle, in degrees\n     */\n    public void setHorizontalAngle(float angle) {\n        mHorizontalAngle = angle;\n    }\n\n    /**\n     * @return The current rendering progress, or -1 if it's not rendering or done.\n     */\n    public int getRenderProgress() {\n        return mRenderProgress;\n    }\n\n    /**\n     * Renders the sphere\n     */\n    protected boolean render(int orientation) {\n        mOrientation = (orientation + 360) % 360;\n\n        for (ProgressListener listener : mProgressListeners) {\n            listener.onRenderStart(this);\n        }\n\n        // Prepare a temporary directory\n        File appFilesDir = mContext.getFilesDir();\n        mPathPrefix = appFilesDir.getAbsolutePath() + \"/\";\n        String tempPathStr = appFilesDir.getAbsolutePath() + \"/\" + System.currentTimeMillis();\n        mTempPath = new File(tempPathStr);\n        if (!mTempPath.mkdir()) {\n            Log.e(TAG, \"Couldn't create temporary path\");\n            return false;\n        }\n\n        mProjectFile = mTempPath + \"/\" + PANO_PROJECT_FILE;\n\n        // Wait till all images are saved and accessible\n        boolean allSaved = false;\n        while (!allSaved) {\n            allSaved = true;\n            for (PicSphereImage pic : mPictures) {\n                File file = new File(pic.getRealPath().getPath());\n                if (!file.exists() || !file.canRead()) {\n                    allSaved = false;\n\n                    // Wait a bit until we retry\n                    try {\n                        Thread.sleep(50);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                    break;\n                }\n            }\n        }\n\n        mRenderProgress = 1;\n\n        // Process our images\n        try {\n            doPtoGen();\n            doPtoVar();\n            doCpFind();\n            doPtclean();\n            doAutoOptimiser();\n            doPanoModify();\n            doNona();\n            doEnblend();\n        } catch (Exception ex) {\n            Log.e(TAG, \"Unable to process: \", ex);\n            for (ProgressListener listener : mProgressListeners) {\n                listener.onRenderDone(this);\n            }\n            removeTempFiles();\n            return false;\n        }\n\n        for (ProgressListener listener : mProgressListeners) {\n            listener.onRenderDone(this);\n        }\n\n        removeTempFiles();\n\n        mRenderProgress = -1;\n        return true;\n    }\n\n    /**\n     * Remove the temporary files used by the app, and the source pictures of the sphere\n     */\n    private void removeTempFiles() {\n        // Remove source pictures and temporary path\n        for (PicSphereImage uri : mPictures) {\n            List<String> segments = uri.getUri().getPathSegments();\n\n            if (segments != null && segments.size() > 0) {\n                Util.removeFromGallery(mContext.getContentResolver(),\n                        Integer.parseInt(segments.get(segments.size()-1)));\n            }\n        }\n\n        run(\"rm -r \" + mTempPath.getPath());\n    }\n\n    /**\n     * Run a command on the system shell\n     * @param command The command to run\n     * @throws IOException\n     */\n    private boolean run(String command) {\n        // HACK: We cd to /storage/emulated/0/DCIM/Camera/ because cpfind tries to\n        // find it in the current path instead of the actual path provided in the .pto. I'm\n        // leaving this here as a hack until I fix it properly in cpfind source\n        String dcimPath = \"/sdcard/DCIM/Camera\";\n        String fullCommand = String.format(\"PATH=%s; LD_LIBRARY_PATH=%s; cd %s; %s 2>&1\",\n                mPathPrefix+\":/system/bin\",\n                mPathPrefix+\":\"+mPathPrefix+\"/../lib/:/system/lib\",\n                dcimPath,\n                command);\n\n        Log.v(TAG, \"Running: \" + fullCommand);\n\n        if (PopenHelper.run(fullCommand) != EXIT_SUCCESS) {\n            Log.e(TAG, \"Command return code is not EXIT_SUCCESS!\");\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Notify the ProgressListeners of a step change\n     * @param step The new step\n     */\n    private void notifyStep(int step) {\n        int progressPerStep = 100 / PicSphere.STEP_TOTAL;\n        mRenderProgress = step * progressPerStep;\n\n        for (ProgressListener listener : mProgressListeners) {\n            listener.onStepChange(this, step);\n        }\n    }\n\n    /**\n     * Most of hugin tools take a pto file as input and output. So the first step is to create this\n     * pto file. For this purpose use pto_gen.\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private void doPtoGen() throws IOException {\n        Log.d(TAG, \"PtoGen...\");\n        notifyStep(STEP_PTOGEN);\n\n        // Build a list of files\n        String filesStr = \"\";\n        for (PicSphereImage picture : mPictures) {\n            filesStr += \" \" + picture.getRealPath().getPath();\n        }\n\n        if (!run(String.format(\"pto_gen -f %f -p %d -o %s %s\",\n                mHorizontalAngle, PANO_PROJECTION_MODE,\n                mProjectFile, filesStr))) {\n            throw new IOException(\"PtoGen failed\");\n        }\n\n        Log.d(TAG, \"PtoGen... done\");\n    }\n\n    /**\n     * Since we know the angle of each image thanks to the gyroscope, we're going to set them\n     * in our pto file before going on to matching the pictures edges.\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private void doPtoVar() throws IOException {\n        Log.d(TAG, \"PtoVar...\");\n        notifyStep(STEP_PTOVAR);\n\n        int index = 0;\n        boolean referenceYawSet = false;\n        float referenceYaw = 0.0f;\n\n        for (PicSphereImage picture : mPictures) {\n            // We stored camera angle into x, y and z respectively as degrees\n            Vector3 a = picture.getAngle();\n\n            // We now convert them to fit pitch, yaw and roll values as expected by Hugin\n            // TODO: Is it orientation independent? Portrait/landscape?\n\n            // Pitch: If you shoot a multi row panorama you will have to tilt the camera up or down.\n            // The angle the optical axis of the camera is tilted up (positive value) or down\n            // (negative value) is the pitch of the image. The pitch of the zenith image (shot\n            // straight upwards) is 90° f.e.\n            float pitch = -a.y;\n\n            // Yaw: If shooting a panorama you rotate the camera horizontally (around a vertical\n            // axis, TrY). You will treat one image as an anchor image which is considered to have\n            // yaw = 0. The angle you rotated your camera relatively to this image is its Yaw value.\n            float yaw = a.x + 180.0f;\n            if (!referenceYawSet) {\n                referenceYaw = yaw;\n                referenceYawSet = true;\n                yaw = 0.0f;\n            } else {\n                yaw = (yaw - referenceYaw) + ((yaw - referenceYaw < 0) ? 360 : 0);\n            }\n\n            // Roll: Roll is the angle of camera rotation around its optical axis. In normal\n            // landscape orientation, the sensor returns a pitch or -180/180 degrees. Hugin expects\n            // that angle to be zero, so we clamp it\n            float roll = (a.z + 180.0f) % 360.0f;\n\n            Log.e(TAG, \"Pitch/Yaw/Roll: \" + pitch + \" ; \" + yaw + \" ; \" + roll);\n            if (!run(String.format(\"pto_var -o %s --set p%d=%f,y%d=%f,r%d=%f %s\",\n                    mProjectFile,\n                    index,\n                    pitch,\n                    index,\n                    yaw,\n                    index,\n                    roll,\n                    mProjectFile))) {\n                throw new IOException(\"PtoVar failed\");\n            }\n\n            index++;\n        }\n    }\n\n    /**\n     * We then look for control points and matching pairs in the images, using CpFind. Note that we\n     * can enable celeste cleaning in cpfind, but we need the celeste reference database. Also, it's\n     * probably a bit slower and not worth it for what we're doing.\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private boolean doCpFind() throws IOException {\n        Log.d(TAG, \"CpFind...\");\n        notifyStep(STEP_CPFIND);\n\n        if (!run(String.format(\"cpfind --sieve1size %d --sieve2width %d --sieve2height %d -n %d -v \" +\n                \"--prealigned --minmatches %d -o \" +\n                \"%s %s\",\n                PANO_CPFIND_SIEVE1SIZE, PANO_CPFIND_SIEVE2WIDTH, PANO_CPFIND_SIEVE2HEIGHT,\n                PANO_THREADS, PANO_CPFIND_MINMATCHES, mProjectFile,  mProjectFile))) {\n            throw new IOException(\"CpFind failed\");\n        }\n\n        Log.d(TAG, \"CpFind... done\");\n        return true;\n    }\n\n    /**\n     * You could go ahead and optimise this project file straight away, but this can be a bit hit\n     * and miss. First it is a good idea to clean up the control points.\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private boolean doPtclean() throws IOException {\n        Log.d(TAG, \"Ptclean...\");\n        notifyStep(STEP_PTCLEAN);\n        if (!run(String.format(\"ptclean -o %s %s\", mProjectFile, mProjectFile))) {\n            throw new IOException(\"PtClean failed\");\n        }\n\n        Log.d(TAG, \"Ptclean... done\");\n        return true;\n    }\n\n    /**\n     * Up to now, the project file simply contains an image list and control points, the images are\n     * not yet aligned, you can do this by optimising geometric parameters with the autooptimiser\n     * tool\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private boolean doAutoOptimiser() throws IOException {\n        Log.d(TAG, \"AutoOptimiser...\");\n        notifyStep(STEP_AUTOOPTIMISER);\n        if (!run(String.format(\"autooptimiser -v %f -a -s -l -m -o %s %s\", mHorizontalAngle,\n                mProjectFile, mProjectFile))) {\n            throw new IOException(\"AutoOptimiser failed\");\n        }\n\n        Log.d(TAG, \"AutoOptimiser... done\");\n        return true;\n    }\n\n    /**\n     * Crop and straighten the panorama to fit a rectangular image\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private boolean doPanoModify() throws IOException {\n        Log.d(TAG, \"PanoModify...\");\n        notifyStep(STEP_PANOMODIFY);\n\n        // Google PhotoSphere (both Google Gallery viewer and Google+ viewer, even if the last one\n        // is a bit more permissive) must have a 2:1 image to work properly. For now, we left this\n        // on AUTO to have a clean final image. They seem to view fine on the Google+ Web viewer,\n        // and we'll have to come up with our own Android viewer anyway at some point.\n        String canvas = \"AUTO\";\n        String crop = \"AUTO\";\n        if (!run(String.format(\"pano_modify --crop=%s --canvas=%s -o %s %s\",\n                crop, canvas, mProjectFile, mProjectFile))) {\n            throw new IOException(\"Pano_Modify failed\");\n        }\n\n        Log.d(TAG, \"PanoModify... done\");\n        return true;\n    }\n\n    /**\n     * The hugin tool for remapping and distorting the photos into the final panorama frame is nona,\n     * it uses the .pto project file as a set of instructions\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private boolean doNona() throws IOException {\n        Log.d(TAG, \"Nona...\");\n        notifyStep(STEP_NONA);\n        if (!run(String.format(\"nona -t %d -o %s/project -m TIFF_m %s\",\n                PANO_THREADS, mTempPath, mProjectFile))) {\n            throw new IOException(\"Nona failed\");\n        }\n\n        Log.d(TAG, \"Nona... done\");\n        return true;\n    }\n\n    /**\n     * nona can do rudimentary assembly of the remapped images, but a much better tool for this\n     * is enblend, feed it the images, it will pick seam lines and blend the overlapping areas.\n     * We use its multithreaded friend, multiblend, which is much faster than enblend for the\n     * same final quality.\n     *\n     * @return Success status\n     * @throws IOException\n     */\n    private void doEnblend() throws IOException {\n        Log.d(TAG, \"Enblend...\");\n        notifyStep(STEP_ENBLEND);\n\n        String jpegPath = mTempPath + \"/final.jpg\";\n\n        // Multiblend can take a lot of RAM when blending a lot of high resolution pictures. We\n        // prepare the system for that.\n        System.gc();\n\n        // As we are running through a sh shell, we can use * instead of writing the full\n        // images list.\n        run(String.format(\"multiblend --wideblend --compression=%d -o %s %s/project*.tif\",\n                PANO_BLEND_COMPRESSION, jpegPath, mTempPath));\n\n        // Apply PhotoSphere EXIF tags on the final jpeg\n        BitmapFactory.Options opts = new BitmapFactory.Options();\n        opts.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(mTempPath + \"/final.jpg\", opts);\n\n        if (!doExifTagging(opts.outWidth, opts.outHeight)) {\n            throw new IOException(\"Cannot tag EXIF info\");\n        }\n\n        // Save it to gallery\n        // XXX: This needs opening the output byte array... Isn't there any way to update\n        // gallery data without having to reload/save the JPEG file? Because we have it already,\n        // we could just move it.\n        byte[] jpegData;\n        RandomAccessFile f = null;\n        try {\n            f = new RandomAccessFile(jpegPath, \"r\");\n\n            // Get and check length\n            long longlength = f.length();\n            int length = (int) longlength;\n            if (length != longlength)\n                throw new IOException(\"File size >= 2 GB\");\n            // Read file and return data\n            jpegData = new byte[length];\n            f.readFully(jpegData);\n        } catch (Exception e) {\n            Log.e(TAG, \"Couldn't access final file, did rendering fail?\");\n            throw new IOException(\"Cannot access final file\");\n        } finally {\n            if (f != null) {\n                f.close();\n            }\n        }\n\n        if (opts.outWidth > 0 && opts.outHeight > 0) {\n            mSnapManager.prepareNamerUri(opts.outWidth, opts.outHeight);\n            mOutputUri = mSnapManager.getNamerUri();\n            mOutputTitle = mSnapManager.getNamerTitle();\n\n            Log.i(TAG, \"PicSphere size: \" + opts.outWidth + \"x\" + opts.outHeight);\n            mSnapManager.saveImage(mOutputUri, mOutputTitle, opts.outWidth, opts.outWidth, 0, jpegData);\n        } else {\n            Log.e(TAG, \"Invalid output image size: \" + opts.outWidth + \"x\" + opts.outHeight);\n            throw new IOException(\"Invalid output image size: \" + opts.outWidth + \"x\" + opts.outHeight);\n        }\n\n        Log.d(TAG, \"Enblend... done\");\n    }\n\n    private boolean doExifTagging(int width, int height) throws IOException {\n        Log.d(TAG, \"XMP metadata tagging...\");\n\n        try {\n            XMPHelper xmp = new XMPHelper();\n            xmp.writeXmpToFile(mTempPath + \"/final.jpg\", generatePhotoSphereXMP(width,\n                    height, mPictures.size()));\n        } catch (Exception e) {\n            Log.e(TAG, \"Couldn't access final file, did rendering fail?\");\n            return false;\n        }\n\n        Log.d(TAG, \"XMP metadata tagging... done\");\n        return true;\n    }\n\n    /**\n     * Generate PhotoSphere XMP data file to apply on panorama images\n     * https://developers.google.com/photo-sphere/metadata/\n     *\n     * @param width The width of the panorama\n     * @param height The height of the panorama\n     * @return RDF XML XMP metadata\n     */\n    public String generatePhotoSphereXMP(int width, int height, int sourceImageCount) {\n        return \"<rdf:RDF xmlns:rdf=\\\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\\\">\\n\" +\n                \"<rdf:Description rdf:about=\\\"\\\" xmlns:GPano=\\\"http://ns.google.com/photos/1.0/panorama/\\\">\\n\" +\n                \"    <GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>\\n\" +\n                \"    <GPano:CaptureSoftware>CyanogenMod Focal</GPano:CaptureSoftware>\\n\" +\n                \"    <GPano:StitchingSoftware>CyanogenMod Focal with Hugin</GPano:StitchingSoftware>\\n\" +\n                \"    <GPano:ProjectionType>equirectangular</GPano:ProjectionType>\\n\" +\n                \"    <GPano:PoseHeadingDegrees>350.0</GPano:PoseHeadingDegrees>\\n\" +\n                \"    <GPano:InitialViewHeadingDegrees>90.0</GPano:InitialViewHeadingDegrees>\\n\" +\n                \"    <GPano:InitialViewPitchDegrees>45.0</GPano:InitialViewPitchDegrees>\\n\" +\n                \"    <GPano:InitialViewRollDegrees>\"+mOrientation+\"</GPano:InitialViewRollDegrees>\\n\" +\n                \"    <GPano:InitialHorizontalFOVDegrees>\"+mHorizontalAngle+\"</GPano:InitialHorizontalFOVDegrees>\\n\" +\n                \"    <GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels>\\n\" +\n                \"    <GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels>\\n\" +\n                \"    <GPano:CroppedAreaImageWidthPixels>\"+(width)+\"</GPano:CroppedAreaImageWidthPixels>\\n\" +\n                \"    <GPano:CroppedAreaImageHeightPixels>\"+(height)+\"</GPano:CroppedAreaImageHeightPixels>\\n\" +\n                \"    <GPano:FullPanoWidthPixels>\"+width+\"</GPano:FullPanoWidthPixels>\\n\" +\n                \"    <GPano:FullPanoHeightPixels>\"+height+\"</GPano:FullPanoHeightPixels>\\n\" +\n                //\"    <GPano:FirstPhotoDate>2012-11-07T21:03:13.465Z</GPano:FirstPhotoDate>\\n\" +\n                //\"    <GPano:LastPhotoDate>2012-11-07T21:04:10.897Z</GPano:LastPhotoDate>\\n\" +\n                \"    <GPano:SourcePhotosCount>\"+sourceImageCount+\"</GPano:SourcePhotosCount>\\n\" +\n                \"    <GPano:ExposureLockUsed>False</GPano:ExposureLockUsed>\\n\" +\n                \"</rdf:Description></rdf:RDF>\";\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/PicSphereCaptureTransformer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.hardware.Camera;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SnapshotManager;\nimport org.cyanogenmod.focal.feats.CaptureTransformer;\nimport org.cyanogenmod.focal.ui.ShutterButton;\n\n/**\n * Capture Transformer for PicSphere that will store all shots to feed them to a new PicSphere\n * created by PicSphereManager\n */\npublic class PicSphereCaptureTransformer extends CaptureTransformer {\n    public final static String TAG = \"PicSphereCaptureTransformer\";\n    private PicSphereManager mPicSphereManager;\n    private PicSphere mPicSphere;\n    private CameraActivity mContext;\n    private Vector3 mLastShotAngle;\n\n    public PicSphereCaptureTransformer(CameraActivity context) {\n        super(context.getCamManager(), context.getSnapManager());\n        mContext = context;\n        mPicSphereManager = context.getPicSphereManager();\n    }\n\n    public void removeLastPicture() {\n        if (mPicSphere == null) {\n            return;\n        }\n\n        mPicSphere.removeLastPicture();\n        mPicSphereManager.getRenderer().removeLastPicture();\n\n        if (mPicSphere.getPicturesCount() == 0) {\n            mContext.setPicSphereUndoVisible(false);\n            mPicSphereManager.getRenderer().setCamPreviewVisible(true);\n        }\n    }\n\n    @Override\n    public void onShutterButtonClicked(ShutterButton button) {\n        if (mPicSphere == null) {\n            // Initialize a new sphere\n            mPicSphere = mPicSphereManager.createPicSphere();\n            Camera.Parameters params = mCamManager.getParameters();\n            float horizontalAngle = 0;\n\n            if (params != null) {\n                horizontalAngle = mCamManager.getParameters().getHorizontalViewAngle();\n            }\n\n            // In theory, drivers should return a proper value for horizontal angle. However,\n            // some careless OEMs put \"0\" or \"360\" to pass CTS, so we just check if the value\n            // seems legit, otherwise we put 45° as it's the angle of most phone lenses.\n            if (horizontalAngle < 30 || horizontalAngle > 70) {\n                horizontalAngle = 45;\n            }\n\n            mPicSphere.setHorizontalAngle(horizontalAngle);\n        }\n\n        mSnapManager.setBypassProcessing(true);\n        mSnapManager.queueSnapshot(true, 0);\n        mPicSphereManager.getRenderer().setCamPreviewVisible(false);\n\n        // Notify how to finish a sphere\n        if (mPicSphere != null && mPicSphere.getPicturesCount() == 0) {\n            CameraActivity.notify(mContext.getString(R.string.ps_long_press_to_stop), 4000);\n        }\n    }\n\n    @Override\n    public void onShutterButtonLongPressed(ShutterButton button) {\n        if (mPicSphere != null) {\n            if (mPicSphere.getPicturesCount() <= 1) {\n                CameraActivity.notify(mCamManager.getContext()\n                        .getString(R.string.picsphere_need_two_pics), 2000);\n                return;\n            }\n            mPicSphereManager.startRendering(mPicSphere, mContext.getOrientation());\n            mPicSphereManager.getRenderer().clearSnapshots();\n            mPicSphere = null;\n\n            mContext.setPicSphereUndoVisible(false);\n        }\n    }\n\n    @Override\n    public void onSnapshotShutter(SnapshotManager.SnapshotInfo info) {\n        mPicSphereManager.getRenderer().addSnapshot(info.mThumbnail);\n        mLastShotAngle = mPicSphereManager.getRenderer().getAngleAsVector();\n    }\n\n    @Override\n    public void onSnapshotPreview(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotProcessing(SnapshotManager.SnapshotInfo info) {\n\n    }\n\n    @Override\n    public void onSnapshotSaved(SnapshotManager.SnapshotInfo info) {\n        if (mPicSphere != null) {\n            mPicSphere.addPicture(info.mUri, mLastShotAngle);\n            mContext.setPicSphereUndoVisible(true);\n            mContext.setHelperText(\"\");\n        } else {\n            Log.e(TAG, \"No current PicSphere\");\n        }\n    }\n\n    @Override\n    public void onMediaSavingStart() {\n\n    }\n\n    @Override\n    public void onMediaSavingDone() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStart() {\n\n    }\n\n    @Override\n    public void onVideoRecordingStop() {\n\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/PicSphereManager.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.content.res.AssetManager;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.util.Log;\nimport android.view.TextureView;\nimport android.widget.FrameLayout;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SnapshotManager;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This class manages the interaction and processing of Picture Spheres (\"PicSphere\"). PicSphere\n * is basically an open-source clone of Google's PhotoSphere, using Hugin open-source panorama\n * stitching libraries.\n *\n * Can you feel the awesomeness?\n */\npublic class PicSphereManager implements PicSphere.ProgressListener {\n    public final static String TAG = \"PicSphereManager\";\n    private List<PicSphere> mPicSpheres;\n    private CameraActivity mContext;\n    private SnapshotManager mSnapManager;\n    private Capture3DRenderer mCapture3DRenderer;\n    private PicSphereRenderingService mBoundService;\n    private FrameLayout mGLRootView;\n    private TextureView mGLSurfaceView;\n    private Handler mHandler;\n    private boolean mIsBound;\n\n    private ServiceConnection mServiceConnection = new ServiceConnection() {\n        public void onServiceConnected(ComponentName className, IBinder service) {\n            // This is called when the connection with the service has been\n            // established, giving us the service object we can use to\n            // interact with the service.  Because we have bound to a explicit\n            // service that we know is running in our own process, we can\n            // cast its IBinder to a concrete class and directly access it.\n            mBoundService = ((PicSphereRenderingService.LocalBinder)service).getService();\n        }\n\n        public void onServiceDisconnected(ComponentName className) {\n            // This is called when the connection with the service has been\n            // unexpectedly disconnected -- that is, its process crashed.\n            // Because it is running in our same process, we should never\n            // see this happen.\n            mBoundService = null;\n        }\n    };\n\n    public PicSphereManager(CameraActivity context, SnapshotManager snapMan) {\n        mContext = context;\n        mSnapManager = snapMan;\n        mPicSpheres = new ArrayList<PicSphere>();\n        mHandler = new Handler();\n        mIsBound = false;\n        doBindService();\n        new Thread() {\n            public void run() { copyBinaries(); }\n        }.start();\n    }\n\n    /**\n     * Creates an empty PicSphere\n     * @return A PicSphere that is empty\n     */\n    public PicSphere createPicSphere() {\n        PicSphere sphere = new PicSphere(mContext, mSnapManager);\n        mPicSpheres.add(sphere);\n        return sphere;\n    }\n\n    public void clearSpheres() {\n        mPicSpheres.clear();\n    }\n\n    /**\n     * Returns the 3D renderer context for PicSphere capture mode\n     */\n    public Capture3DRenderer getRenderer() {\n        if (mCapture3DRenderer == null) {\n            // Initialize the 3D renderer\n            mCapture3DRenderer = new Capture3DRenderer(mContext, mContext.getCamManager());\n\n            // We route the camera preview to our texture\n            mGLRootView = (FrameLayout) mContext.findViewById(R.id.gl_renderer_container);\n        }\n\n        return mCapture3DRenderer;\n    }\n\n    /**\n     * Returns the progress of the currently rendering picsphere\n     * @return The percentage of progress, or -1 if no picsphere is rendering\n     */\n    public int getRenderingProgress() {\n        if (mPicSpheres.size() > 0) {\n            return mPicSpheres.get(0).getRenderProgress();\n        }\n\n        return -1;\n    }\n\n    /**\n     * Starts rendering the provided PicSphere in a thread, and handles a service\n     * that will keep on processing even once Nemesis is closed\n     *\n     * @param sphere The PicSphere to render\n     */\n    public void startRendering(final PicSphere sphere, final int orientation) {\n        // Notify toast\n        CameraActivity.notify(mContext.getString(R.string.picsphere_toast_background_render), 2500);\n\n        if (mIsBound && mBoundService != null) {\n            sphere.addProgressListener(this);\n            mBoundService.render(sphere, orientation);\n        } else {\n            doBindService();\n            mHandler.postDelayed(new Runnable() {\n                public void run() {\n                    startRendering(sphere, orientation);\n                }\n            }, 500);\n        }\n    }\n\n    void doBindService() {\n        // Establish a connection with the service.  We use an explicit\n        // class name because we want a specific service implementation that\n        // we know will be running in our own process (and thus won't be\n        // supporting component replacement by other applications).\n        Log.v(TAG, \"Binding PicSphere rendering service\");\n        mContext.bindService(new Intent(mContext, PicSphereRenderingService.class),\n                mServiceConnection, Context.BIND_AUTO_CREATE);\n        mIsBound = true;\n    }\n\n    public void tearDown() {\n        if (mCapture3DRenderer != null) {\n            mCapture3DRenderer.onPause();\n            mCapture3DRenderer = null;\n        }\n\n        mPicSpheres.clear();\n\n        if (mIsBound) {\n            // Detach our existing connection.\n            mContext.unbindService(mServiceConnection);\n            mIsBound = false;\n        }\n    }\n\n    public void onPause() {\n        if (mCapture3DRenderer != null) {\n            mCapture3DRenderer.onPause();\n        }\n\n        tearDown();\n\n        mPicSpheres.clear();\n    }\n\n    public void onResume() {\n        if (mCapture3DRenderer != null) {\n            mCapture3DRenderer.onResume();\n        }\n    }\n\n    public int getSpheresCount() {\n        return mPicSpheres.size();\n    }\n\n    /**\n     * Copy the required binaries and libraries to the data folder\n     * @return\n     */\n    private boolean copyBinaries() {\n        boolean result = true;\n        try {\n            String files[] = {\n                    \"autooptimiser\", \"pto_gen\", \"cpfind\", \"multiblend\", \"enfuse\", \"nona\", \"pano_modify\",\n                    \"ptclean\", \"tiffinfo\", \"align_image_stack\", \"pto_var\",\n                    \"libexiv2.so\", \"libglib-2.0.so\", \"libgmodule-2.0.so\", \"libgobject-2.0.so\",\n                    \"libgthread-2.0.so\", \"libjpeg.so\", \"libpano13.so\", \"libtiff.so\", \"libtiffdecoder.so\",\n                    \"libvigraimpex.so\"\n            };\n\n            final AssetManager am = mContext.getAssets();\n\n            for (String file : files) {\n                InputStream is = am.open(\"picsphere/\" + file);\n\n                // Create the output file\n                File dir = mContext.getFilesDir();\n                File outFile = new File(dir, file);\n\n                if (outFile.exists())\n                    outFile.delete();\n\n                OutputStream os = new FileOutputStream(outFile);\n\n                // Copy the file\n                byte[] buffer = new byte[1024];\n                int read;\n                while ((read = is.read(buffer)) != -1) {\n                    os.write(buffer, 0, read);\n                }\n\n                if (!outFile.getName().endsWith(\".so\") && !outFile.setExecutable(true)) {\n                    result = false;\n                }\n\n                os.flush();\n                os.close();\n                is.close();\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Error copying libraries and binaries\", e);\n            result = false;\n        }\n        return result;\n    }\n\n\n    @Override\n    public void onRenderStart(PicSphere sphere) {\n\n    }\n\n    @Override\n    public void onStepChange(PicSphere sphere, int newStep) {\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PICSPHERE) {\n            mContext.setHelperText(String.format(mContext.getString(\n                    R.string.picsphere_rendering_progress), sphere.getRenderProgress()));\n        }\n    }\n\n    @Override\n    public void onRenderDone(PicSphere sphere) {\n        mPicSpheres.remove(sphere);\n\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PICSPHERE) {\n            mContext.setHelperText(mContext.getString(R.string.picsphere_start_hint));\n        }\n\n        if (mCapture3DRenderer != null) {\n            mCapture3DRenderer.setCamPreviewVisible(true);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/PicSphereRenderingService.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.app.Notification;\nimport android.app.NotificationManager;\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport fr.xplod.focal.R;\n\n/**\n * Service that handles PicSphere rendering outside\n * the app context (as the rendering thread would get killed)\n */\npublic class PicSphereRenderingService extends Service implements PicSphere.ProgressListener {\n    private NotificationManager mNM;\n\n    // Unique Identification Number for the Notification.\n    // We use it on Notification start, and to cancel it.\n    private int NOTIFICATION = 1337;\n\n    private boolean mHasFailed = false;\n    private int mRenderQueue = 0;\n\n    /**\n     * Class for clients to access.  Because we know this service always\n     * runs in the same process as its clients, we don't need to deal with\n     * IPC.\n     */\n    public class LocalBinder extends Binder {\n        PicSphereRenderingService getService() {\n            return PicSphereRenderingService.this;\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.i(\"LocalService\", \"Received start id \" + startId + \": \" + intent);\n        // We want this service to continue running until it is explicitly\n        // stopped, so return sticky.\n        return START_STICKY;\n    }\n\n    @Override\n    public void onDestroy() {\n        // Cancel the persistent notification.\n        mNM.cancel(NOTIFICATION);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return mBinder;\n    }\n\n    // This is the object that receives interactions from clients.  See\n    // RemoteService for a more complete example.\n    private final IBinder mBinder = new LocalBinder();\n\n    public void render(final PicSphere sphere, final int orientation) {\n        sphere.addProgressListener(this);\n        mRenderQueue++;\n        new Thread() {\n            public void run() {\n                if (!sphere.render(orientation)) {\n                    mHasFailed = true;\n                    mNM.notify(NOTIFICATION,\n                            buildFailureNotification(getString(R.string.picsphere_failed),\n                                    getString(R.string.picsphere_failed_details)));\n                }\n                mRenderQueue--;\n\n                if (mRenderQueue == 0) {\n                    // We have no more PicSpheres to render, so we can stop the service. Otherwise,\n                    // we leave it on for other spheres.\n                    PicSphereRenderingService.this.stopSelf();\n                }\n            }\n        }.start();\n    }\n\n    @Override\n    public void onRenderStart(PicSphere sphere) {\n        // Display a notification\n        mNM.notify(NOTIFICATION, buildProgressNotification(0,\n                getString(R.string.picsphere_step_preparing)));\n        mHasFailed = false;\n    }\n\n    @Override\n    public void onStepChange(PicSphere sphere, int newStep) {\n        int progressPerStep = 100 / PicSphere.STEP_TOTAL;\n        int progress = newStep * progressPerStep;\n        String text = \"\";\n\n        switch (newStep) {\n            case PicSphere.STEP_PTOGEN:\n                text = getString(R.string.picsphere_step_ptogen);\n                break;\n\n            case PicSphere.STEP_PTOVAR:\n                text = getString(R.string.picsphere_step_ptovar);\n                break;\n\n            case PicSphere.STEP_CPFIND:\n                text = getString(R.string.picsphere_step_cpfind);\n                break;\n\n            case PicSphere.STEP_PTCLEAN:\n                text = getString(R.string.picsphere_step_ptclean);\n                break;\n\n            case PicSphere.STEP_AUTOOPTIMISER:\n                text = getString(R.string.picsphere_step_autooptimiser);\n                break;\n\n            case PicSphere.STEP_PANOMODIFY:\n                text = getString(R.string.picsphere_step_panomodify);\n                break;\n\n            case PicSphere.STEP_NONA:\n                text = getString(R.string.picsphere_step_nona);\n                break;\n\n            case PicSphere.STEP_ENBLEND:\n                text = getString(R.string.picsphere_step_enblend);\n                break;\n        }\n\n        mNM.notify(NOTIFICATION, buildProgressNotification(progress, text));\n    }\n\n    @Override\n    public void onRenderDone(PicSphere sphere) {\n        if (!mHasFailed) {\n            mNM.cancel(NOTIFICATION);\n        }\n    }\n\n    private Notification buildProgressNotification(int percentage, String text) {\n        Notification.Builder mBuilder =\n                new Notification.Builder(this)\n                        .setSmallIcon(R.drawable.ic_launcher)\n                        .setContentTitle(getString(R.string.picsphere_notif_title))\n                        .setContentText(percentage+\"% (\"+text+\")\")\n                        .setOngoing(true);\n\n        return mBuilder.build();\n    }\n\n    private Notification buildFailureNotification(String title, String text) {\n        Notification.Builder mBuilder =\n                new Notification.Builder(this)\n                        .setSmallIcon(R.drawable.ic_launcher)\n                        .setContentTitle(title)\n                        .setContentText(text)\n                        .setOngoing(false);\n\n        return mBuilder.build();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/Quaternion.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.opengl.Matrix;\n\n/**\n * 3D Maths - Quaternion\n * Inspired from http://content.gpwiki.org/index.php/OpenGL%3aTutorials%3aUsing_Quaternions_to_represent_rotation\n */\npublic class Quaternion {\n    public float x;\n    public float y;\n    public float z;\n    public float w = 1.0f;\n\n    public final static float PIOVER180 = (float) (Math.PI / 180.0f);\n    private final static float TOLERANCE = 0.00001f;\n\n    public Quaternion() {\n\n    }\n\n    public Quaternion(Quaternion o) {\n        this.x = o.x;\n        this.y = o.y;\n        this.z = o.z;\n        this.w = o.w;\n    }\n\n    public Quaternion(float x, float y, float z, float w) {\n        this.x = x;\n        this.y = y;\n        this.z = z;\n        this.w = w;\n    }\n\n    public void fromEuler(float pitch, float yaw, float roll) {\n        // Basically we create 3 Quaternions, one for pitch, one for yaw, one for roll\n        // and multiply those together.\n        // the calculation below does the same, just shorter\n\n        float p = pitch * PIOVER180 / 2.0f;\n        float y = yaw * PIOVER180 / 2.0f;\n        float r = roll * PIOVER180 / 2.0f;\n\n        float sinp = (float) Math.sin(p);\n        float siny = (float) Math.sin(y);\n        float sinr = (float) Math.sin(r);\n        float cosp = (float) Math.cos(p);\n        float cosy = (float) Math.cos(y);\n        float cosr = (float) Math.cos(r);\n\n        this.x = sinr * cosp * cosy - cosr * sinp * siny;\n        this.y = cosr * sinp * cosy + sinr * cosp * siny;\n        this.z = cosr * cosp * siny - sinr * sinp * cosy;\n        this.w = cosr * cosp * cosy + sinr * sinp * siny;\n\n        normalise();\n    }\n\n    // Convert to Matrix\n    public float[] getMatrix() {\n        float x2 = x * x;\n        float y2 = y * y;\n        float z2 = z * z;\n        float xy = x * y;\n        float xz = x * z;\n        float yz = y * z;\n        float wx = w * x;\n        float wy = w * y;\n        float wz = w * z;\n\n        // This calculation would be a lot more complicated for non-unit length quaternions\n        // Note: The constructor of Matrix4 expects the Matrix in column-major format like\n        // expected by OpenGL\n        return new float[]{1.0f - 2.0f * (y2 + z2), 2.0f * (xy - wz), 2.0f * (xz + wy), 0.0f,\n                2.0f * (xy + wz), 1.0f - 2.0f * (x2 + z2), 2.0f * (yz - wx), 0.0f,\n                2.0f * (xz - wy), 2.0f * (yz + wx), 1.0f - 2.0f * (x2 + y2), 0.0f,\n                0.0f, 0.0f, 0.0f, 1.0f};\n    }\n\n    public Quaternion getConjugate() {\n        return new Quaternion(-x, -y, -z, w);\n    }\n\n    // Multiplying q1 with q2 applies the rotation q2 to q1\n    public Quaternion multiply(Quaternion rq) {\n        // the constructor takes its arguments as (x, y, z, w)\n        return new Quaternion(w * rq.x + x * rq.w + y * rq.z - z * rq.y,\n                w * rq.y + y * rq.w + z * rq.x - x * rq.z,\n                w * rq.z + z * rq.w + x * rq.y - y * rq.x,\n                w * rq.w - x * rq.x - y * rq.y - z * rq.z);\n    }\n\n    // normalising a quaternion works similar to a vector. This method will not do anything\n    // if the quaternion is close enough to being unit-length. define TOLERANCE as something\n    // small like 0.00001f to get accurate results\n    public void normalise() {\n        // Don't normalize if we don't have to\n        float mag2 = w * w + x * x + y * y + z * z;\n        if (Math.abs(mag2) > TOLERANCE && Math.abs(mag2 - 1.0f) > TOLERANCE) {\n            float mag = (float) Math.sqrt(mag2);\n            w /= mag;\n            x /= mag;\n            y /= mag;\n            z /= mag;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/SensorFusion.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2012 Paul Lawitzki\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\nimport android.content.Context;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\n\nimport java.util.Timer;\nimport java.util.TimerTask;\n\npublic class SensorFusion implements SensorEventListener {\n    private SensorManager mSensorManager = null;\n    float[] mOrientation = new float[3];\n    float[] mRotationMatrix;\n\n    public SensorFusion(Context context) {\n        // get sensorManager and initialise sensor listeners\n        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);\n        initListeners();\n    }\n\n    public void onPauseOrStop() {\n        mSensorManager.unregisterListener(this);\n    }\n\n    public void onResume() {\n        // restore the sensor listeners when user resumes the application.\n        initListeners();\n    }\n\n    // This function registers sensor listeners for the accelerometer, magnetometer and gyroscope.\n    public void initListeners(){\n        mSensorManager.registerListener(this,\n                mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR),\n                20000);\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        if (mRotationMatrix == null) {\n            mRotationMatrix = new float[16];\n        }\n\n        // Get rotation matrix from angles\n        SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);\n\n        // Remap the axes\n        SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Z,\n                SensorManager.AXIS_X, mRotationMatrix);\n\n        // Get back a remapped orientation vector\n        SensorManager.getOrientation(mRotationMatrix, mOrientation);\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int i) {\n\n    }\n\n    public float[] getFusedOrientation() {\n        return mOrientation;\n    }\n\n    public float[] getRotationMatrix() {\n        return mRotationMatrix;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/picsphere/Vector3.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.picsphere;\n\n/**\n * 3D Maths - 3 component vector\n */\npublic class Vector3 {\n    public float x;\n    public float y;\n    public float z;\n\n    public Vector3() {\n\n    }\n\n    public Vector3(float x, float y, float z) {\n        this.x = x;\n        this.y = y;\n        this.z = z;\n    }\n\n    public Vector3(Vector3 other) {\n        this.x = other.x;\n        this.y = other.y;\n        this.z = other.z;\n    }\n\n    /**\n     * Returns the length of the vector\n     */\n    public float length() {\n        return (float) Math.sqrt(x * x + y * y + z * z);\n    }\n\n\n    public void normalise() {\n        final float length = length();\n\n        if(length != 0) {\n            y = x/length;\n            y = y/length;\n            z = z/length;\n        }\n    }\n\n    public Vector3 multiply(Quaternion quat) {\n        Vector3 vn = new Vector3(this);\n        vn.normalise();\n\n        Quaternion vecQuat = new Quaternion(),\n                resQuat = new Quaternion();\n        vecQuat.x = vn.x;\n        vecQuat.y = vn.y;\n        vecQuat.z = vn.z;\n        vecQuat.w = 0.0f;\n\n        resQuat = vecQuat.multiply(quat.getConjugate());\n        resQuat = quat.multiply(resQuat);\n\n        return new Vector3(resQuat.x, resQuat.y, resQuat.z);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/CenteredSeekBar.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Paint.Style;\nimport android.graphics.RectF;\nimport android.os.Bundle;\nimport android.os.Parcelable;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.ViewConfiguration;\nimport android.widget.ImageView;\n\nimport fr.xplod.focal.R;\n\nimport java.math.BigDecimal;\n\n/**\n * Seek bar that starts from the middle\n * Based on RangeSeekBar at https://code.google.com/p/range-seek-bar/\n */\npublic class CenteredSeekBar extends ImageView {\n    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private final Bitmap thumbImage = BitmapFactory.decodeResource(\n            getResources(), R.drawable.seek_thumb_normal);\n    private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(\n            getResources(), R.drawable.seek_thumb_pressed);\n    private final float thumbWidth = thumbImage.getWidth();\n    private final float thumbHalfWidth = 0.5f * thumbWidth;\n    private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();\n    private final float lineHeight = 0.3f * thumbHalfHeight;\n    private final float padding = thumbHalfWidth;\n    private final Integer absoluteMinValue, absoluteMaxValue;\n    private final NumberType numberType;\n    private final double absoluteMinValuePrim, absoluteMaxValuePrim;\n    private double normalizedValue = 0d;\n    private Thumb pressedThumb = null;\n    private boolean notifyWhileDragging = false;\n    private OnCenteredSeekBarChangeListener listener;\n\n    /**\n     * Default color of a {@link CenteredSeekBar}, #FF33B5E5.\n     * This is also known as \"Ice Cream Sandwich\" blue.\n     */\n    public static final int DEFAULT_COLOR = Color.argb(0xFF, 0x33, 0xB5, 0xE5);\n\n    /**\n     * An invalid pointer id.\n     */\n    public static final int INVALID_POINTER_ID = 255;\n\n    // Localized constants from MotionEvent for compatibility\n    // with API < 8 \"Froyo\".\n    public static final int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00,\n            ACTION_POINTER_INDEX_SHIFT = 8;\n\n    private float mDownMotionX;\n    private int mActivePointerId = INVALID_POINTER_ID;\n\n    /**\n     * On touch, this offset plus the scaled value from the position of the\n     * touch will form the progress value. Usually 0.\n     */\n    float mTouchProgressOffset;\n\n    private int mScaledTouchSlop;\n    private boolean mIsDragging;\n\n    /**\n     * Creates a new RangeSeekBar.\n     *\n     * @param absoluteMinValue\n     *            The minimum value of the selectable range.\n     * @param absoluteMaxValue\n     *            The maximum value of the selectable range.\n     * @param context\n     * @throws IllegalArgumentException\n     *             Will be thrown if min/max value type is not one of Long, Double,\n     *             Integer, Float, Short, Byte or BigDecimal.\n     */\n    public CenteredSeekBar(Integer absoluteMinValue, Integer absoluteMaxValue,\n            Context context) throws IllegalArgumentException {\n        super(context);\n        this.absoluteMinValue = absoluteMinValue;\n        this.absoluteMaxValue = absoluteMaxValue;\n        absoluteMinValuePrim = absoluteMinValue.doubleValue();\n        absoluteMaxValuePrim = absoluteMaxValue.doubleValue();\n        numberType = NumberType.fromNumber(absoluteMinValue);\n\n        // Make RangeSeekBar focusable. This solves focus handling issues in case\n        // EditText widgets are being used along with the RangeSeekBar within ScollViews.\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        init();\n    }\n\n    public CenteredSeekBar(Context context, AttributeSet attrs) {\n        super(context);\n        this.absoluteMinValue = 0;\n        this.absoluteMaxValue = 10;\n        absoluteMinValuePrim = absoluteMinValue.doubleValue();\n        absoluteMaxValuePrim = absoluteMaxValue.doubleValue();\n        numberType = NumberType.fromNumber(absoluteMinValue);\n\n        // Make RangeSeekBar focusable. This solves focus handling issues in case\n        // EditText widgets are being used along with the RangeSeekBar within ScollViews.\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        init();\n    }\n\n    private final void init() {\n        mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();\n    }\n\n    public boolean isNotifyWhileDragging() {\n        return notifyWhileDragging;\n    }\n\n    /**\n     * Should the widget notify the listener callback while the user\n     * is still dragging a thumb? Default is false.\n     *\n     * @param flag\n     */\n    public void setNotifyWhileDragging(boolean flag) {\n        this.notifyWhileDragging = flag;\n    }\n\n    /**\n     * Returns the absolute minimum value of the range that has\n     * been set at construction time.\n     *\n     * @return The absolute minimum value of the range.\n     */\n    public Integer getAbsoluteMinValue() {\n        return absoluteMinValue;\n    }\n\n    /**\n     * Returns the absolute maximum value of the range that has\n     * been set at construction time.\n     *\n     * @return The absolute maximum value of the range.\n     */\n    public Integer getAbsoluteMaxValue() {\n        return absoluteMaxValue;\n    }\n\n    /**\n     * Returns the currently selected value.\n     *\n     * @return The currently selected value.\n     */\n    public Integer getSelectedValue() {\n        return normalizedToValue(normalizedValue);\n    }\n\n    /**\n     * Sets the currently selected minimum value.\n     * The widget will be invalidated and redrawn.\n     *\n     * @param value\n     *            The Integer value to set the minimum value to.\n     *            Will be clamped to given absolute minimum/maximum range.\n     */\n    public void setSelectedMinValue(Integer value) {\n        // In case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.\n        if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {\n            setNormalizedValue(0d);\n        } else {\n            setNormalizedValue(valueToNormalized(value));\n        }\n    }\n\n    /**\n     * Registers given listener callback to notify about changed selected values.\n     *\n     * @param listener\n     *            The listener to notify about changed selected values.\n     */\n    public void setOnCenteredSeekBarChangeListener(OnCenteredSeekBarChangeListener listener) {\n        this.listener = listener;\n    }\n\n    /**\n     * Handles thumb selection and movement.\n     * Notifies listener callback on certain events.\n     */\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (!isEnabled()) {\n            return false;\n        }\n\n        int pointerIndex;\n\n        final int action = event.getAction();\n        switch (action & MotionEvent.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN:\n                // Remember where the motion event started\n                mActivePointerId = event.getPointerId(event.getPointerCount() - 1);\n                pointerIndex = event.findPointerIndex(mActivePointerId);\n                mDownMotionX = event.getX(pointerIndex);\n\n                pressedThumb = evalPressedThumb(mDownMotionX);\n\n                // Only handle thumb presses\n                if (pressedThumb == null) {\n                    return super.onTouchEvent(event);\n                }\n\n                setPressed(true);\n                invalidate();\n                onStartTrackingTouch();\n                trackTouchEvent(event);\n                attemptClaimDrag();\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (pressedThumb != null) {\n                    if (mIsDragging) {\n                        trackTouchEvent(event);\n                    } else {\n                        // Scroll to follow the motion event\n                        pointerIndex = event.findPointerIndex(mActivePointerId);\n                        final float x = event.getX(pointerIndex);\n\n                        if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {\n                            setPressed(true);\n                            invalidate();\n                            onStartTrackingTouch();\n                            trackTouchEvent(event);\n                            attemptClaimDrag();\n                        }\n                    }\n\n                    if (notifyWhileDragging && listener != null) {\n                        listener.OnCenteredSeekBarValueChanged(this, getSelectedValue());\n                    }\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n                if (mIsDragging) {\n                    trackTouchEvent(event);\n                    onStopTrackingTouch();\n                    setPressed(false);\n                } else {\n                    // Touch up when we never crossed the touch slop threshold\n                    // should be interpreted as a tap-seek to that location.\n                    onStartTrackingTouch();\n                    trackTouchEvent(event);\n                    onStopTrackingTouch();\n                }\n\n                pressedThumb = null;\n                invalidate();\n                if (listener != null) {\n                    listener.OnCenteredSeekBarValueChanged(this, getSelectedValue());\n                }\n                break;\n            case MotionEvent.ACTION_POINTER_DOWN:\n                final int index = event.getPointerCount() - 1;\n                // Final int index = ev.getActionIndex();\n                mDownMotionX = event.getX(index);\n                mActivePointerId = event.getPointerId(index);\n                invalidate();\n                break;\n            case MotionEvent.ACTION_POINTER_UP:\n                onSecondaryPointerUp(event);\n                invalidate();\n                break;\n            case MotionEvent.ACTION_CANCEL:\n                if (mIsDragging) {\n                    onStopTrackingTouch();\n                    setPressed(false);\n                }\n                invalidate(); // see above explanation\n                break;\n        }\n        return true;\n    }\n\n    private final void onSecondaryPointerUp(MotionEvent ev) {\n        final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK)\n                >> ACTION_POINTER_INDEX_SHIFT;\n\n        final int pointerId = ev.getPointerId(pointerIndex);\n        if (pointerId == mActivePointerId) {\n            // This was our active pointer going up. Choose\n            // a new active pointer and adjust accordingly.\n            // TODO: Make this decision more intelligent.\n            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;\n            mDownMotionX = ev.getX(newPointerIndex);\n            mActivePointerId = ev.getPointerId(newPointerIndex);\n        }\n    }\n\n    private final void trackTouchEvent(MotionEvent event) {\n        final int pointerIndex = event.findPointerIndex(mActivePointerId);\n        final float x = event.getX(pointerIndex);\n\n        setNormalizedValue(screenToNormalized(x));\n        setSelectedMinValue(normalizedToValue(normalizedValue));\n\n    }\n\n    /**\n     * Tries to claim the user's drag motion, and requests disallowing any ancestors\n     * from stealing events in the drag.\n     */\n    private void attemptClaimDrag() {\n        if (getParent() != null) {\n            getParent().requestDisallowInterceptTouchEvent(true);\n        }\n    }\n\n    /**\n     * This is called when the user has started touching this widget.\n     */\n    void onStartTrackingTouch() {\n        mIsDragging = true;\n    }\n\n    /**\n     * This is called when the user either releases his touch or the touch is canceled.\n     */\n    void onStopTrackingTouch() {\n        mIsDragging = false;\n    }\n\n    /**\n     * Ensures correct size of the widget.\n     */\n    @Override\n    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        setMeasuredDimension(getMaxWidth(), getMaxHeight());\n    }\n\n    /**\n     * Draws the widget on the given canvas.\n     */\n    @Override\n    protected synchronized void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        // draw seek bar background line\n        final RectF rect = new RectF(padding, 0.5f * (getHeight() - lineHeight),\n                getWidth() - padding, 0.5f * (getHeight() + lineHeight));\n        paint.setStyle(Style.FILL);\n        paint.setColor(Color.GRAY);\n        paint.setAntiAlias(true);\n        canvas.drawRect(rect, paint);\n\n        // Draw seek bar active range line\n        if (normalizedValue > valueToNormalized(absoluteMaxValue) / 2) {\n            rect.left = normalizedToScreen(valueToNormalized(absoluteMaxValue) / 2);\n            rect.right = normalizedToScreen(normalizedValue);\n        } else {\n            rect.left = normalizedToScreen(normalizedValue);\n            rect.right = normalizedToScreen(valueToNormalized(absoluteMaxValue) / 2);\n        }\n\n        // Fill color\n        paint.setColor(DEFAULT_COLOR);\n        canvas.drawRect(rect, paint);\n\n        // Draw a small ball at the middle\n        canvas.drawCircle(normalizedToScreen(valueToNormalized(absoluteMaxValue) / 2),\n                getHeight() / 2.0f - 2.0f, 8.0f, paint);\n\n        // Draw  thumb\n        drawThumb(normalizedToScreen(normalizedValue), Thumb.MIN.equals(pressedThumb), canvas);\n    }\n\n    /**\n     * Overridden to save instance state when device orientation changes.\n     * This method is called automatically if you assign an id to the\n     * RangeSeekBar widget using the {@link #setId(int)} method.\n     * Other members of this class than the normalized min and\n     * max values don't need to be saved.\n     */\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        final Bundle bundle = new Bundle();\n        bundle.putParcelable(\"SUPER\", super.onSaveInstanceState());\n        bundle.putDouble(\"MIN\", normalizedValue);\n        return bundle;\n    }\n\n    /**\n     * Overridden to restore instance state when device orientation changes.\n     * This method is called automatically if you assign an id to the\n     * RangeSeekBar widget using the {@link #setId(int)} method.\n     */\n    @Override\n    protected void onRestoreInstanceState(Parcelable parcel) {\n        final Bundle bundle = (Bundle) parcel;\n        super.onRestoreInstanceState(bundle.getParcelable(\"SUPER\"));\n        normalizedValue = bundle.getDouble(\"MIN\");\n    }\n\n    /**\n     * Draws the \"normal\" resp. \"pressed\" thumb image on specified x-coordinate.\n     *\n     * @param screenCoord\n     *            The x-coordinate in screen space where to draw the image.\n     * @param pressed\n     *            Is the thumb currently in \"pressed\" state?\n     * @param canvas\n     *            The canvas to draw upon.\n     */\n    private void drawThumb(float screenCoord, boolean pressed, Canvas canvas) {\n        canvas.drawBitmap(pressed ? thumbPressedImage : thumbImage, screenCoord\n                - thumbHalfWidth, (float) ((0.5f * getMeasuredHeight()) - thumbHalfHeight), paint);\n    }\n\n    /**\n     * Decides which (if any) thumb is touched by the given x-coordinate.\n     *\n     * @param touchX\n     *            The x-coordinate of a touch event in screen space.\n     * @return The pressed thumb or null if none has been touched.\n     */\n    private Thumb evalPressedThumb(float touchX) {\n        Thumb result = null;\n        boolean minThumbPressed = isInThumbRange(touchX, normalizedValue);\n        if (minThumbPressed) {\n            result = Thumb.MIN;\n        }\n        return result;\n    }\n\n    /**\n     * Decides if given x-coordinate in screen space needs to be interpreted as\n     * \"within\" the normalized thumb x-coordinate.\n     *\n     * @param touchX\n     *            The x-coordinate in screen space to check.\n     * @param normalizedThumbValue\n     *            The normalized x-coordinate of the thumb to check.\n     * @return true if x-coordinate is in thumb range, false otherwise.\n     */\n    private boolean isInThumbRange(float touchX, double normalizedThumbValue) {\n        return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth;\n    }\n\n    /**\n     * Sets normalized min value to value so that 0 <= value <= normalized max value <= 1.\n     * The View will get invalidated when calling this method.\n     *\n     * @param value\n     *            The new normalized min value to set.\n     */\n    public void setNormalizedValue(double value) {\n        normalizedValue = Math.max(0d, Math.min(1d, Math.min(value,\n                valueToNormalized(absoluteMaxValue))));\n        invalidate();\n    }\n\n    /**\n     * Converts a normalized value to a Integer object in the value\n     * space between absolute minimum and maximum.\n     *\n     * @param normalized\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    private Integer normalizedToValue(double normalized) {\n        return (Integer) numberType.toNumber(absoluteMinValuePrim + normalized\n                * (absoluteMaxValuePrim - absoluteMinValuePrim));\n    }\n\n    /**\n     * Converts the given Integer value to a normalized double.\n     *\n     * @param value\n     *            The Integer value to normalize.\n     * @return The normalized double.\n     */\n    private double valueToNormalized(Integer value) {\n        if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {\n            // prevent division by zero, simply return 0.\n            return 0d;\n        }\n        return (value.doubleValue() - absoluteMinValuePrim)\n                / (absoluteMaxValuePrim - absoluteMinValuePrim);\n    }\n\n    /**\n     * Converts a normalized value into screen space.\n     *\n     * @param normalizedCoord\n     *            The normalized value to convert.\n     * @return The converted value in screen space.\n     */\n    private float normalizedToScreen(double normalizedCoord) {\n        return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));\n    }\n\n    /**\n     * Converts screen space x-coordinates into normalized values.\n     *\n     * @param screenCoord\n     *            The x-coordinate in screen space to convert.\n     * @return The normalized value.\n     */\n    private double screenToNormalized(float screenCoord) {\n        int width = getWidth();\n        if (width <= 2 * padding) {\n            // prevent division by zero, simply return 0.\n            return 0d;\n        }\n        else {\n            double result = (screenCoord - padding) / (width - 2 * padding);\n            return Math.min(1d, Math.max(0d, result));\n        }\n    }\n\n    /**\n     * Callback listener interface to notify about changed range values.\n     *\n     * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)\n     *\n     */\n    public interface OnCenteredSeekBarChangeListener {\n        public void OnCenteredSeekBarValueChanged(CenteredSeekBar bar, Integer minValue);\n    }\n\n    /**\n     * Thumb constants (min and max).\n     */\n    private static enum Thumb {\n        MIN\n    };\n\n    /**\n     * Utility enumaration used to convert between Numbers and doubles.\n     *\n     * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)\n     *\n     */\n    private static enum NumberType {\n        LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;\n\n        public static <E extends Number> NumberType fromNumber(E value)\n                throws IllegalArgumentException {\n            if (value instanceof Long) {\n                return LONG;\n            }\n            if (value instanceof Double) {\n                return DOUBLE;\n            }\n            if (value instanceof Integer) {\n                return INTEGER;\n            }\n            if (value instanceof Float) {\n                return FLOAT;\n            }\n            if (value instanceof Short) {\n                return SHORT;\n            }\n            if (value instanceof Byte) {\n                return BYTE;\n            }\n            if (value instanceof BigDecimal) {\n                return BIG_DECIMAL;\n            }\n            throw new IllegalArgumentException(\"Integer class '\"\n                    + value.getClass().getName() + \"' is not supported\");\n        }\n\n        public Integer toNumber(double value) {\n            return (int)value;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/CircleTimerView.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2012 The Android Open Source Project\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.RectF;\nimport android.os.SystemClock;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport fr.xplod.focal.R;\n\npublic class CircleTimerView extends View {\n\n\n    private int mRedColor;\n    private int mWhiteColor;\n    private long mIntervalTime = 0;\n    private long mIntervalStartTime = -1;\n    private long mMarkerTime = -1;\n    private long mCurrentIntervalTime = 0;\n    private long mAccumulatedTime = 0;\n    private boolean mPaused = false;\n    private boolean mAnimate = false;\n    private static float mCircleXCenterLeftPadding = 0;\n    private static float mStrokeSize = 4;\n    private static float mDiamondStrokeSize = 12;\n    private static float mMarkerStrokeSize = 2;\n    private final Paint mPaint = new Paint();\n    private final Paint mFill = new Paint();\n    private final RectF mArcRect = new RectF();\n    private float mRectHalfWidth = 6f;\n    private Resources mResources;\n    private float mRadiusOffset;   // amount to remove from radius to account for markers on circle\n    private float mScreenDensity;\n\n    // Class has 2 modes:\n    // Timer mode - counting down. in this mode the animation is counter-clockwise and stops at 0\n    // Stop watch mode - counting up - in this mode the animation is clockwise and will keep the\n    //                   animation until stopped.\n    private boolean mTimerMode = true; // default is timer mode\n\n    public CircleTimerView(Context context) {\n        this(context, null);\n    }\n\n    public CircleTimerView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n        setAlpha(0);\n    }\n\n    public void setIntervalTime(long t) {\n        mIntervalTime = t;\n        postInvalidate();\n    }\n\n    public void setMarkerTime(long t) {\n        mMarkerTime = t;\n        postInvalidate();\n    }\n\n    public void reset() {\n        mIntervalStartTime = -1;\n        mMarkerTime = -1;\n        postInvalidate();\n    }\n\n    public void startIntervalAnimation() {\n        mIntervalStartTime = SystemClock.elapsedRealtime();\n        mAnimate = true;\n        invalidate();\n        mPaused = false;\n    }\n\n    public void stopIntervalAnimation() {\n        mAnimate = false;\n        mIntervalStartTime = -1;\n        mAccumulatedTime = 0;\n    }\n\n    public boolean isAnimating() {\n        return (mIntervalStartTime != -1);\n    }\n\n    public void pauseIntervalAnimation() {\n        mAnimate = false;\n        mAccumulatedTime += SystemClock.elapsedRealtime() - mIntervalStartTime;\n        mPaused = true;\n    }\n\n    public void abortIntervalAnimation() {\n        mAnimate = false;\n    }\n\n    public void setPassedTime(long time, boolean drawRed) {\n        // The onDraw() method checks if mIntervalStartTime has been set before drawing any red.\n        // Without drawRed, mIntervalStartTime should not be set here at all, and would remain at -1\n        // when the state is reconfigured after exiting and re-entering the application.\n        // If the timer is currently running, this drawRed will not be set, and will have no effect\n        // because mIntervalStartTime will be set when the thread next runs.\n        // When the timer is not running, mIntervalStartTime will not be set upon loading the state,\n        // and no red will be drawn, so drawRed is used to force onDraw() to draw the red portion,\n        // despite the timer not running.\n        mCurrentIntervalTime = mAccumulatedTime = time;\n        if (drawRed) {\n            mIntervalStartTime = SystemClock.elapsedRealtime();\n        }\n        postInvalidate();\n    }\n\n    /**\n     * Calculate the amount by which the radius of a CircleTimerView should\n     * be offset by the any of the extra painted objects.\n     */\n    public static float calculateRadiusOffset(\n            float strokeSize, float diamondStrokeSize, float markerStrokeSize) {\n        return Math.max(strokeSize, Math.max(diamondStrokeSize, markerStrokeSize));\n    }\n\n    private void init(Context c) {\n        mResources = c.getResources();\n        mCircleXCenterLeftPadding = (mResources.getDimension(R.dimen.timer_circle_width)\n                - mResources.getDimension(R.dimen.timer_circle_diameter)) / 2;\n        mStrokeSize = mResources.getDimension(R.dimen.circletimer_circle_size);\n        mDiamondStrokeSize = mResources.getDimension(R.dimen.circletimer_diamond_size);\n        mMarkerStrokeSize = mResources.getDimension(R.dimen.circletimer_marker_size);\n        mRadiusOffset = calculateRadiusOffset(\n                mStrokeSize, mDiamondStrokeSize, mMarkerStrokeSize);\n        mPaint.setAntiAlias(true);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mWhiteColor = mResources.getColor(R.color.clock_white);\n        mRedColor = mResources.getColor(R.color.clock_red);\n        mScreenDensity = mResources.getDisplayMetrics().density;\n        mFill.setAntiAlias(true);\n        mFill.setStyle(Paint.Style.FILL);\n        mFill.setColor(mWhiteColor);\n        mRectHalfWidth = mDiamondStrokeSize / 2f;\n    }\n\n    public void setTimerMode(boolean mode) {\n        mTimerMode = mode;\n    }\n\n    @Override\n    public void onDraw(Canvas canvas) {\n        int xCenter = getWidth() / 2 + 1;\n        int yCenter = getHeight() / 2;\n\n        mPaint.setStrokeWidth(mStrokeSize);\n        float radius = Math.min(xCenter, yCenter) - mRadiusOffset;\n\n        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {\n            xCenter = (int) (radius + mRadiusOffset);\n            if (mTimerMode) {\n                xCenter += mCircleXCenterLeftPadding;\n            }\n        }\n\n        if (mIntervalStartTime == -1) {\n            // Just draw a complete red circle, no white arc needed\n            mPaint.setColor(mRedColor);\n            canvas.drawCircle (xCenter, yCenter, radius, mPaint);\n            if (mTimerMode) {\n                drawWhiteDiamond(canvas, 0f, xCenter, yCenter, radius);\n            }\n        } else {\n            if (mAnimate) {\n                mCurrentIntervalTime = SystemClock.elapsedRealtime()\n                        - mIntervalStartTime + mAccumulatedTime;\n            }\n            // Draw a combination of red and white arcs to create a circle\n            mArcRect.top = yCenter - radius;\n            mArcRect.bottom = yCenter + radius;\n            mArcRect.left =  xCenter - radius;\n            mArcRect.right = xCenter + radius;\n            float redPercent = (float)mCurrentIntervalTime / (float)mIntervalTime;\n            // Prevent timer from doing more than one full circle\n            redPercent = (redPercent > 1 && mTimerMode) ? 1 : redPercent;\n\n            float whitePercent = 1 - (redPercent > 1 ? 1 : redPercent);\n            // Draw white arc here\n            mPaint.setColor(mWhiteColor);\n            if (mTimerMode){\n                canvas.drawArc (mArcRect, 270, - redPercent * 360 , false, mPaint);\n            } else {\n                canvas.drawArc (mArcRect, 270, + redPercent * 360 , false, mPaint);\n            }\n\n            // Draw red arc here\n            mPaint.setStrokeWidth(mStrokeSize);\n            mPaint.setColor(mRedColor);\n            if (mTimerMode) {\n                canvas.drawArc(mArcRect, 270, + whitePercent * 360, false, mPaint);\n            } else {\n                canvas.drawArc(mArcRect, 270 + (1 - whitePercent) * 360,\n                        whitePercent * 360, false, mPaint);\n            }\n\n            if (mMarkerTime != -1 && radius > 0 && mIntervalTime != 0) {\n                mPaint.setStrokeWidth(mMarkerStrokeSize);\n                float angle = (float)(mMarkerTime % mIntervalTime) / (float)mIntervalTime * 360;\n                // Draw 2dips thick marker the formula to draw the marker 1 unit thick is:\n                // 180 / (radius * Math.PI) after that we have to scale it by the screen density\n                canvas.drawArc (mArcRect, 270 + angle, mScreenDensity *\n                        (float) (360 / (radius * Math.PI)) , false, mPaint);\n            }\n            drawWhiteDiamond(canvas, redPercent, xCenter, yCenter, radius);\n        }\n        if (mAnimate) {\n            invalidate();\n        }\n    }\n\n    protected void drawWhiteDiamond(\n            Canvas canvas, float degrees, int xCenter, int yCenter, float radius) {\n        mPaint.setColor(mWhiteColor);\n        float diamondPercent;\n        if (mTimerMode) {\n            diamondPercent = 270 - degrees * 360;\n        } else {\n            diamondPercent = 270 + degrees * 360;\n        }\n\n        canvas.save();\n        final double diamondRadians = Math.toRadians(diamondPercent);\n        canvas.translate(xCenter + (float) (radius * Math.cos(diamondRadians)),\n                yCenter + (float) (radius * Math.sin(diamondRadians)));\n        canvas.rotate(diamondPercent + 45f);\n        canvas.drawRect(-mRectHalfWidth, -mRectHalfWidth, mRectHalfWidth, mRectHalfWidth, mFill);\n        canvas.restore();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/ExposureHudRing.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * Exposure ring HUD that lets user select exposure metering point\n */\npublic class ExposureHudRing extends HudRing {\n    private CameraManager mCamManager;\n    private long mTimeLastSet = 0;\n    private final static long SET_INTERVAL = 100;\n\n    public ExposureHudRing(Context context) {\n        super(context);\n    }\n\n    public ExposureHudRing(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public ExposureHudRing(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        setImageResource(R.drawable.hud_exposure_ring);\n    }\n\n    public void setManagers(CameraManager camMan) {\n        mCamManager = camMan;\n    }\n\n    /**\n     * Centers the exposure ring on the x,y coordinates provided\n     * and sets the focus to this position\n     *\n     * @param x\n     * @param y\n     */\n    public void setPosition(float x, float y) {\n        setX(x - getWidth() / 2.0f);\n        setY(y - getHeight() / 2.0f);\n        applyExposurePoint();\n    }\n\n\n    private void applyExposurePoint() {\n        ViewGroup parent = (ViewGroup) getParent();\n        if (parent == null) return;\n\n        // We swap X/Y as we have a landscape preview in portrait mode\n        float centerPointX = getY() + getHeight() / 2.0f;\n        float centerPointY = parent.getWidth() - (getX() + getWidth() / 2.0f);\n\n        centerPointX *= 1000.0f / parent.getHeight();\n        centerPointY *= 1000.0f / parent.getWidth();\n\n        centerPointX = (centerPointX - 500.0f) * 2.0f;\n        centerPointY = (centerPointY - 500.0f) * 2.0f;\n\n        mTimeLastSet = System.currentTimeMillis();\n        mCamManager.setExposurePoint((int) centerPointX, (int) centerPointY);\n    }\n\n    @Override\n    public boolean onTouch(View view, MotionEvent motionEvent) {\n        super.onTouch(view, motionEvent);\n\n        if (motionEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {\n            if (System.currentTimeMillis() - mTimeLastSet > SET_INTERVAL) {\n                applyExposurePoint();\n            }\n        } else if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {\n            applyExposurePoint();\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/FocusHudRing.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport org.cyanogenmod.focal.FocusManager;\nimport fr.xplod.focal.R;\n\n/**\n * Focus ring HUD that lets user select focus point (tap to focus)\n */\npublic class FocusHudRing extends HudRing {\n    private CameraManager mCamManager;\n    private FocusManager mFocusManager;\n\n    public FocusHudRing(Context context) {\n        super(context);\n    }\n\n    public FocusHudRing(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public FocusHudRing(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n\n        setFocusImage(true);\n    }\n\n    public void setFocusImage(boolean success) {\n        if (success) {\n            setImageResource(R.drawable.hud_focus_ring_success);\n        } else {\n            setImageResource(R.drawable.hud_focus_ring_fail);\n        }\n    }\n\n    public void setManagers(CameraManager camMan, FocusManager focusMan) {\n        mCamManager = camMan;\n        mFocusManager = focusMan;\n    }\n\n    /**\n     * Centers the focus ring on the x,y coordinates provided\n     * and sets the focus to this position\n     *\n     * @param x\n     * @param y\n     */\n    public void setPosition(float x, float y) {\n        setX(x - getWidth() / 2.0f);\n        setY(y - getHeight() / 2.0f);\n        applyFocusPoint();\n    }\n\n\n    private void applyFocusPoint() {\n        ViewGroup parent = (ViewGroup) getParent();\n        if (parent == null) return;\n\n        // We swap X/Y as we have a landscape preview in portrait mode\n        float centerPointX = getY() + getHeight() / 2.0f;\n        float centerPointY = parent.getWidth() - (getX() + getWidth() / 2.0f);\n\n        centerPointX *= 1000.0f / parent.getHeight();\n        centerPointY *= 1000.0f / parent.getWidth();\n\n        centerPointX = (centerPointX - 500.0f) * 2.0f;\n        centerPointY = (centerPointY - 500.0f) * 2.0f;\n\n        // The CamManager might be null if users try to tap the preview area, when the\n        // camera is actually not yet ready\n        if (mCamManager != null) {\n            mCamManager.setFocusPoint((int) centerPointX, (int) centerPointY);\n        }\n    }\n\n    @Override\n    public boolean onTouch(View view, MotionEvent motionEvent) {\n        super.onTouch(view, motionEvent);\n\n        if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {\n            applyFocusPoint();\n\n            if (mFocusManager != null) {\n                mFocusManager.refocus();\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/HudRing.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.ImageView;\n\n/**\n * Represents a ring that can be dragged on the screen\n */\npublic class HudRing extends ImageView implements View.OnTouchListener {\n    private float mLastX;\n    private float mLastY;\n\n    private final static float IDLE_ALPHA = 0.25f;\n    private final static int ANIMATION_DURATION = 120;\n\n    public HudRing(Context context) {\n        super(context);\n    }\n\n    public HudRing(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public HudRing(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        setOnTouchListener(this);\n        setAlpha(IDLE_ALPHA);\n    }\n\n    @Override\n    public boolean onTouch(View view, MotionEvent motionEvent) {\n        if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {\n            mLastX = motionEvent.getRawX();\n            mLastY = motionEvent.getRawY();\n           /* mOffsetX = motionEvent.getX() - motionEvent.getRawX();\n            mOffsetY = motionEvent.getY() - motionEvent.getRawY();*/\n            animatePressDown();\n        } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {\n            setX(getX() + (motionEvent.getRawX() - mLastX));\n            setY(getY() + (motionEvent.getRawY() - mLastY));\n\n            mLastX = motionEvent.getRawX();\n            mLastY = motionEvent.getRawY();\n        } else if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {\n            animatePressUp();\n        }\n\n        return true;\n    }\n\n    public void animatePressDown() {\n        animate().alpha(1.0f).setDuration(ANIMATION_DURATION).start();\n    }\n\n    public void animatePressUp() {\n        animate().alpha(IDLE_ALPHA).rotation(0).setDuration(ANIMATION_DURATION).start();\n    }\n\n    public void animateWorking(long duration) {\n        animate().rotationBy(45.0f).setDuration(duration).setInterpolator(\n                new DecelerateInterpolator()).start();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/Notifier.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.util.AttributeSet;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\n\n/**\n * Little notifier that comes in from the side of the screen\n * to tell the user about something quickly\n */\npublic class Notifier extends LinearLayout {\n    private TextView mTextView;\n    private Handler mHandler;\n    private int mOrientation;\n    private Runnable mFadeOutRunnable = new Runnable() {\n        @Override\n        public void run() {\n            fadeOut();\n        }\n    };\n    private float mTargetX;\n    private float mTargetY;\n\n    public Notifier(Context context) {\n        super(context);\n        initialize();\n    }\n\n    public Notifier(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public Notifier(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    private void initialize() {\n        mHandler = new Handler();\n    }\n\n    public void notifyOrientationChanged(int orientation) {\n        mOrientation = orientation;\n        if (mTextView == null) return;\n\n        if (orientation % 180 != 0) {\n            // We translate to fit orientation properly within the screen\n            animate().rotation(orientation).setDuration(200).x(mTargetX + mTextView\n                    .getMeasuredHeight() / 2).y(mTargetY - mTextView.getMeasuredWidth() / 2)\n                    .setInterpolator(new DecelerateInterpolator()).start();\n        } else {\n            animate().rotation(orientation).setDuration(200).x(mTargetX)\n                    .y(mTargetY).setInterpolator(new DecelerateInterpolator()).start();\n        }\n    }\n\n    /**\n     * Shows the provided {@param text} during {@param durationMs} milliseconds with\n     * a nice animation.\n     *\n     * @param text\n     * @param durationMs\n     * @note This method must be ran in UI thread!\n     */\n    public void notify(final String text, final long durationMs) {\n        mHandler.post(new Runnable() {\n            public void run() {\n                mHandler.removeCallbacks(mFadeOutRunnable);\n                mTextView = (TextView) findViewById(R.id.notifier_text);\n                mTextView.setText(text);\n                setAlpha(0.0f);\n                setX(Util.getScreenSize(null).x*1.0f/3.0f);\n                setY(Util.getScreenSize(null).y*2.0f/3.0f);\n                mTargetX = getX();\n                mTargetY = getY();\n                notifyOrientationChanged(mOrientation);\n\n                fadeIn();\n                mHandler.postDelayed(mFadeOutRunnable, durationMs);\n            }\n        });\n    }\n\n    /**\n     * Shows the provided {@param text} during {@param durationMs} milliseconds at the\n     * provided {@param x} and {@param y} position with a nice animation.\n     *\n     * @param text The text to display\n     * @param durationMs How long should we display it\n     * @param x The X position\n     * @param y The Y position\n     */\n    public void notify(final String text, final long durationMs, final float x, final float y) {\n        mHandler.post(new Runnable() {\n            public void run() {\n                mHandler.removeCallbacks(mFadeOutRunnable);\n                mTextView = (TextView) findViewById(R.id.notifier_text);\n                mTextView.setText(text);\n                setAlpha(0.0f);\n                setX(x);\n                setY(y);\n\n                mTargetX = getX();\n                mTargetY = getY();\n\n                notifyOrientationChanged(mOrientation);\n\n                fadeIn();\n                mHandler.postDelayed(mFadeOutRunnable, durationMs);\n            }\n        });\n    }\n\n    private void fadeIn() {\n        animate().setDuration(300).alpha(1.0f).setInterpolator(\n                new AccelerateInterpolator()).start();\n    }\n\n    private void fadeOut() {\n        animate().setDuration(300).alpha(0.0f).setInterpolator(\n                new DecelerateInterpolator()).start();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/PanoProgressBar.java",
    "content": "/*\n * Copyright (C) 2011 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.RectF;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\npublic class PanoProgressBar extends ImageView {\n    @SuppressWarnings(\"unused\")\n    private static final String TAG = \"PanoProgressBar\";\n    public static final int DIRECTION_NONE = 0;\n    public static final int DIRECTION_LEFT = 1;\n    public static final int DIRECTION_RIGHT = 2;\n    private float mProgress = 0;\n    private float mMaxProgress = 0;\n    private float mLeftMostProgress = 0;\n    private float mRightMostProgress = 0;\n    private float mProgressOffset = 0;\n    private float mIndicatorWidth = 0;\n    private int mDirection = 0;\n    private final Paint mBackgroundPaint = new Paint();\n    private final Paint mDoneAreaPaint = new Paint();\n    private final Paint mIndicatorPaint = new Paint();\n    private float mWidth;\n    private float mHeight;\n    private RectF mDrawBounds;\n    private OnDirectionChangeListener mListener = null;\n\n    public interface OnDirectionChangeListener {\n        public void onDirectionChange(int direction);\n    }\n\n    public PanoProgressBar(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mDoneAreaPaint.setStyle(Paint.Style.FILL);\n        mDoneAreaPaint.setAlpha(0xff);\n\n        mBackgroundPaint.setStyle(Paint.Style.FILL);\n        mBackgroundPaint.setAlpha(0xff);\n\n        mIndicatorPaint.setStyle(Paint.Style.FILL);\n        mIndicatorPaint.setAlpha(0xff);\n\n        mDrawBounds = new RectF();\n    }\n\n    public void setOnDirectionChangeListener(OnDirectionChangeListener l) {\n        mListener = l;\n    }\n\n    private void setDirection(int direction) {\n        if (mDirection != direction) {\n            mDirection = direction;\n            if (mListener != null) {\n                mListener.onDirectionChange(mDirection);\n            }\n            invalidate();\n        }\n    }\n\n    public int getDirection() {\n        return mDirection;\n    }\n\n    @Override\n    public void setBackgroundColor(int color) {\n        mBackgroundPaint.setColor(color);\n        invalidate();\n    }\n\n    public void setDoneColor(int color) {\n        mDoneAreaPaint.setColor(color);\n        invalidate();\n    }\n\n    public void setIndicatorColor(int color) {\n        mIndicatorPaint.setColor(color);\n        invalidate();\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        mWidth = w;\n        mHeight = h;\n        mDrawBounds.set(0, 0, mWidth, mHeight);\n    }\n\n    public void setMaxProgress(int progress) {\n        mMaxProgress = progress;\n    }\n\n    public void setIndicatorWidth(float w) {\n        mIndicatorWidth = w;\n        invalidate();\n    }\n\n    public void setRightIncreasing(boolean rightIncreasing) {\n        if (rightIncreasing) {\n            mLeftMostProgress = 0;\n            mRightMostProgress = 0;\n            mProgressOffset = 0;\n            setDirection(DIRECTION_RIGHT);\n        } else {\n            mLeftMostProgress = mWidth;\n            mRightMostProgress = mWidth;\n            mProgressOffset = mWidth;\n            setDirection(DIRECTION_LEFT);\n        }\n        invalidate();\n    }\n\n    public void setProgress(int progress) {\n        // The panning direction will be decided after user pan more\n        // than 10 degrees in one direction.\n        if (mDirection == DIRECTION_NONE) {\n            if (progress > 10) {\n                setRightIncreasing(true);\n            } else if (progress < -10) {\n                setRightIncreasing(false);\n            }\n        }\n        // mDirection might be modified by setRightIncreasing() above. Need to check again.\n        if (mDirection != DIRECTION_NONE) {\n            mProgress = progress * mWidth / mMaxProgress + mProgressOffset;\n            // Value bounds\n            mProgress = Math.min(mWidth, Math.max(0, mProgress));\n            if (mDirection == DIRECTION_RIGHT) {\n                // The right most progress is adjusted.\n                mRightMostProgress = Math.max(mRightMostProgress, mProgress);\n            }\n            if (mDirection == DIRECTION_LEFT) {\n                // The left most progress is adjusted.\n                mLeftMostProgress = Math.min(mLeftMostProgress, mProgress);\n            }\n            invalidate();\n        }\n    }\n\n    public void reset() {\n        mProgress = 0;\n        mProgressOffset = 0;\n        setDirection(DIRECTION_NONE);\n        invalidate();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        // the background\n        canvas.drawRect(mDrawBounds, mBackgroundPaint);\n        if (mDirection != DIRECTION_NONE) {\n            // the progress area\n            canvas.drawRect(mLeftMostProgress, mDrawBounds.top, mRightMostProgress,\n                    mDrawBounds.bottom, mDoneAreaPaint);\n            // the indication bar\n            float l;\n            float r;\n            if (mDirection == DIRECTION_RIGHT) {\n                l = Math.max(mProgress - mIndicatorWidth, 0f);\n                r = mProgress;\n            } else {\n                l = mProgress;\n                r = Math.min(mProgress + mIndicatorWidth, mWidth);\n            }\n            canvas.drawRect(l, mDrawBounds.top, r, mDrawBounds.bottom, mIndicatorPaint);\n        }\n\n        // Draw the mask image on the top for shaping.\n        super.onDraw(canvas);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/PreviewFrameLayout.java",
    "content": "/*\n * Copyright (C) 2009 The Android Open Source Project\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.RelativeLayout;\n\nimport org.cyanogenmod.focal.Util;\n\n/**\n * A layout which handles the preview aspect ratio.\n */\npublic class PreviewFrameLayout extends RelativeLayout {\n\n    public static final String TAG = \"CAM_preview\";\n\n    /**\n     * A callback to be invoked when the preview frame's size changes.\n     */\n    public interface OnSizeChangedListener {\n        public void onSizeChanged(int width, int height);\n    }\n\n    private double mAspectRatio;\n    private int mPreviewWidth;\n    private int mPreviewHeight;\n    private OnSizeChangedListener mListener;\n\n\n    public PreviewFrameLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        setAspectRatio(4.0 / 3.0);\n    }\n\n    public void setAspectRatio(double ratio) {\n        if (ratio <= 0.0) {\n            throw new IllegalArgumentException();\n        }\n\n        ratio = 1 / ratio;\n\n        if (mAspectRatio != ratio) {\n            mAspectRatio = ratio;\n            requestLayout();\n        }\n    }\n\n    public void setPreviewSize(int width, int height) {\n        mPreviewWidth = height;\n        mPreviewHeight = width;\n        requestLayout();\n    }\n\n    @Override\n    protected void onMeasure(int widthSpec, int heightSpec) {\n        // Scale the preview while keeping the aspect ratio\n        int fullWidth = Util.getScreenSize(null).x;\n        int fullHeight = Util.getScreenSize(null).y;\n\n        if (fullWidth == 0 || mPreviewWidth == 0) {\n            setMeasuredDimension(mPreviewWidth, mPreviewHeight);\n            return;\n        }\n\n        setMeasuredDimension(fullWidth, fullHeight);\n\n        // Ask children to follow the new preview dimension.\n        super.onMeasure(MeasureSpec.makeMeasureSpec((int) fullWidth, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec((int) fullHeight, MeasureSpec.EXACTLY));\n    }\n\n    /**\n     * Called when this view should assign a size and position to all of its children.\n     * @param changed\n     * @param l\n     * @param t\n     * @param r\n     * @param b\n     */\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        if(changed && getChildCount() > 0) {\n            final View child = getChildAt(0);\n\n            // Scale the preview while keeping the aspect ratio\n            int fullWidth = Util.getScreenSize(null).x;\n            int fullHeight = Util.getScreenSize(null).y;\n\n            if (fullWidth == 0 || mPreviewWidth == 0) {\n                setMeasuredDimension(fullWidth, fullHeight);\n                return;\n            }\n\n            float ratio = Math.min((float) fullHeight / (float) mPreviewHeight,\n                    (float) fullWidth / (float) mPreviewWidth);\n            float width = mPreviewWidth * ratio;\n            float height = mPreviewHeight * ratio;\n\n            // Center the child SurfaceView within the parent.\n            if (child != null) {\n                Log.v(TAG, \"Layout: (\" + (int) ((fullWidth - width)) / 2 + \", \"\n                        + (int) ((fullHeight - height)) / 2 + \", \" + (int) ((fullWidth\n                        + width)) / 2 + \", \" + (int) ((fullHeight + height)) / 2 + \")\");\n                child.layout((int) ((fullWidth - width)) / 2, (int) ((fullHeight - height))\n                        / 2, (int) ((fullWidth + width)) / 2, (int) ((fullHeight + height)) / 2);\n            }\n        }\n    }\n\n    public void setOnSizeChangedListener(OnSizeChangedListener listener) {\n        mListener = listener;\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        if (mListener != null) mListener.onSizeChanged(w, h);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/ReviewDrawer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.animation.Animator;\nimport android.content.ActivityNotFoundException;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.provider.MediaStore;\nimport android.support.v4.view.ViewPager;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.ImageButton;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class handles the review drawer that can be opened by swiping down\n */\npublic class ReviewDrawer extends RelativeLayout {\n    public final static String TAG = \"ReviewDrawer\";\n\n    private final static long DRAWER_TOGGLE_DURATION = 400;\n    private final static float MIN_REMOVE_THRESHOLD = 10.0f;\n    private final static String GALLERY_CAMERA_BUCKET = \"Camera\";\n\n    private List<Integer> mImages;\n\n    private Handler mHandler;\n    private ImageListAdapter mImagesListAdapter;\n    private int mReviewedImageId;\n    private int mCurrentOrientation;\n    private final Object mImagesLock = new Object();\n    private boolean mIsOpen;\n    private ViewPager mViewPager;\n\n    public ReviewDrawer(Context context) {\n        super(context);\n        initialize();\n    }\n\n    public ReviewDrawer(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public ReviewDrawer(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    private void initialize() {\n        mHandler = new Handler();\n        mImages = new ArrayList<Integer>();\n\n        // Default hidden\n        setAlpha(0.0f);\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                setTranslationY(-getMeasuredHeight());\n            }\n        });\n\n        // Setup the list adapter\n        mImagesListAdapter = new ImageListAdapter();\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n\n        // Load pictures or videos from gallery\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            updateFromGallery(false, 0);\n        } else {\n            updateFromGallery(true, 0);\n        }\n\n        // Make sure drawer is initially closed\n        setTranslationY(-99999);\n\n        ImageButton editImageButton = (ImageButton) findViewById(R.id.button_retouch);\n        editImageButton.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                editInGallery(mReviewedImageId);\n            }\n        });\n\n        ImageButton openImageButton = (ImageButton) findViewById(R.id.button_open_in_gallery);\n        openImageButton.setOnClickListener(new OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                openInGallery(mReviewedImageId);\n            }\n        });\n\n        mViewPager = (ViewPager) findViewById(R.id.reviewed_image);\n        mViewPager.setAdapter(mImagesListAdapter);\n        mViewPager.setPageMargin((int) Util.dpToPx(getContext(), 8));\n        mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());\n        mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n            @Override\n            public void onPageScrolled(int i, float v, int i2) {\n\n            }\n\n            @Override\n            public void onPageSelected(int i) {\n                mReviewedImageId = mImages.get(i);\n            }\n\n            @Override\n            public void onPageScrollStateChanged(int i) {\n\n            }\n        });\n    }\n\n    /**\n     * Clears the list of images and reload it from the Gallery (MediaStore)\n     * This method is threaded!\n     *\n     * @param images True to get images, false to get videos\n     * @param scrollPos Position to scroll to, or 0 to get latest image\n     */\n    public void updateFromGallery(final boolean images, final int scrollPos) {\n        new Thread() {\n            public void run() {\n                updateFromGallerySynchronous(images, scrollPos);\n            }\n        }.start();\n    }\n\n    /**\n     * Clears the list of images and reload it from the Gallery (MediaStore)\n     * This method is synchronous, see updateFromGallery for the threaded one.\n     *\n     * @param images True to get images, false to get videos\n     * @param scrollPos Position to scroll to, or 0 to get latest image\n     */\n    public void updateFromGallerySynchronous(final boolean images, final int scrollPos) {\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                synchronized (mImagesLock) {\n                    mImages.clear();\n                    mImagesListAdapter.notifyDataSetChanged();\n                }\n\n                String[] columns;\n                String orderBy;\n                if (images) {\n                    columns = new String[]{MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID};\n                    orderBy = MediaStore.Images.Media.DATE_TAKEN + \" ASC\";\n                } else {\n                    columns = new String[]{MediaStore.Video.Media.DATA, MediaStore.Video.Media._ID};\n                    orderBy = MediaStore.Video.Media.DATE_TAKEN + \" ASC\";\n                }\n\n                // Select only the images that has been taken from the Camera\n                Context ctx = getContext();\n                if (ctx == null) return;\n                ContentResolver cr = ctx.getContentResolver();\n                if (cr == null) {\n                    Log.e(TAG, \"No content resolver!\");\n                    return;\n                }\n\n                Cursor cursor;\n\n                if (images) {\n                    cursor = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns,\n                            MediaStore.Images.Media.BUCKET_DISPLAY_NAME + \" LIKE ?\",\n                            new String[]{GALLERY_CAMERA_BUCKET}, orderBy);\n                } else {\n                    cursor = cr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, columns,\n                            MediaStore.Video.Media.BUCKET_DISPLAY_NAME + \" LIKE ?\",\n                            new String[]{GALLERY_CAMERA_BUCKET}, orderBy);\n                }\n\n                if (cursor == null) {\n                    Log.e(TAG, \"Null cursor from MediaStore!\");\n                    return;\n                }\n\n                final int imageColumnIndex = cursor.getColumnIndex(images ?\n                        MediaStore.Images.Media._ID : MediaStore.Video.Media._ID);\n\n                for (int i = 0; i < cursor.getCount(); i++) {\n                    cursor.moveToPosition(i);\n\n                    int id = cursor.getInt(imageColumnIndex);\n                    if (mReviewedImageId <= 0) {\n                        mReviewedImageId = id;\n                    }\n                    addImageToList(id);\n                    mImagesListAdapter.notifyDataSetChanged();\n                }\n\n                cursor.close();\n\n                if (scrollPos < mImages.size()) {\n                    mViewPager.setCurrentItem(scrollPos+1, false);\n                    mViewPager.setCurrentItem(scrollPos, true);\n                }\n            }\n        });\n    }\n\n    /**\n     * Queries the Media service for image orientation\n     *\n     * @param id The id of the gallery image\n     * @return The orientation of the image, or 0 if it failed\n     */\n    public int getCameraPhotoOrientation(final int id) {\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            return 0;\n        }\n        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()\n                .appendPath(Integer.toString(id)).build();\n        String[] orientationColumn = new String[] {\n                MediaStore.Images.ImageColumns.ORIENTATION\n        };\n\n        int orientation = 0;\n        Cursor cur = getContext().getContentResolver().query(uri,\n                orientationColumn, null, null, null);\n        if (cur != null && cur.moveToFirst()) {\n            orientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));\n        }\n        if (cur != null) {\n            cur.close();\n            cur = null;\n        }\n        return orientation;\n    }\n\n\n    /**\n     * Add an image at the head of the image ribbon\n     *\n     * @param id The id of the image from the MediaStore\n     */\n    public void addImageToList(final int id) {\n        mImagesListAdapter.addImage(id);\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                mImagesListAdapter.notifyDataSetChanged();\n            }\n        });\n    }\n\n    public void scrollToLatestImage() {\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                mViewPager.setCurrentItem(0);\n            }\n        });\n    }\n\n    public void notifyOrientationChanged(final int orientation) {\n        mCurrentOrientation = orientation;\n    }\n\n    private void openInGallery(final int imageId) {\n        if (imageId > 0) {\n            Uri uri;\n            if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n                uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon()\n                        .appendPath(Integer.toString(imageId)).build();\n            } else {\n                uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()\n                        .appendPath(Integer.toString(imageId)).build();\n            }\n            Intent intent = new Intent(Intent.ACTION_VIEW, uri);\n            try {\n                Context ctx = getContext();\n                if (ctx != null) {\n                    ctx.startActivity(intent);\n                }\n            } catch (ActivityNotFoundException e) {\n                CameraActivity.notify(getContext().getString(R.string.no_video_player), 2000);\n            }\n        }\n    }\n\n    private void editInGallery(final int imageId) {\n        if (imageId > 0) {\n            Intent editIntent = new Intent(Intent.ACTION_EDIT);\n\n            // Get URI\n            Uri uri = null;\n            if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n                uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon()\n                        .appendPath(Integer.toString(imageId)).build();\n            } else {\n                uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()\n                        .appendPath(Integer.toString(imageId)).build();\n            }\n\n            // Start gallery edit activity\n            editIntent.setDataAndType(uri, \"image/*\");\n            editIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            Context ctx = getContext();\n            if (ctx != null) {\n                ctx.startActivity(Intent.createChooser(editIntent, null));\n            }\n        }\n    }\n\n    /**\n     * Sets the review drawer to temporary hide, by reducing alpha to a very low\n     * level\n     *\n     * @param enabled To hide or not to hide, that is the question\n     */\n    public void setTemporaryHide(final boolean enabled) {\n        float alpha = (enabled ? 0.2f : 1.0f);\n        animate().alpha(alpha).setDuration(200).start();\n    }\n\n    /**\n     * Returns whether or not the review drawer is FULLY open (ie. not in\n     * quick review mode)\n     */\n    public boolean isOpen() {\n        return mIsOpen;\n    }\n\n    /**\n     * Normally opens the review drawer (animation)\n     */\n    public void open() {\n        //mReviewedImage.setVisibility(View.VISIBLE);\n        openImpl(1.0f);\n    }\n\n    private void openImpl(final float alpha) {\n        mIsOpen = true;\n        setVisibility(View.VISIBLE);\n        animate().setDuration(DRAWER_TOGGLE_DURATION).setInterpolator(new AccelerateInterpolator())\n                .translationY(0.0f).setListener(null).alpha(alpha).start();\n    }\n\n    /**\n     * Normally closes the review drawer (animation)\n     */\n    public void close() {\n        mIsOpen = false;\n        animate().setDuration(DRAWER_TOGGLE_DURATION).setInterpolator(new DecelerateInterpolator())\n                .translationY(-getMeasuredHeight()).alpha(0.0f)\n                .setListener(new Animator.AnimatorListener() {\n                    @Override\n                    public void onAnimationStart(Animator animator) {\n\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator animator) {\n                        setVisibility(View.GONE);\n                    }\n\n                    @Override\n                    public void onAnimationCancel(Animator animator) {\n\n                    }\n\n                    @Override\n                    public void onAnimationRepeat(Animator animator) {\n\n                    }\n                }).start();\n    }\n\n    /**\n     * Slide the review drawer of the specified distance on the X axis\n     *\n     * @param distance The distance to slide\n     */\n    public void slide(final float distance) {\n        float finalPos = getTranslationY() + distance;\n        if (finalPos > 0) {\n            finalPos = 0;\n        }\n\n        setTranslationY(finalPos);\n\n        if (getAlpha() == 0.0f) {\n            setVisibility(View.VISIBLE);\n            setAlpha(1.0f);\n        }\n    }\n\n    public void clampSliding() {\n        if (getTranslationY() < -getMeasuredHeight() / 2) {\n            close();\n        } else {\n            openImpl(getAlpha());\n        }\n    }\n\n    /**\n     * Removes the currently reviewed image from the\n     * internal memory.\n     */\n    public void removeReviewedImage() {\n        Util.removeFromGallery(getContext().getContentResolver(), mReviewedImageId);\n        int position = mViewPager.getCurrentItem();\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            updateFromGallery(false, position);\n        } else {\n            updateFromGallery(true, position);\n        }\n\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                if (mImages.size() > 0) {\n                    int imageId = mImages.get(0);\n                    mReviewedImageId = imageId;\n                }\n            }\n        }, 300);\n        // XXX: Undo popup\n    }\n\n    /**\n     * Open the drawer in Quick Review mode\n     */\n    public void openQuickReview() {\n        openImpl(0.5f);\n    }\n\n    /**\n     * Adapter responsible for showing the images in the list of the review drawer\n     */\n    private class ImageListAdapter extends android.support.v4.view.PagerAdapter {\n        private Map<ImageView, Integer> mViewsToId;\n\n        public ImageListAdapter() {\n            mViewsToId = new HashMap<ImageView, Integer>();\n        }\n\n        public void addImage(int id) {\n            synchronized (mImagesLock) {\n                mImages.add(0, id);\n            }\n        }\n\n        @Override\n        public int getItemPosition(Object object) {\n            ImageView img = (ImageView) object;\n\n            if (mImages.indexOf(mViewsToId.get(img)) >= 0) {\n                return mImages.indexOf(mViewsToId.get(img));\n            } else {\n                return POSITION_NONE;\n            }\n        }\n\n        @Override\n        public int getCount() {\n            return mImages.size();\n        }\n\n        @Override\n        public boolean isViewFromObject(View view, Object o) {\n            return view == o;\n        }\n\n        @Override\n        public Object instantiateItem(ViewGroup container, final int position) {\n            final ImageView imageView = new ImageView(getContext());\n            container.addView(imageView);\n\n            ViewGroup.LayoutParams params = imageView.getLayoutParams();\n            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;\n            params.height = ViewGroup.LayoutParams.WRAP_CONTENT;\n            imageView.setLayoutParams(params);\n\n            imageView.setOnTouchListener(new ThumbnailTouchListener(imageView));\n\n            new Thread() {\n                public void run() {\n                    int imageId = -1;\n                    synchronized (mImagesLock) {\n                        try {\n                            imageId = mImages.get(position);\n                        } catch (IndexOutOfBoundsException e) {\n                            return;\n                        }\n                    }\n\n                    mViewsToId.put(imageView, imageId);\n                    final Bitmap thumbnail = CameraActivity.getCameraMode() ==\n                            CameraActivity.CAMERA_MODE_VIDEO ? (MediaStore.Video.Thumbnails\n                            .getThumbnail(getContext().getContentResolver(),\n                                    mImages.get(position), MediaStore.Video.Thumbnails.MINI_KIND, null))\n                            : (MediaStore.Images.Thumbnails.getThumbnail(getContext()\n                            .getContentResolver(), imageId,\n                            MediaStore.Images.Thumbnails.MINI_KIND, null));\n\n                    final int rotation = getCameraPhotoOrientation(imageId);\n                    mHandler.post(new Runnable() {\n                        @Override\n                        public void run() {\n                            imageView.setImageBitmap(thumbnail);\n                            imageView.setRotation(rotation);\n                        }\n                    });\n                }\n            }.start();\n\n            return imageView;\n        }\n\n        @Override\n        public void destroyItem(ViewGroup container, int position, Object object) {\n            // Remove viewpager_item.xml from ViewPager\n            container.removeView((ImageView) object);\n\n        }\n    }\n\n    /**\n     * Pager transition animation\n     */\n    public class ZoomOutPageTransformer implements ViewPager.PageTransformer {\n        private final float MIN_SCALE = 0.85f;\n        private final float MIN_ALPHA = 0.5f;\n\n        public void transformPage(View view, float position) {\n            int pageWidth = view.getWidth();\n            int pageHeight = view.getHeight();\n\n            if (position < -1) { // [-Infinity,-1)\n                // This page is way off-screen to the left.\n                view.setAlpha(0);\n\n            } else if (position <= 1) { // [-1,1]\n                // Modify the default slide transition to shrink the page as well\n                float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));\n                float vertMargin = pageHeight * (1 - scaleFactor) / 2;\n                float horzMargin = pageWidth * (1 - scaleFactor) / 2;\n                if (position < 0) {\n                    view.setTranslationX(horzMargin - vertMargin / 2);\n                } else {\n                    view.setTranslationX(-horzMargin + vertMargin / 2);\n                }\n\n                // Scale the page down (between MIN_SCALE and 1)\n                view.setScaleX(scaleFactor);\n                view.setScaleY(scaleFactor);\n\n                // Fade the page relative to its size.\n                view.setAlpha(MIN_ALPHA +\n                        (scaleFactor - MIN_SCALE) /\n                                (1 - MIN_SCALE) * (1 - MIN_ALPHA));\n\n            } else { // (1,+Infinity]\n                // This page is way off-screen to the right.\n                view.setAlpha(0);\n            }\n        }\n    }\n\n    public class ThumbnailTouchListener implements OnTouchListener {\n        private final GestureDetector mGestureDetector;\n        private ImageView mImageView;\n        private GestureDetector.SimpleOnGestureListener mListener =\n                new GestureDetector.SimpleOnGestureListener() {\n                    private final float DRIFT_THRESHOLD = 80.0f;\n                    private final int SWIPE_THRESHOLD_VELOCITY = 800;\n\n                    @Override\n                    public boolean onDown(MotionEvent motionEvent) {\n                        return true;\n                    }\n\n                    @Override\n                    public void onShowPress(MotionEvent motionEvent) {\n\n                    }\n\n                    @Override\n                    public boolean onSingleTapUp(MotionEvent motionEvent) {\n                        return false;\n                    }\n\n                    @Override\n                    public boolean onScroll(MotionEvent ev1, MotionEvent ev2, float vX, float vY) {\n                        if (Math.abs(ev2.getX() - ev1.getX()) > DRIFT_THRESHOLD\n                                && Math.abs(mImageView.getTranslationY()) < MIN_REMOVE_THRESHOLD) {\n                            return false;\n                        }\n\n                        mImageView.setTranslationY(ev2.getRawY() - ev1.getRawY());\n                        float alpha = Math.max(0.0f, 1.0f - Math.abs(mImageView.getTranslationY()\n                                / mImageView.getMeasuredHeight())*2.0f);\n                        mImageView.setAlpha(alpha);\n\n                        return true;\n                    }\n\n                    @Override\n                    public void onLongPress(MotionEvent motionEvent) {\n\n                    }\n\n                    @Override\n                    public boolean onFling(MotionEvent ev1, MotionEvent ev2, float vX, float vY) {\n                        if (Math.abs(ev2.getX() - ev1.getX()) > DRIFT_THRESHOLD) {\n                            return false;\n                        }\n\n                        if (Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {\n                            mImageView.animate().translationY(-mImageView.getHeight()).alpha(0.0f)\n                                    .setDuration(300).start();\n                            removeReviewedImage();\n                            return true;\n                        }\n\n                        return false;\n                    }\n                };\n\n        public ThumbnailTouchListener(ImageView iv) {\n            mImageView = iv;\n            mGestureDetector = new GestureDetector(getContext(), mListener);\n        }\n\n        @Override\n        public boolean onTouch(View view, MotionEvent motionEvent) {\n            if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {\n                if (mImageView.getTranslationY() > mImageView.getMeasuredHeight()*0.5f) {\n                    mImageView.animate().translationY(-mImageView.getHeight()).alpha(0.0f)\n                            .setDuration(300).start();\n                    mHandler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            mImageView.setTranslationY(0.0f);\n                            mImageView.setTranslationX(mImageView.getWidth());\n                            mImageView.animate().translationX(0.0f).alpha(1.0f).start();\n                        }\n                    }, 400);\n                    removeReviewedImage();\n                } else {\n                    mImageView.animate().translationY(0).alpha(1.0f)\n                            .setDuration(300).start();\n                }\n            }\n\n            return mGestureDetector.onTouchEvent(motionEvent);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/RuleOfThirds.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n/**\n * Shows Rule of Thirds helper lines on the screen\n */\npublic class RuleOfThirds extends View {\n    private Paint mPaint;\n\n    public RuleOfThirds(Context context) {\n        super(context);\n    }\n\n    public RuleOfThirds(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public RuleOfThirds(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (mPaint == null) {\n            mPaint = new Paint();\n        }\n\n        mPaint.setARGB(255, 255, 255, 255);\n\n        int width = getMeasuredWidth();\n        int height = getMeasuredHeight();\n\n        canvas.drawLine(width / 3, 0, width / 3, height, mPaint);\n        canvas.drawLine(width * 2 / 3, 0, width * 2 / 3, height, mPaint);\n        canvas.drawLine(0, height / 3, width, height / 3, mPaint);\n        canvas.drawLine(0, height * 2 / 3, width, height * 2 / 3, mPaint);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/SavePinger.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.animation.ValueAnimator;\nimport android.animation.ValueAnimator.AnimatorUpdateListener;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport fr.xplod.focal.R;\n\npublic class SavePinger extends View {\n    public final static String TAG = \"SavePinger\";\n\n    // Just the circles pulsing\n    public final static int PING_MODE_SIMPLE = 0;\n    // Circles pulsing + save icon\n    public final static int PING_MODE_SAVE = 1;\n    // Circles pulsing + enhancer icon\n    public final static int PING_MODE_ENHANCER = 2;\n\n    private ValueAnimator mFadeAnimator;\n    private ValueAnimator mConstantAnimator;\n    private float mFadeProgress;\n    private Paint mPaint;\n    private long mRingTime[] = new long[CIRCLES_COUNT];\n    private long mLastTime;\n    private Bitmap mSaveIcon;\n    private Bitmap mEnhanceIcon;\n    private int mOrientation;\n    private int mPingMode;\n\n    private final static int CIRCLES_COUNT = 3;\n    private float mRingRadius;\n\n    public SavePinger(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    public SavePinger(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public SavePinger(Context context) {\n        super(context);\n        initialize();\n    }\n\n    private void initialize() {\n        mPaint = new Paint();\n        setLayerType(LAYER_TYPE_SOFTWARE, mPaint);\n        mPaint.setShadowLayer(2.0f, 0.0f, 0.0f, 0xFF444444);\n\n        // start hidden\n        mFadeProgress = 0.0f;\n\n        mSaveIcon = ((BitmapDrawable) getResources()\n                .getDrawable(R.drawable.ic_save)).getBitmap();\n        mEnhanceIcon = ((BitmapDrawable) getResources()\n                .getDrawable(R.drawable.ic_enhancing)).getBitmap();\n\n        mFadeAnimator = new ValueAnimator();\n        mFadeAnimator.setDuration(1500);\n        mFadeAnimator.addUpdateListener(new AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator arg0) {\n                mFadeProgress = (Float) arg0.getAnimatedValue();\n                invalidate();\n            }\n        });\n\n        mConstantAnimator = new ValueAnimator();\n        mConstantAnimator.setDuration(1000);\n        mConstantAnimator.setRepeatMode(ValueAnimator.INFINITE);\n        mConstantAnimator.addUpdateListener(new AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator arg0) {\n                invalidate();\n            }\n        });\n        mConstantAnimator.setFloatValues(0, 1);\n        mConstantAnimator.start();\n\n        mLastTime = System.currentTimeMillis();\n\n        for (int i = 0; i < CIRCLES_COUNT; i++) {\n            mRingTime[i] = i * -500;\n        }\n    }\n\n    public void setPingMode(int mode) {\n        mPingMode = mode;\n    }\n\n    public void startSaving() {\n        mFadeAnimator.setFloatValues(0, 1);\n        mFadeAnimator.start();\n    }\n\n    public void stopSaving() {\n        mFadeAnimator.setFloatValues(1, 0);\n        mFadeAnimator.start();\n    }\n\n    public void notifyOrientationChanged(int angle) {\n        mOrientation = angle;\n    }\n\n    @Override\n    public void onDraw(Canvas canvas) {\n        mRingRadius = getWidth() * 0.5f;\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setStrokeWidth(8.0f);\n\n        long systemTime = System.currentTimeMillis();\n        long deltaMs = systemTime - mLastTime;\n\n        for (int i = 0; i < CIRCLES_COUNT; i++) {\n            mRingTime[i] += deltaMs * 0.2f;\n\n            if (mRingTime[i] < 0) continue;\n\n            float circleValue = mRingTime[i] / 255.0f;\n            float ringProgress = circleValue * mRingRadius;\n\n            if (circleValue > 1) circleValue = 1;\n\n            mPaint.setARGB((int) ((255.0f - 255.0f * circleValue) * mFadeProgress * 0.5f),\n                    51, 181, 229);\n\n            canvas.drawCircle(getWidth() / 2, getHeight() / 2, ringProgress, mPaint);\n\n            if (circleValue == 1) {\n                mRingTime[i] = 0;\n            }\n        }\n\n        if (mPingMode != PING_MODE_SIMPLE) {\n            int alpha = (int) (((Math.cos((double) systemTime / 200.0) + 1.0f) / 2.0f) * 255.0f);\n            canvas.save();\n            canvas.translate(getWidth() / 2, getHeight() / 2);\n            canvas.rotate(mOrientation);\n            mPaint.setARGB((int) (alpha * mFadeProgress), 255, 255, 255);\n\n            if (mPingMode == PING_MODE_SAVE) {\n                canvas.drawBitmap(mSaveIcon, -mSaveIcon.getWidth() / 2,\n                        -mSaveIcon.getHeight() / 2, mPaint);\n            } else if (mPingMode == PING_MODE_ENHANCER) {\n                canvas.drawBitmap(mEnhanceIcon, -mEnhanceIcon.getWidth() / 2,\n                        -mEnhanceIcon.getHeight() / 2, mPaint);\n            }\n\n            canvas.restore();\n        }\n\n        mLastTime = systemTime;\n        if (getAlpha() > 0.0) {\n            invalidate();\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/ShutterButton.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.widget.ImageView;\n\npublic class ShutterButton extends ImageView {\n    public final static String TAG = \"ShutterButton\";\n    private boolean mSlideOpen = false;\n\n    /**\n     * Interface that notifies the CameraActivity that\n     * the shutter button has been slided (and should show the\n     * pad ring), or that there is a motionevent handled by this\n     * View that should belong to the SwitchRingPad\n     */\n    public interface ShutterSlideListener {\n        public void onSlideOpen();\n        public void onSlideClose();\n        public void onShutterButtonPressed();\n        public boolean onMotionEvent(MotionEvent ev);\n    }\n\n\n    private float mDownX;\n    private float mDownY;\n    private ShutterSlideListener mListener;\n\n    public ShutterButton(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    public ShutterButton(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public ShutterButton(Context context) {\n        super(context);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {\n            mDownX = event.getRawX();\n            mDownY = event.getRawY();\n            mListener.onShutterButtonPressed();\n        } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {\n            if ((event.getRawY() - mDownY < -getHeight() / 2 || Math.abs(event.getRawX()\n                    - mDownX) > getWidth() / 2) && mListener != null) {\n                if (!mSlideOpen) {\n                    mListener.onSlideOpen();\n                    mSlideOpen = true;\n                }\n            } else {\n                if (mSlideOpen) {\n                    mListener.onSlideClose();\n                    mSlideOpen = false;\n                }\n            }\n        }\n\n        boolean listenerResult = false;\n        if (mListener != null && mSlideOpen) {\n            listenerResult = mListener.onMotionEvent(event);\n        }\n\n        return (super.onTouchEvent(event) || listenerResult);\n    }\n\n    public void setSlideListener(ShutterSlideListener listener) {\n        mListener = listener;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/SideBar.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.hardware.Camera;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.HorizontalScrollView;\nimport android.widget.ScrollView;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.CameraCapabilities;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.widgets.WidgetBase;\n\npublic class SideBar extends ScrollView {\n    public final static String TAG = \"SideBar\";\n    public final static int SLIDE_ANIMATION_DURATION_MS = 300;\n    private final static float BAR_MARGIN = 0;\n    private CameraCapabilities mCapabilities;\n    private ViewGroup mToggleContainer;\n    private boolean mIsOpen;\n\n\n    public SideBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    public SideBar(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public SideBar(Context context) {\n        super(context);\n        initialize();\n    }\n\n    /**\n     * Setup the bar\n     */\n    private void initialize() {\n        this.setBackgroundColor(getResources().getColor(R.color.widget_background));\n        mIsOpen = true;\n    }\n\n\n    /**\n     * Check the capabilities of the device, and populate the sidebar\n     * with the toggle buttons.\n     */\n    public void checkCapabilities(CameraActivity activity, ViewGroup widgetsContainer) {\n        mToggleContainer = (ViewGroup) this.getChildAt(0);\n        ViewGroup shortcutsContainer = (ViewGroup) activity.findViewById(R.id.shortcuts_container);\n\n        if (mCapabilities != null) {\n            mToggleContainer.removeAllViews();\n            shortcutsContainer.removeAllViews();\n            mCapabilities = null;\n        }\n\n        mCapabilities = new CameraCapabilities(activity);\n        Camera.Parameters params = activity.getCamManager().getParameters();\n\n        if (params != null) {\n            mCapabilities.populateSidebar(params, mToggleContainer,\n                    shortcutsContainer, widgetsContainer);\n        } else {\n            Log.e(TAG, \"Parameters were null when capabilities were checked\");\n        }\n    }\n\n    /**\n     * Notify the bar of the rotation\n     *\n     * @param target Target orientation\n     */\n    public void notifyOrientationChanged(float target) {\n        mToggleContainer = (ViewGroup) this.getChildAt(0);\n        int buttonsCount = mToggleContainer.getChildCount();\n\n        for (int i = 0; i < buttonsCount; i++) {\n            View child = mToggleContainer.getChildAt(i);\n\n            child.animate().rotation(target)\n                    .setDuration(200).setInterpolator(new DecelerateInterpolator()).start();\n        }\n    }\n\n    /**\n     * Slides the sidebar off the provided distance, and clamp it at either\n     * sides of the screen (out/in)\n     *\n     * @param distance The distance to translate the bar\n     */\n    public void slide(float distance) {\n        float finalX = this.getTranslationX() + distance;\n\n        if (finalX > 0)\n            finalX = 0;\n        else if (finalX < -getWidth())\n            finalX = -getWidth();\n\n        this.setTranslationX(finalX);\n    }\n\n    /**\n     * Clamp the sliding position of the bar. Basically, if the\n     * bar is half-visible, it will animate it in its visible position,\n     * and vice-versa.\n     */\n    public void clampSliding() {\n        if (this.getTranslationX() < 0) {\n            slideClose();\n        } else {\n            slideOpen();\n        }\n    }\n\n    /**\n     * @return Whether or not the sidebar is open\n     */\n    public boolean isOpen() {\n        return mIsOpen;\n    }\n\n    /**\n     * Smoothly close the sidebar\n     */\n    public void slideClose() {\n        this.animate().translationX(-this.getWidth())\n                .setDuration(SLIDE_ANIMATION_DURATION_MS).start();\n\n        mIsOpen = false;\n    }\n\n    /**\n     * Smoothly open the sidebar\n     */\n    public void slideOpen() {\n        this.animate().translationX(0)\n                .setDuration(SLIDE_ANIMATION_DURATION_MS).start();\n\n        mIsOpen = true;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/SwitchRingPad.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.animation.ValueAnimator;\nimport android.animation.ValueAnimator.AnimatorUpdateListener;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Point;\nimport android.graphics.RectF;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.animation.AccelerateDecelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\n\npublic class SwitchRingPad extends View implements AnimatorUpdateListener {\n    public interface RingPadListener {\n        public void onButtonActivated(int eventId);\n    }\n\n    private float mEdgePadding;\n    private int mButtonSize;\n    private float mRingRadius;\n\n    public final static int BUTTON_CAMERA = 1;\n    public final static int BUTTON_VIDEO = 2;\n    public final static int BUTTON_PANO = 3;\n    public final static int BUTTON_PICSPHERE = 4;\n    public final static int BUTTON_SWITCHCAM = 5;\n\n    private final static int SLOT_RIGHT = 4;\n    private final static int SLOT_MIDRIGHT = 3;\n    private final static int SLOT_MID = 2;\n    private final static int SLOT_MIDLEFT = 1;\n    private final static int SLOT_LEFT = 0;\n    private final static int SLOT_MAX = 5;\n\n    private final static int RING_ANIMATION_DURATION_MS = 150;\n\n    private PadButton[] mButtons;\n    private Paint mPaint;\n    private ValueAnimator mAnimator;\n    private float mOpenProgress;\n    private boolean mIsOpen;\n    public float mTargetOrientation;\n    public float mCurrentOrientation;\n    private RingPadListener mListener;\n    private float mHintProgress;\n    private ValueAnimator mHintAnimator;\n\n    private class PadButton {\n        public Bitmap mNormalBitmap;\n        public Bitmap mHoverBitmap;\n        public boolean mIsHovering;\n        public int mEventId;\n        public float mLastDrawnX;\n        public float mLastDrawnY;\n        public String mHintText;\n        public float mHintTextAlpha = 0.0f;\n        public boolean mHintAnimationDirection = false;\n    }\n\n    public SwitchRingPad(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    public SwitchRingPad(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public SwitchRingPad(Context context) {\n        super(context);\n        initialize();\n    }\n\n    private Bitmap getDrawable(int resId) {\n        Bitmap bmp = ((BitmapDrawable) getResources().getDrawable(resId)).getBitmap();\n        mButtonSize = bmp.getWidth();\n        return bmp;\n    }\n\n    public void animateHint() {\n        mHintAnimator = new ValueAnimator();\n        mHintAnimator.setDuration(1500);\n        mHintAnimator.setFloatValues(0, 1);\n        mHintAnimator.setStartDelay(500);\n        mHintAnimator.addUpdateListener(new AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator arg0) {\n                mHintProgress = (Float) arg0.getAnimatedValue();\n                invalidate();\n            }\n        });\n        mHintAnimator.start();\n    }\n\n    private void initialize() {\n        mIsOpen = false;\n        mPaint = new Paint();\n\n        animateHint();\n\n        mAnimator = new ValueAnimator();\n        mAnimator.setDuration(RING_ANIMATION_DURATION_MS);\n        mAnimator.setInterpolator(new DecelerateInterpolator());\n        mAnimator.addUpdateListener(this);\n\n        mEdgePadding = getResources().getDimension(R.dimen.ringpad_edge_spacing);\n        mRingRadius = getResources().getDimension(R.dimen.ringpad_radius);\n\n        mButtons = new PadButton[SLOT_MAX];\n\n        // Camera pad button\n        Resources res = getResources();\n        addRingPad(getDrawable(R.drawable.btn_ring_camera_normal),\n                getDrawable(R.drawable.btn_ring_camera_hover),\n                BUTTON_CAMERA, SLOT_LEFT, res.getString(R.string.mode_photo));\n\n        // Panorama pad button\n        addRingPad(getDrawable(R.drawable.btn_ring_pano_normal),\n                getDrawable(R.drawable.btn_ring_pano_hover),\n                BUTTON_PANO, SLOT_MIDLEFT, res.getString(R.string.mode_panorama));\n\n        // Video pad button\n        addRingPad(getDrawable(R.drawable.btn_ring_video_normal),\n                getDrawable(R.drawable.btn_ring_video_hover),\n                BUTTON_VIDEO, SLOT_MID, res.getString(R.string.mode_video));\n\n        // PictureSphere pad button\n        addRingPad(getDrawable(R.drawable.btn_ring_picsphere_normal),\n                getDrawable(R.drawable.btn_ring_picsphere_hover),\n                BUTTON_PICSPHERE, SLOT_MIDRIGHT, res.getString(R.string.mode_picsphere));\n\n        // Switch Cam pad button\n        addRingPad(getDrawable(R.drawable.btn_ring_switchcam_normal),\n                getDrawable(R.drawable.btn_ring_switchcam_hover),\n                BUTTON_SWITCHCAM, SLOT_RIGHT, res.getString(R.string.mode_switchcam));\n    }\n\n    public void setListener(RingPadListener listener) {\n        mListener = listener;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (mOpenProgress == 0 && mHintProgress == 1)\n            return;\n\n        if (mPaint == null) {\n            mPaint = new Paint();\n        }\n\n        // Get the size dimensions regardless of orientation\n        final Point screenSize = Util.getScreenSize(null);\n\n        final int width = Math.min(screenSize.x, screenSize.y);\n        final int height = Math.max(screenSize.x, screenSize.y);\n\n        // Draw the ping animation\n        final float buttonOffset = Util.dpToPx(getContext(), 12);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setStrokeWidth(4.0f);\n        mPaint.setARGB((int) (255.0f - 255.0f * mHintProgress), 255, 255, 255);\n        canvas.drawCircle(width / 2, height - mEdgePadding + buttonOffset, mHintProgress\n                * mRingRadius, mPaint);\n        canvas.drawCircle(width / 2, height - mEdgePadding + buttonOffset, mHintProgress\n                * mRingRadius * 0.66f, mPaint);\n        canvas.drawCircle(width / 2, height - mEdgePadding + buttonOffset, mHintProgress\n                * mRingRadius * 0.33f, mPaint);\n\n        final float ringRadius = mRingRadius * mOpenProgress;\n\n        // Draw the inner circle (dark)\n        mPaint.setStyle(Paint.Style.FILL);\n        mPaint.setColor(0x88888888);\n        canvas.drawCircle(width / 2, height - mEdgePadding, ringRadius, mPaint);\n\n        // Draw the outline stroke\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setStrokeWidth(4.0f);\n        mPaint.setColor(0x88DDDDDD);\n        canvas.drawCircle(width / 2, height - mEdgePadding, ringRadius, mPaint);\n\n        // Draw the actual pad buttons\n        for (int i = 0; i < SLOT_MAX; i++) {\n            mPaint.setAlpha((int) (255.0f * mOpenProgress));\n\n            PadButton button = mButtons[i];\n            if (button == null) continue;\n\n            final float radAngle = (float) ((i * (180.0f / 4.0f) + 90.0f) * Math.PI / 180.0f);\n\n            final float y = (float) (height + ringRadius * Math.cos(radAngle) - mButtonSize);\n            // We remove the button edge\n            final float x = (float) (width / 2 - button.mNormalBitmap.getWidth() / 2\n                    - ringRadius * Math.sin(radAngle));\n\n            canvas.save();\n            canvas.translate(x + button.mNormalBitmap.getWidth() / 2,\n                    y + button.mNormalBitmap.getWidth() / 2);\n            canvas.rotate(mCurrentOrientation);\n\n            if (button.mIsHovering) {\n                canvas.drawBitmap(button.mHoverBitmap, -button.mNormalBitmap.getWidth() / 2,\n                        -button.mNormalBitmap.getWidth() / 2, mPaint);\n            } else {\n                canvas.drawBitmap(button.mNormalBitmap, -button.mNormalBitmap.getWidth() / 2,\n                        -button.mNormalBitmap.getWidth() / 2, mPaint);\n            }\n            canvas.restore();\n\n            if (mOpenProgress == 1.0f) {\n                animateAlpha(button.mIsHovering, button);\n            } else {\n                animateAlpha(false, button);\n            }\n\n            if ((button.mIsHovering || button.mHintTextAlpha > 0.0f) && mOpenProgress == 1.0f) {\n                // Draw hint text\n                int alpha = (int) (255 * (button.mHintTextAlpha));\n                mPaint.setStyle(Paint.Style.FILL);\n\n                mPaint.setTextSize(36);\n                mPaint.setTextAlign(Paint.Align.CENTER);\n                mPaint.setShadowLayer(12, 0, 0, 0xEE333333 * ((alpha & 0xFF) << 24));\n                mPaint.setAlpha(alpha);\n\n                float measureText = mPaint.measureText(button.mHintText);\n\n                canvas.save();\n                canvas.translate(x + button.mNormalBitmap.getWidth() / 2 - mPaint.getTextSize() / 2,\n                        y - measureText / 2 - Util.dpToPx(getContext(), 4));\n                canvas.rotate(mCurrentOrientation);\n\n                canvas.drawText(button.mHintText, 0, 0, mPaint);\n\n                canvas.restore();\n            }\n\n            button.mLastDrawnX = x;\n            button.mLastDrawnY = y;\n        }\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (!mIsOpen) return false;\n\n        if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {\n            for (int i = 0; i < SLOT_MAX; i++) {\n                PadButton button = mButtons[i];\n                if (button == null) {\n                    continue;\n                }\n\n                RectF btnRect = new RectF(button.mLastDrawnX, button.mLastDrawnY,\n                        button.mLastDrawnX + button.mNormalBitmap.getWidth(),\n                        button.mLastDrawnY + button.mNormalBitmap.getHeight());\n\n                if (btnRect.contains(event.getRawX(), event.getRawY())) {\n                    button.mIsHovering = true;\n                } else {\n                    button.mIsHovering = false;\n                }\n            }\n        } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {\n            animateClose();\n\n            for (int i = 0; i < SLOT_MAX; i++) {\n                PadButton button = mButtons[i];\n                if (button == null) continue;\n\n                if (button.mIsHovering && mListener != null) {\n                    mListener.onButtonActivated(button.mEventId);\n                }\n            }\n            return false;\n        }\n\n        invalidate();\n        return super.onTouchEvent(event);\n    }\n\n    private void animateAlpha(boolean in, final PadButton button) {\n        if (button.mHintAnimationDirection == in) {\n            return;\n        }\n\n        button.mHintAnimationDirection = in;\n        ValueAnimator anim = new ValueAnimator();\n        anim.setDuration(200);\n        anim.setFloatValues(in ? 0 : 1, in ? 1 : 0);\n        anim.addUpdateListener(new AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator arg0) {\n                button.mHintTextAlpha = (Float) arg0.getAnimatedValue();\n                invalidate();\n            }\n        });\n        anim.setInterpolator(new AccelerateDecelerateInterpolator());\n        anim.start();\n    }\n\n    public void notifyOrientationChanged(float orientation) {\n        mTargetOrientation = orientation;\n        ValueAnimator anim = new ValueAnimator();\n        anim.setDuration(200);\n        anim.setFloatValues(mCurrentOrientation, orientation);\n        anim.addUpdateListener(new AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator arg0) {\n                mCurrentOrientation = (Float) arg0.getAnimatedValue();\n                invalidate();\n            }\n        });\n        anim.setInterpolator(new AccelerateDecelerateInterpolator());\n        anim.start();\n    }\n\n    public boolean isOpen() {\n        return mIsOpen;\n    }\n\n    public void animateOpen() {\n        if (mIsOpen) {\n            return;\n        }\n\n        mAnimator.cancel();\n        mAnimator.setFloatValues(0, 1);\n        mAnimator.start();\n\n        mIsOpen = true;\n    }\n\n    public void animateClose() {\n        if (!mIsOpen) {\n            return;\n        }\n\n        mAnimator.cancel();\n        mAnimator.setFloatValues(1, 0);\n        mAnimator.start();\n\n        mIsOpen = false;\n    }\n\n    public void addRingPad(Bitmap iconNormal, Bitmap iconHover,\n            int eventId, int slot, String hint) {\n        mButtons[slot] = new PadButton();\n        mButtons[slot].mNormalBitmap = iconNormal;\n        mButtons[slot].mHoverBitmap = iconHover;\n        mButtons[slot].mEventId = eventId;\n        mButtons[slot].mHintText = hint;\n    }\n\n    @Override\n    public void onAnimationUpdate(ValueAnimator animator) {\n        mOpenProgress = (Float) animator.getAnimatedValue();\n        invalidate();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/ThumbnailFlinger.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.animation.Animator;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.ViewGroup;\nimport android.view.animation.AccelerateInterpolator;\nimport android.widget.ImageView;\n\n/**\n * View designed to show and fade the preview of a snapshot\n * after it was taken.\n */\npublic class ThumbnailFlinger extends ImageView {\n    private final static int FADE_IN_DURATION_MS = 250;\n    private final static int FADE_OUT_DURATION_MS = 250;\n\n    public ThumbnailFlinger(Context context) {\n        super(context);\n    }\n\n    public ThumbnailFlinger(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public ThumbnailFlinger(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    public void doAnimation() {\n        // Setup original values\n        this.clearAnimation();\n\n        setScaleX(0.8f);\n        setScaleY(0.8f);\n        setAlpha(0.0f);\n        setTranslationX(0.0f);\n        setTranslationY(0.0f);\n\n        // First step of animation: fade in quick\n        animate().alpha(1.0f).scaleX(1.0f).scaleY(1.0f).setInterpolator(\n                new AccelerateInterpolator()).setDuration(FADE_IN_DURATION_MS)\n                .setListener(new Animator.AnimatorListener() {\n            @Override\n            public void onAnimationStart(Animator animator) {\n\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animator) {\n                // Second step: fade out and scale and slide\n                animate().alpha(0.0f).scaleY(0.7f).scaleX(0.7f).translationYBy(\n                        -getHeight()*1.5f).setStartDelay(100).setDuration(\n                        FADE_OUT_DURATION_MS).setListener(\n                        new Animator.AnimatorListener() {\n                    @Override\n                    public void onAnimationStart(Animator animator) {\n\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator animator) {\n                        ((ViewGroup) getParent()).removeView(ThumbnailFlinger.this);\n                    }\n\n                    @Override\n                    public void onAnimationCancel(Animator animator) {\n\n                    }\n\n                    @Override\n                    public void onAnimationRepeat(Animator animator) {\n\n                    }\n                }).start();\n            }\n\n            @Override\n            public void onAnimationCancel(Animator animator) {\n\n            }\n\n            @Override\n            public void onAnimationRepeat(Animator animator) {\n\n            }\n        }).start();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/WidgetRenderer.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.FrameLayout;\n\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.widgets.WidgetBase;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class WidgetRenderer extends FrameLayout {\n    public final static String TAG = \"WidgetRenderer\";\n\n    private static float WIDGETS_MARGIN;\n\n    private List<WidgetBase.WidgetContainer> mOpenWidgets;\n    private float mTotalHeight;\n    private float mSpacing;\n    private int mOrientation;\n    private float mWidgetDragStartPoint;\n    private boolean mIsHidden;\n\n    public WidgetRenderer(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initialize();\n    }\n\n    public WidgetRenderer(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initialize();\n    }\n\n    public WidgetRenderer(Context context) {\n        super(context);\n        initialize();\n    }\n\n    private void initialize() {\n        mOpenWidgets = new ArrayList<WidgetBase.WidgetContainer>();\n        WIDGETS_MARGIN = Util.dpToPx(getContext(), 32);\n        mTotalHeight = WIDGETS_MARGIN;\n        mSpacing = getResources().getDimension(R.dimen.widget_spacing);\n    }\n\n    /**\n     * Rotate the contents of open widgets\n     *\n     * @param orientation\n     */\n    public void notifyOrientationChanged(int orientation) {\n        mOrientation = orientation;\n\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            mOpenWidgets.get(i).notifyOrientationChanged(orientation, false);\n        }\n    }\n\n    /**\n     * Notifies the renderer a widget has been pressed.\n     *\n     * @param widget The widget that has been pressed\n     */\n    public void widgetPressed(WidgetBase.WidgetContainer widget) {\n        mWidgetDragStartPoint = widget.getFinalY();\n    }\n\n    /**\n     * Notifies the renderer a widget has been moved. The renderer\n     * will then move the other widgets accordingly if needed.\n     *\n     * @param widget The widget that has been moved\n     */\n    public void widgetMoved(WidgetBase.WidgetContainer widget) {\n        // Don't move widget if it was just a small tap\n        if (Math.abs(widget.getY() - mWidgetDragStartPoint) < 40.0f) return;\n        if (mOpenWidgets.size() == 0) return;\n\n        boolean isFirst = (mOpenWidgets.get(0) == widget);\n\n        // Check if we overlap the top of a widget\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            WidgetBase.WidgetContainer tested = mOpenWidgets.get(i);\n\n            if (tested == widget) {\n                continue;\n            }\n\n            if (widget.getY() < tested.getFinalY() + tested.getMeasuredHeight()) {\n                // Don't try to go before the first if we're already it\n                if (isFirst && widget.getY() + widget.getHeight() < tested.getFinalY()\n                        - tested.getHeight() / 2)\n                    break;\n\n                // Move the widget in our list\n                mOpenWidgets.remove(widget);\n                mOpenWidgets.add(i, widget);\n\n                reorderWidgets(widget);\n                break;\n            }\n        }\n    }\n\n    /**\n     * Reorder the widgets and clamp their position after a widget\n     * has been dropped.\n     *\n     * @param widget\n     */\n    public void widgetDropped(WidgetBase.WidgetContainer widget) {\n        reorderWidgets(null);\n    }\n\n    public void reorderWidgets(WidgetBase.WidgetContainer ignore) {\n        mTotalHeight = WIDGETS_MARGIN;\n\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            WidgetBase.WidgetContainer widget = mOpenWidgets.get(i);\n\n            if (widget != ignore) {\n                widget.setYSmooth(mTotalHeight);\n            }\n\n            mTotalHeight += widget.getMeasuredHeight() + mSpacing;\n        }\n    }\n\n    /**\n     * Notifies the renderer a widget has been opened and needs to be\n     * positioned.\n     *\n     * @param widget Widget opened\n     */\n    public void widgetOpened(final WidgetBase.WidgetContainer widget) {\n        if (mOpenWidgets.contains(widget)) {\n            Log.w(TAG, \"Widget was already rendered!\");\n            return;\n        }\n\n        mOpenWidgets.add(widget);\n\n        // Make sure the widget is properly oriented\n        widget.notifyOrientationChanged(mOrientation, true);\n\n        // Position it properly\n        float finalY = mTotalHeight;\n        widget.setYSmooth(finalY);\n        mTotalHeight += widget.getMeasuredHeight() + mSpacing;\n\n        reorderWidgets(null);\n    }\n\n    /**\n     * Notifies the renderer a widget has been closed and its\n     * space can be reclaimed\n     *\n     * @param widget Widget closed\n     */\n    public void widgetClosed(WidgetBase.WidgetContainer widget) {\n        widget.setYSmooth(widget.getFinalY() - Util.dpToPx(getContext(), 8));\n        mOpenWidgets.remove(widget);\n\n        // Reposition all the widgets\n        reorderWidgets(null);\n    }\n\n    /**\n     * Close all the widgets currently opened\n     */\n    public void closeAllWidgets() {\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            mOpenWidgets.get(i).getWidgetBase().close();\n        }\n    }\n\n    public void notifySidebarSlideStatus(float distance) {\n        float finalX = getTranslationX() + distance;\n        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();\n\n        if (finalX > params.leftMargin) {\n            finalX = params.leftMargin;\n        } else if (finalX < -params.leftMargin) {\n            finalX = -params.leftMargin;\n        }\n\n        setTranslationX(finalX);\n    }\n\n    public void notifySidebarSlideClose() {\n        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();\n        animate().translationX(-params.leftMargin).setDuration(\n                SideBar.SLIDE_ANIMATION_DURATION_MS).start();\n    }\n\n    public void notifySidebarSlideOpen() {\n        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();\n        animate().translationX(params.leftMargin).setDuration(\n                SideBar.SLIDE_ANIMATION_DURATION_MS).start();\n    }\n\n    public void hideWidgets() {\n        mIsHidden = true;\n\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            WidgetBase.WidgetContainer widget = mOpenWidgets.get(i);\n            widget.animate().translationXBy(-widget.getWidth()).setDuration(300).alpha(0)\n                    .setInterpolator(new AccelerateInterpolator()).start();\n        }\n    }\n\n    public void restoreWidgets() {\n        mIsHidden = false;\n\n        for (int i = 0; i < mOpenWidgets.size(); i++) {\n            WidgetBase.WidgetContainer widget = mOpenWidgets.get(i);\n            widget.animate().translationXBy(widget.getWidth()).setDuration(300).alpha(1)\n                    .setInterpolator(new DecelerateInterpolator()).start();\n        }\n    }\n\n    public boolean isHidden() {\n        return mIsHidden;\n    }\n\n    public int getWidgetsCount() {\n        return mOpenWidgets.size();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/showcase/AnimationUtils.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n* Copyright (C) 2012 Alex Curran\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui.showcase;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorSet;\nimport android.animation.ObjectAnimator;\nimport android.os.Handler;\nimport android.view.View;\n\n\npublic class AnimationUtils {\n    private static final int DEFAULT_DURATION = 300;\n    private static final String ALPHA = \"alpha\";\n    private static final float INVISIBLE = 0f;\n    private static final float VISIBLE = 1f;\n    private static final String COORD_X = \"x\";\n    private static final String COORD_Y = \"y\";\n    private static final int INSTANT = 0;\n\n    public interface AnimationStartListener {\n        void onAnimationStart();\n    }\n\n    public interface AnimationEndListener {\n        void onAnimationEnd();\n    }\n\n    public static float getX(View view) {\n        return view.getX();\n    }\n\n    public static float getY(View view) {\n        return view.getY();\n    }\n\n    public static void hide(View view) {\n        view.setAlpha(INVISIBLE);\n    }\n\n    public static ObjectAnimator createFadeInAnimation(Object target,\n            final AnimationStartListener listener) {\n        return createFadeInAnimation(target, DEFAULT_DURATION, listener);\n    }\n\n    public static ObjectAnimator createFadeInAnimation(Object target, int duration,\n            final AnimationStartListener listener) {\n        ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, VISIBLE);\n        oa.setDuration(duration).addListener(new Animator.AnimatorListener() {\n            @Override\n            public void onAnimationStart(Animator animator) {\n                listener.onAnimationStart();\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animator) {\n            }\n\n            @Override\n            public void onAnimationCancel(Animator animator) {\n            }\n\n            @Override\n            public void onAnimationRepeat(Animator animator) {\n            }\n        });\n        return oa;\n    }\n\n    public static ObjectAnimator createFadeOutAnimation(Object target,\n            final AnimationEndListener listener) {\n        return createFadeOutAnimation(target, DEFAULT_DURATION, listener);\n    }\n\n    public static ObjectAnimator createFadeOutAnimation(Object target, int duration,\n            final AnimationEndListener listener) {\n        ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, INVISIBLE);\n        oa.setDuration(duration).addListener(new Animator.AnimatorListener() {\n            @Override\n            public void onAnimationStart(Animator animator) {\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animator) {\n                listener.onAnimationEnd();\n            }\n\n            @Override\n            public void onAnimationCancel(Animator animator) {\n            }\n\n            @Override\n            public void onAnimationRepeat(Animator animator) {\n            }\n        });\n        return oa;\n    }\n\n    public static AnimatorSet createMovementAnimation(View view, float canvasX, float canvasY,\n            float offsetStartX, float offsetStartY, float offsetEndX, float offsetEndY,\n            final AnimationEndListener listener) {\n        hide(view);\n\n        ObjectAnimator alphaIn = ObjectAnimator.ofFloat(\n                view, ALPHA, INVISIBLE, VISIBLE).setDuration(500);\n\n        ObjectAnimator setUpX = ObjectAnimator.ofFloat(\n                view, COORD_X, canvasX + offsetStartX).setDuration(INSTANT);\n        ObjectAnimator setUpY = ObjectAnimator.ofFloat(\n                view, COORD_Y, canvasY + offsetStartY).setDuration(INSTANT);\n\n        ObjectAnimator moveX = ObjectAnimator.ofFloat(\n                view, COORD_X, canvasX + offsetEndX).setDuration(1000);\n        ObjectAnimator moveY = ObjectAnimator.ofFloat(\n                view, COORD_Y, canvasY + offsetEndY).setDuration(1000);\n        moveX.setStartDelay(1000);\n        moveY.setStartDelay(1000);\n\n        ObjectAnimator alphaOut = ObjectAnimator.ofFloat(\n                view, ALPHA, INVISIBLE).setDuration(500);\n        alphaOut.setStartDelay(2500);\n\n        AnimatorSet as = new AnimatorSet();\n        as.play(setUpX).with(setUpY).before(alphaIn).before(moveX).with(moveY).before(alphaOut);\n\n        Handler handler = new Handler();\n        Runnable runnable = new Runnable() {\n            @Override\n            public void run() {\n                listener.onAnimationEnd();\n            }\n        };\n        handler.postDelayed(runnable, 3000);\n\n        return as;\n    }\n\n    public static AnimatorSet createMovementAnimation(View view, float x, float y) {\n        ObjectAnimator alphaIn = ObjectAnimator.ofFloat(\n                view, ALPHA, INVISIBLE, VISIBLE).setDuration(500);\n\n        ObjectAnimator setUpX = ObjectAnimator.ofFloat(view, COORD_X, x).setDuration(INSTANT);\n        ObjectAnimator setUpY = ObjectAnimator.ofFloat(view, COORD_Y, y).setDuration(INSTANT);\n\n        AnimatorSet as = new AnimatorSet();\n        as.play(setUpX).with(setUpY).before(alphaIn);\n        return as;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/showcase/ShowcaseView.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2012 Alex Curran\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui.showcase;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.text.DynamicLayout;\nimport android.text.Layout;\nimport android.text.TextPaint;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\nimport android.widget.Button;\nimport android.widget.RelativeLayout;\n\nimport fr.xplod.focal.R;\n\nimport java.lang.reflect.Field;\n\nimport static org.cyanogenmod.focal.ui.showcase.AnimationUtils.AnimationEndListener;\nimport static org.cyanogenmod.focal.ui.showcase.AnimationUtils.AnimationStartListener;\n\n/**\n * A view which allows you to showcase areas of your app with an explanation.\n */\npublic class ShowcaseView extends RelativeLayout implements View.OnClickListener,\n        View.OnTouchListener {\n    public static final int TYPE_NO_LIMIT = 0;\n    public static final int TYPE_ONE_SHOT = 1;\n    public static final int INSERT_TO_DECOR = 0;\n    public static final int INSERT_TO_VIEW = 1;\n    public static final int ITEM_ACTION_HOME = 0;\n    public static final int ITEM_TITLE = 1;\n    public static final int ITEM_SPINNER = 2;\n    public static final int ITEM_ACTION_ITEM = 3;\n    public static final int ITEM_ACTION_OVERFLOW = 6;\n    public static final int INNER_CIRCLE_RADIUS = 94;\n    private static final String PREFS_SHOWCASE_INTERNAL = \"showcase_internal\";\n\n    private final Button mEndButton;\n    private final String buttonText;\n\n    private float showcaseX = -1;\n    private float showcaseY = -1;\n    private float showcaseRadius = -1;\n    private float metricScale = 1.0f;\n    private float legacyShowcaseX = -1;\n    private float legacyShowcaseY = -1;\n    private boolean isRedundant = false;\n    private boolean hasCustomClickListener = false;\n    private ConfigOptions mOptions;\n    private Paint mPaintTitle, mEraser;\n    private TextPaint mPaintDetail;\n    private int backColor;\n    private Drawable showcase;\n    private View mHandy;\n    private OnShowcaseEventListener mEventListener;\n    private Rect voidedArea;\n    private String mTitleText, mSubText;\n    private int detailTextColor = -1;\n    private int titleTextColor = -1;\n    private DynamicLayout mDynamicTitleLayout;\n    private DynamicLayout mDynamicDetailLayout;\n    private float[] mBestTextPosition;\n    private boolean mAlteredText = false;\n    private float scaleMultiplier = 1f;\n    private int mOrientation;\n\n    public ShowcaseView(Context context) {\n        this(context, null, R.styleable.CustomTheme_showcaseViewStyle);\n    }\n\n    public ShowcaseView(Context context, AttributeSet attrs) {\n        this(context, attrs, R.styleable.CustomTheme_showcaseViewStyle);\n    }\n\n    public ShowcaseView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        // Get the attributes for the ShowcaseView\n        final TypedArray styled = context.getTheme().obtainStyledAttributes(\n                attrs, R.styleable.ShowcaseView, R.attr.showcaseViewStyle, R.style.ShowcaseView);\n        backColor = styled.getInt(R.styleable.ShowcaseView_sv_backgroundColor,\n                Color.argb(128, 80, 80, 80));\n        detailTextColor = styled.getColor(R.styleable.ShowcaseView_sv_detailTextColor, Color.WHITE);\n        titleTextColor = styled.getColor(R.styleable.ShowcaseView_sv_titleTextColor,\n                Color.parseColor(\"#49C0EC\"));\n        buttonText = styled.getString(R.styleable.ShowcaseView_sv_buttonText);\n        styled.recycle();\n\n        metricScale = getContext().getResources().getDisplayMetrics().density;\n        mEndButton = (Button) LayoutInflater.from(context).inflate(R.layout.showcase_button, null);\n\n        ConfigOptions options = new ConfigOptions();\n        options.showcaseId = getId();\n        setConfigOptions(options);\n    }\n\n    /**\n     * Quick method to insert a ShowcaseView into an Activity\n     *\n     * @param viewToShowcase View to showcase\n     * @param activity       Activity to insert into\n     * @param title          Text to show as a title. Can be null.\n     * @param detailText     More detailed text. Can be null.\n     * @param options        A set of options to customise the ShowcaseView\n     * @return the created ShowcaseView instance\n     */\n    public static ShowcaseView insertShowcaseView(View viewToShowcase, Activity activity,\n            String title, String detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcaseView(viewToShowcase);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    /**\n     * Quick method to insert a ShowcaseView into an Activity\n     *\n     * @param viewToShowcase View to showcase\n     * @param activity       Activity to insert into\n     * @param title          Text to show as a title. Can be null.\n     * @param detailText     More detailed text. Can be null.\n     * @param options        A set of options to customise the ShowcaseView\n     * @return the created ShowcaseView instance\n     */\n    public static ShowcaseView insertShowcaseView(View viewToShowcase, Activity activity,\n            int title, int detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcaseView(viewToShowcase);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    public static ShowcaseView insertShowcaseView(int showcaseViewId, Activity activity,\n            String title, String detailText, ConfigOptions options) {\n        View v = activity.findViewById(showcaseViewId);\n        if (v != null) {\n            return insertShowcaseView(v, activity, title, detailText, options);\n        }\n        return null;\n    }\n\n    public static ShowcaseView insertShowcaseView(int showcaseViewId, Activity activity,\n            int title, int detailText, ConfigOptions options) {\n        View v = activity.findViewById(showcaseViewId);\n        if (v != null) {\n            return insertShowcaseView(v, activity, title, detailText, options);\n        }\n        return null;\n    }\n\n    public static ShowcaseView insertShowcaseView(float x, float y, Activity activity,\n            String title, String detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcasePosition(x, y);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    public static ShowcaseView insertShowcaseView(float x, float y, Activity activity,\n            int title, int detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcasePosition(x, y);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    public static ShowcaseView insertShowcaseView(View showcase, Activity activity) {\n        return insertShowcaseView(showcase, activity, null, null, null);\n    }\n\n    /**\n     * Quickly insert a ShowcaseView into an Activity, highlighting an item.\n     *\n     * @param type       the type of item to showcase (can be ITEM_ACTION_HOME, ITEM_TITLE_OR_SPINNER, ITEM_ACTION_ITEM or ITEM_ACTION_OVERFLOW)\n     * @param itemId     the ID of an Action item to showcase (only required for ITEM_ACTION_ITEM\n     * @param activity   Activity to insert the ShowcaseView into\n     * @param title      Text to show as a title. Can be null.\n     * @param detailText More detailed text. Can be null.\n     * @param options    A set of options to customise the ShowcaseView\n     * @return the created ShowcaseView instance\n     */\n    public static ShowcaseView insertShowcaseViewWithType(int type, int itemId, Activity activity,\n            String title, String detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcaseItem(type, itemId, activity);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    /**\n     * Quickly insert a ShowcaseView into an Activity, highlighting an item.\n     *\n     * @param type       the type of item to showcase (can be ITEM_ACTION_HOME, ITEM_TITLE_OR_SPINNER, ITEM_ACTION_ITEM or ITEM_ACTION_OVERFLOW)\n     * @param itemId     the ID of an Action item to showcase (only required for ITEM_ACTION_ITEM\n     * @param activity   Activity to insert the ShowcaseView into\n     * @param title      Text to show as a title. Can be null.\n     * @param detailText More detailed text. Can be null.\n     * @param options    A set of options to customise the ShowcaseView\n     * @return the created ShowcaseView instance\n     */\n    public static ShowcaseView insertShowcaseViewWithType(int type, int itemId, Activity activity,\n            int title, int detailText, ConfigOptions options) {\n        ShowcaseView sv = new ShowcaseView(activity);\n        if (options != null)\n            sv.setConfigOptions(options);\n        if (sv.getConfigOptions().insert == INSERT_TO_DECOR) {\n            ((ViewGroup) activity.getWindow().getDecorView()).addView(sv);\n        } else {\n            ((ViewGroup) activity.findViewById(android.R.id.content)).addView(sv);\n        }\n        sv.setShowcaseItem(type, itemId, activity);\n        sv.setText(title, detailText);\n        return sv;\n    }\n\n    public static ShowcaseView insertShowcaseView(float x, float y, Activity activity) {\n        return insertShowcaseView(x, y, activity, null, null, null);\n    }\n\n    private void init() {\n        boolean hasShot = getContext().getSharedPreferences(PREFS_SHOWCASE_INTERNAL,\n                Context.MODE_PRIVATE).getBoolean(\"hasShot\" + getConfigOptions().showcaseId, false);\n        if (hasShot && mOptions.shotType == TYPE_ONE_SHOT) {\n            // The showcase has already been shot once, so we don't need to do anything\n            setVisibility(View.GONE);\n            isRedundant = true;\n            return;\n        }\n        showcase = getContext().getResources().getDrawable(R.drawable.cling);\n\n        showcaseRadius = metricScale * INNER_CIRCLE_RADIUS;\n        PorterDuffXfermode mBlender = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);\n        setOnTouchListener(this);\n\n        mPaintTitle = new Paint();\n        mPaintTitle.setColor(titleTextColor);\n        mPaintTitle.setShadowLayer(2.0f, 0f, 2.0f, Color.DKGRAY);\n        mPaintTitle.setTextSize(24 * metricScale);\n        mPaintTitle.setAntiAlias(true);\n\n        mPaintDetail = new TextPaint();\n        mPaintDetail.setColor(detailTextColor);\n        mPaintDetail.setShadowLayer(2.0f, 0f, 2.0f, Color.DKGRAY);\n        mPaintDetail.setTextSize(16 * metricScale);\n        mPaintDetail.setAntiAlias(true);\n\n        mEraser = new Paint();\n        mEraser.setColor(0xFFFFFF);\n        mEraser.setAlpha(0);\n        mEraser.setXfermode(mBlender);\n        mEraser.setAntiAlias(true);\n\n        if (!mOptions.noButton && mEndButton.getParent() == null) {\n            RelativeLayout.LayoutParams lps = (LayoutParams) generateDefaultLayoutParams();\n            lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);\n            lps.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);\n            int margin = ((Number) (metricScale * 12)).intValue();\n            lps.setMargins(margin, margin, margin, margin);\n            lps.height = LayoutParams.WRAP_CONTENT;\n            lps.width = LayoutParams.WRAP_CONTENT;\n            mEndButton.setLayoutParams(lps);\n            mEndButton.setText(buttonText != null ? buttonText : getResources().getString(R.string.ok));\n            if (!hasCustomClickListener) {\n                mEndButton.setOnClickListener(this);\n            }\n            addView(mEndButton);\n        }\n\n    }\n\n    public void notifyOrientationChanged(int orientation) {\n        if (mEndButton != null) {\n            mEndButton.animate().rotation(orientation).setDuration(300).start();\n        }\n\n        if (mHandy != null) {\n            mHandy.animate().rotation(orientation).setDuration(300).start();\n        }\n\n        mOrientation = orientation;\n        invalidate();\n    }\n\n    /**\n     * Set the view to showcase\n     *\n     * @param view The {@link View} to showcase.\n     */\n    public void setShowcaseView(final View view) {\n        if (isRedundant || view == null) {\n            isRedundant = true;\n            return;\n        }\n        isRedundant = false;\n\n        view.post(new Runnable() {\n            @Override\n            public void run() {\n                init();\n                if (mOptions.insert == INSERT_TO_VIEW) {\n                    showcaseX = (float) (view.getLeft() + view.getWidth() / 2);\n                    showcaseY = (float) (view.getTop() + view.getHeight() / 2);\n                } else {\n                    int[] coordinates = new int[2];\n                    view.getLocationInWindow(coordinates);\n                    showcaseX = (float) (coordinates[0] + view.getWidth() / 2);\n                    showcaseY = (float) (coordinates[1] + view.getHeight() / 2);\n                }\n                invalidate();\n            }\n        });\n    }\n\n    /**\n     * Set a specific position to showcase\n     *\n     * @param x X co-ordinate\n     * @param y Y co-ordinate\n     */\n    public void setShowcasePosition(float x, float y) {\n        if (isRedundant) {\n            return;\n        }\n        showcaseX = x;\n        showcaseY = y;\n        init();\n        invalidate();\n    }\n\n    public void setShowcaseItem(final int itemType, final int actionItemId,\n            final Activity activity) {\n        post(new Runnable() {\n            @Override\n            public void run() {\n                View homeButton = activity.findViewById(android.R.id.home);\n                if (homeButton == null) {\n                    // Thanks to @hameno for this\n                    int homeId = activity.getResources().getIdentifier(\n                            \"abs__home\", \"id\", activity.getPackageName());\n                    if (homeId != 0) {\n                        homeButton = activity.findViewById(homeId);\n                    }\n                }\n                if (homeButton == null)\n                    throw new RuntimeException(\"insertShowcaseViewWithType cannot be used when the theme \" +\n                            \"has no ActionBar\");\n                ViewParent p = homeButton.getParent().getParent(); //ActionBarView\n\n                if (!p.getClass().getName().contains(\"ActionBarView\")) {\n                    String previousP = p.getClass().getName();\n                    p = p.getParent();\n                    String throwP = p.getClass().getName();\n                    if (!p.getClass().getName().contains(\"ActionBarView\"))\n                        throw new IllegalStateException(\"Cannot find ActionBarView for \" +\n                                \"Activity, instead found \" + previousP + \" and \" + throwP);\n                }\n\n                Class abv = p.getClass(); //ActionBarView class\n                Class absAbv = abv.getSuperclass(); //AbsActionBarView class\n\n                switch (itemType) {\n                    case ITEM_ACTION_HOME:\n                        setShowcaseView(homeButton);\n                        break;\n                    case ITEM_SPINNER:\n                        showcaseSpinner(p, abv);\n                        break;\n                    case ITEM_TITLE:\n                        showcaseTitle(p, abv);\n                        break;\n                    case ITEM_ACTION_ITEM:\n                    case ITEM_ACTION_OVERFLOW:\n                        showcaseActionItem(p, absAbv, itemType, actionItemId);\n                        break;\n                    default:\n                        Log.e(\"TAG\", \"Unknown item type\");\n                }\n            }\n        });\n\n    }\n\n    private void showcaseActionItem(ViewParent p, Class absAbv, int itemType, int actionItemId) {\n        try {\n            Field mAmpField = absAbv.getDeclaredField(\"mActionMenuPresenter\");\n            mAmpField.setAccessible(true);\n            Object mAmp = mAmpField.get(p);\n            if (itemType == ITEM_ACTION_OVERFLOW) {\n                // Finds the overflow button associated with the ActionMenuPresenter\n                Field mObField = mAmp.getClass().getDeclaredField(\"mOverflowButton\");\n                mObField.setAccessible(true);\n                View mOb = (View) mObField.get(mAmp);\n                if (mOb != null)\n                    setShowcaseView(mOb);\n            } else {\n                // Want an ActionItem, so find it\n                Field mAmvField = mAmp.getClass().getSuperclass().getDeclaredField(\"mMenuView\");\n                mAmvField.setAccessible(true);\n                Object mAmv = mAmvField.get(mAmp);\n\n                Field mChField;\n                if (mAmv.getClass().toString().contains(\"com.actionbarsherlock\")) {\n                    // There are thousands of superclasses to traverse up\n                    // Have to get superclasses because mChildren is private\n                    mChField = mAmv.getClass().getSuperclass().getSuperclass()\n                            .getSuperclass().getSuperclass().getDeclaredField(\"mChildren\");\n                } else\n                    mChField = mAmv.getClass().getSuperclass().getSuperclass()\n                            .getDeclaredField(\"mChildren\");\n                mChField.setAccessible(true);\n                Object[] mChs = (Object[]) mChField.get(mAmv);\n                for (Object mCh : mChs) {\n                    if (mCh != null) {\n                        View v = (View) mCh;\n                        if (v.getId() == actionItemId)\n                            setShowcaseView(v);\n                    }\n                }\n            }\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        } catch (NoSuchFieldException e) {\n            e.printStackTrace();\n        } catch (NullPointerException npe) {\n            throw new RuntimeException(\"insertShowcaseViewWithType() must be called \" +\n                    \"after or during onCreateOptionsMenu() of the host Activity\");\n        }\n    }\n\n    private void showcaseSpinner(ViewParent p, Class abv) {\n        try {\n            Field mSpinnerField = abv.getDeclaredField(\"mSpinner\");\n            mSpinnerField.setAccessible(true);\n            View mSpinnerView = (View) mSpinnerField.get(p);\n            if (mSpinnerView != null) {\n                setShowcaseView(mSpinnerView);\n            }\n        } catch (NoSuchFieldException e) {\n            Log.e(\"TAG\", \"Failed to find actionbar spinner\", e);\n        } catch (IllegalAccessException e) {\n            Log.e(\"TAG\", \"Failed to access actionbar spinner\", e);\n\n        }\n    }\n\n    private void showcaseTitle(ViewParent p, Class abv) {\n        try {\n            Field mTitleViewField = abv.getDeclaredField(\"mTitleView\");\n            mTitleViewField.setAccessible(true);\n            View titleView = (View) mTitleViewField.get(p);\n            if (titleView != null) {\n                setShowcaseView(titleView);\n            }\n        } catch (NoSuchFieldException e) {\n            Log.e(\"TAG\", \"Failed to find actionbar title\", e);\n        } catch (IllegalAccessException e) {\n            Log.e(\"TAG\", \"Failed to access actionbar title\", e);\n\n        }\n    }\n\n    /**\n     * Set the shot method of the showcase - only once or no limit\n     *\n     * @param shotType either TYPE_ONE_SHOT or TYPE_NO_LIMIT\n     * @deprecated Use the option in {@link ConfigOptions} instead.\n     */\n    @Deprecated\n    public void setShotType(int shotType) {\n        if (shotType == TYPE_NO_LIMIT || shotType == TYPE_ONE_SHOT) {\n            mOptions.shotType = shotType;\n        }\n    }\n\n    /**\n     * Decide whether touches outside the showcased circle should be ignored or not\n     *\n     * @param block true to block touches, false otherwise. By default, this is true.\n     * @deprecated Use the option in {@link ConfigOptions} instead.\n     */\n    @Deprecated\n    public void blockNonShowcasedTouches(boolean block) {\n        mOptions.block = block;\n    }\n\n    /**\n     * Override the standard button click event\n     *\n     * @param listener Listener to listen to on click events\n     */\n    public void overrideButtonClick(OnClickListener listener) {\n        if (isRedundant) {\n            return;\n        }\n        if (mEndButton != null) {\n            mEndButton.setOnClickListener(listener != null ? listener : this);\n        }\n        hasCustomClickListener = true;\n    }\n\n    public void setOnShowcaseEventListener(OnShowcaseEventListener listener) {\n        mEventListener = listener;\n    }\n\n    @Override\n    protected void dispatchDraw(Canvas canvas) {\n        if (showcaseX < 0 || showcaseY < 0 || isRedundant) {\n            super.dispatchDraw(canvas);\n            return;\n        }\n\n        Bitmap b = Bitmap.createBitmap(getMeasuredWidth(),\n                getMeasuredHeight(), Bitmap.Config.ARGB_8888);\n        Canvas c = new Canvas(b);\n\n        //Draw the semi-transparent background\n        c.drawColor(backColor);\n\n        //Draw to the scale specified\n        Matrix mm = new Matrix();\n        mm.postScale(scaleMultiplier, scaleMultiplier, showcaseX, showcaseY);\n        c.setMatrix(mm);\n\n        //Erase the area for the ring\n        c.drawCircle(showcaseX, showcaseY, showcaseRadius, mEraser);\n\n        boolean recalculateText = makeVoidedRect() || mAlteredText;\n        mAlteredText = false;\n\n        showcase.setBounds(voidedArea);\n        showcase.draw(c);\n\n        canvas.drawBitmap(b, 0, 0, null);\n\n        // Clean up, as we no longer require these items.\n        try {\n            c.setBitmap(null);\n        } catch (NullPointerException npe) {\n            //TODO why does this NPE happen?\n            npe.printStackTrace();\n        }\n        b.recycle();\n        b = null;\n\n\n        if (!TextUtils.isEmpty(mTitleText) || !TextUtils.isEmpty(mSubText)) {\n            if (recalculateText) {\n                mBestTextPosition = getBestTextPosition(canvas.getWidth(), canvas.getHeight());\n            }\n\n            if (!TextUtils.isEmpty(mTitleText)) {\n                //TODO: use a dynamic detail layout\n                canvas.save();\n                float width = mPaintTitle.measureText(mTitleText);\n                canvas.rotate(mOrientation, mBestTextPosition[0] + width / 2.0f,\n                        mBestTextPosition[1] + mPaintTitle.getTextSize() / 2.0f);\n                canvas.drawText(mTitleText, mBestTextPosition[0], mBestTextPosition[1], mPaintTitle);\n                canvas.restore();\n            }\n\n            if (!TextUtils.isEmpty(mSubText)) {\n                canvas.save();\n                if (recalculateText)\n                    mDynamicDetailLayout = new DynamicLayout(mSubText, mPaintDetail,\n                            ((Number) mBestTextPosition[2]).intValue(),\n                            Layout.Alignment.ALIGN_NORMAL, 1.2f, 1.0f, true);\n\n                if (mOrientation % 180 == 0) {\n                    canvas.translate(mBestTextPosition[0], mBestTextPosition[1]);\n                } else {\n                    canvas.rotate(mOrientation, mDynamicDetailLayout.getWidth() / 2,\n                        mDynamicDetailLayout.getHeight() / 2);\n                }\n\n                mDynamicDetailLayout.draw(canvas);\n                canvas.restore();\n            }\n        }\n\n        super.dispatchDraw(canvas);\n    }\n\n    /**\n     * Calculates the best place to position text\n     *\n     * @param canvasW width of the screen\n     * @param canvasH height of the screen\n     * @return\n     */\n    private float[] getBestTextPosition(int canvasW, int canvasH) {\n        float spaceTop = voidedArea.top;\n        float spaceBottom = canvasH - voidedArea.bottom - 64 * metricScale; //64dip considers the OK button\n\n        return new float[]{24 * metricScale, spaceTop > spaceBottom ? 128\n                * metricScale : 24 * metricScale + voidedArea.bottom, canvasW - 48 * metricScale};\n    }\n\n    /**\n     * Creates a {@link Rect} which represents the area the showcase covers.\n     * Used to calculate where best to place the text.\n     *\n     * @return true if voidedArea has changed, false otherwise.\n     */\n    private boolean makeVoidedRect() {\n\n        // This if statement saves resources by not recalculating voidedArea\n        // if the X & Y coordinates haven't changed\n        if (voidedArea == null || (showcaseX != legacyShowcaseX || showcaseY != legacyShowcaseY)) {\n            int cx = (int) showcaseX, cy = (int) showcaseY;\n            int dw = showcase.getIntrinsicWidth();\n            int dh = showcase.getIntrinsicHeight();\n\n            voidedArea = new Rect(cx - dw / 2, cy - dh / 2, cx + dw / 2, cy + dh / 2);\n\n            legacyShowcaseX = showcaseX;\n            legacyShowcaseY = showcaseY;\n\n            return true;\n        }\n\n        return false;\n    }\n\n    public void animateGesture(float offsetStartX, float offsetStartY,\n            float offsetEndX, float offsetEndY) {\n        mHandy = ((LayoutInflater) getContext().getSystemService(\n                Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.handy, null);\n        addView(mHandy);\n        mHandy.setRotation(mOrientation);\n        moveHand(offsetStartX, offsetStartY, offsetEndX, offsetEndY, new AnimationEndListener() {\n            @Override\n            public void onAnimationEnd() {\n                removeView(mHandy);\n            }\n        });\n    }\n\n    private void moveHand(float offsetStartX, float offsetStartY, float offsetEndX,\n            float offsetEndY, AnimationEndListener listener) {\n        AnimationUtils.createMovementAnimation(mHandy, showcaseX, showcaseY,\n                offsetStartX, offsetStartY,\n                offsetEndX, offsetEndY,\n                listener).start();\n    }\n\n    @Override\n    public void onClick(View view) {\n        // If the type is set to one-shot, store that it has shot\n        if (mOptions.shotType == TYPE_ONE_SHOT) {\n            SharedPreferences internal = getContext().getSharedPreferences(\n                    PREFS_SHOWCASE_INTERNAL, Context.MODE_PRIVATE);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {\n                internal.edit().putBoolean(\n                        \"hasShot\" + getConfigOptions().showcaseId, true).apply();\n            } else {\n                internal.edit().putBoolean(\n                        \"hasShot\" + getConfigOptions().showcaseId, true).commit();\n            }\n        }\n        hide();\n    }\n\n    public void hide() {\n        if (mEventListener != null) {\n            mEventListener.onShowcaseViewHide(this);\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {\n            fadeOutShowcase();\n        } else {\n            setVisibility(View.GONE);\n        }\n    }\n\n    private void fadeOutShowcase() {\n        AnimationUtils.createFadeOutAnimation(this, new AnimationEndListener() {\n            @Override\n            public void onAnimationEnd() {\n                setVisibility(View.GONE);\n            }\n        }).start();\n    }\n\n    public void show() {\n        if (mEventListener != null) {\n            mEventListener.onShowcaseViewShow(this);\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {\n            fadeInShowcase();\n        } else {\n            setVisibility(View.VISIBLE);\n        }\n    }\n\n    private void fadeInShowcase() {\n        AnimationUtils.createFadeInAnimation(this, new AnimationStartListener() {\n            @Override\n            public void onAnimationStart() {\n                setVisibility(View.VISIBLE);\n            }\n        }).start();\n    }\n\n    @Override\n    public boolean onTouch(View view, MotionEvent motionEvent) {\n\n        float xDelta = Math.abs(motionEvent.getRawX() - showcaseX);\n        float yDelta = Math.abs(motionEvent.getRawY() - showcaseY);\n        double distanceFromFocus = Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2));\n\n        if (mOptions.hideOnClickOutside && distanceFromFocus > showcaseRadius) {\n            this.hide();\n            return true;\n        }\n\n        return mOptions.block && distanceFromFocus > showcaseRadius;\n    }\n\n    public void setShowcaseIndicatorScale(float scaleMultiplier) {\n        this.scaleMultiplier = scaleMultiplier;\n    }\n\n    public ShowcaseView setTextColors(int titleTextColor, int detailTextColor) {\n        this.titleTextColor = titleTextColor;\n        this.detailTextColor = detailTextColor;\n        if (mPaintTitle != null) {\n            mPaintTitle.setColor(titleTextColor);\n        }\n        if (mPaintDetail != null) {\n            mPaintDetail.setColor(detailTextColor);\n        }\n        invalidate();\n        return this;\n    }\n\n    public void setText(int titleTextResId, int subTextResId) {\n        String titleText = getContext().getResources().getString(titleTextResId);\n        String subText = getContext().getResources().getString(subTextResId);\n        setText(titleText, subText);\n    }\n\n    public void setText(String titleText, String subText) {\n        mTitleText = titleText;\n        mSubText = subText;\n        mAlteredText = true;\n        invalidate();\n    }\n\n    /**\n     * Get the ghostly gesture hand for custom gestures\n     *\n     * @return a View representing the ghostly hand\n     */\n    public View getHand() {\n        final View mHandy = ((LayoutInflater) getContext().getSystemService(\n                Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.handy, null);\n        addView(mHandy);\n        AnimationUtils.hide(mHandy);\n\n        return mHandy;\n    }\n\n    /**\n     * Point to a specific view\n     *\n     * @param view The {@link View} to Showcase\n     */\n    public void pointTo(View view) {\n        float x = AnimationUtils.getX(view) + view.getWidth() / 2;\n        float y = AnimationUtils.getY(view) + view.getHeight() / 2;\n        pointTo(x, y);\n    }\n\n    /**\n     * Point to a specific point on the screen\n     *\n     * @param x X-coordinate to point to\n     * @param y Y-coordinate to point to\n     */\n    public void pointTo(float x, float y) {\n        AnimationUtils.createMovementAnimation(mHandy, x, y).start();\n    }\n\n    private ConfigOptions getConfigOptions() {\n        // Make sure that this method never returns null\n        if (mOptions == null) return mOptions = new ConfigOptions();\n        return mOptions;\n    }\n\n    private void setConfigOptions(ConfigOptions options) {\n        mOptions = options;\n    }\n\n    public interface OnShowcaseEventListener {\n        public void onShowcaseViewHide(ShowcaseView showcaseView);\n        public void onShowcaseViewShow(ShowcaseView showcaseView);\n    }\n\n    public static class ConfigOptions {\n        public boolean block = true, noButton = false;\n        public int showcaseId = 0;\n        public int shotType = TYPE_NO_LIMIT;\n        public int insert = INSERT_TO_DECOR;\n        public boolean hideOnClickOutside = false;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/showcase/ShowcaseViewBuilder.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2012 Alex Curran\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui.showcase;\n\nimport android.app.Activity;\nimport android.view.View;\n\npublic class ShowcaseViewBuilder {\n    private final ShowcaseView showcaseView;\n\n    public ShowcaseViewBuilder(Activity activity) {\n        this.showcaseView = new ShowcaseView(activity);\n    }\n\n    public ShowcaseViewBuilder(ShowcaseView showcaseView) {\n        this.showcaseView = showcaseView;\n    }\n\n    public ShowcaseViewBuilder(Activity activity, int showcaseLayoutViewId) {\n        this.showcaseView = (ShowcaseView) activity.getLayoutInflater()\n                .inflate(showcaseLayoutViewId, null);\n    }\n\n    public ShowcaseViewBuilder setShowcaseView(View view) {\n        showcaseView.setShowcaseView(view);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setShowcasePosition(float x, float y) {\n        showcaseView.setShowcasePosition(x, y);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setShowcaseItem(int itemType,\n            int actionItemId, Activity activity) {\n        showcaseView.setShowcaseItem(itemType, actionItemId, activity);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setShowcaseIndicatorScale(float scale) {\n        showcaseView.setShowcaseIndicatorScale(scale);\n        return this;\n    }\n\n    public ShowcaseViewBuilder overrideButtonClick(View.OnClickListener listener) {\n        showcaseView.overrideButtonClick(listener);\n        return this;\n    }\n\n    public ShowcaseViewBuilder animateGesture(float offsetStartX, float offsetStartY,\n            float offsetEndX, float offsetEndY) {\n        showcaseView.animateGesture(offsetStartX, offsetStartY, offsetEndX, offsetEndY);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setTextColors(int titleTextColor, int detailTextColor) {\n        showcaseView.setTextColors(titleTextColor, detailTextColor);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setText(String titleText, String subText) {\n        showcaseView.setText(titleText, subText);\n        return this;\n    }\n\n    public ShowcaseViewBuilder setText(int titleText, int subText) {\n        showcaseView.setText(titleText, subText);\n        return this;\n    }\n\n    public ShowcaseViewBuilder pointTo(View view) {\n        showcaseView.pointTo(view);\n        return this;\n    }\n\n    public ShowcaseViewBuilder pointTo(float x, float y) {\n        showcaseView.pointTo(x, y);\n        return this;\n    }\n\n    public ShowcaseView build() {\n        return showcaseView;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/ui/showcase/ShowcaseViews.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n * Copyright (C) 2012 Alex Curran\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.ui.showcase;\n\nimport android.app.Activity;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ShowcaseViews {\n\n    private final List<ShowcaseView> views = new ArrayList<ShowcaseView>();\n    private final Activity activity;\n    private final int showcaseTemplateId;\n    private OnShowcaseAcknowledged showcaseAcknowledgedListener = new OnShowcaseAcknowledged() {\n        @Override\n        public void onShowCaseAcknowledged(ShowcaseView showcaseView) {\n            //DEFAULT LISTENER - DOESN'T DO ANYTHING!\n        }\n    };\n\n    public interface OnShowcaseAcknowledged {\n        void onShowCaseAcknowledged(ShowcaseView showcaseView);\n    }\n\n    public ShowcaseViews(Activity activity, int showcaseTemplateLayout) {\n        this.activity = activity;\n        this.showcaseTemplateId = showcaseTemplateLayout;\n    }\n\n    public ShowcaseViews(Activity activity, int showcaseTemplateLayout, OnShowcaseAcknowledged acknowledgedListener) {\n        this(activity, showcaseTemplateLayout);\n        this.showcaseAcknowledgedListener = acknowledgedListener;\n    }\n\n    public void addView(ItemViewProperties properties) {\n        ShowcaseView showcaseView = new ShowcaseViewBuilder(activity, showcaseTemplateId).setShowcaseItem(properties.itemType, properties.id, activity)\n                .setText(properties.titleResId, properties.messageResId)\n                .setShowcaseIndicatorScale(properties.scale)\n                .build();\n        showcaseView.overrideButtonClick(createShowcaseViewDismissListener(showcaseView));\n        views.add(showcaseView);\n    }\n\n    private View.OnClickListener createShowcaseViewDismissListener(final ShowcaseView showcaseView) {\n        return new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                showcaseView.hide();\n                if (views.isEmpty()) {\n                    showcaseAcknowledgedListener.onShowCaseAcknowledged(showcaseView);\n                } else {\n                    show();\n                }\n            }\n        };\n    }\n\n    public void show() {\n        if (views.isEmpty()) {\n            return;\n        }\n        final ShowcaseView view = views.get(0);\n        ((ViewGroup) activity.getWindow().getDecorView()).addView(view);\n        views.remove(0);\n    }\n\n    public boolean hasViews() {\n        return !views.isEmpty();\n    }\n\n    public static class ItemViewProperties {\n        public static final int ID_SPINNER = 0;\n        public static final int ID_TITLE = 1;\n        public static final int ID_OVERFLOW = 2;\n        private static final float DEFAULT_SCALE = 1f;\n\n        protected final int titleResId;\n        protected final int messageResId;\n        protected final int id;\n        protected final int itemType;\n        protected final float scale;\n\n        public ItemViewProperties(int id, int titleResId, int messageResId, int itemType) {\n            this(id, titleResId, messageResId, itemType, DEFAULT_SCALE);\n        }\n\n        public ItemViewProperties(int id, int titleResId, int messageResId, int itemType, float scale) {\n            this.id = id;\n            this.titleResId = titleResId;\n            this.messageResId = messageResId;\n            this.itemType = itemType;\n            this.scale = scale;\n        }\n    }\n}"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/AutoExposureWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * Auto Exposure Widget, manages the auto-exposure measurement method\n */\npublic class AutoExposureWidget extends SimpleToggleWidget {\n    private static final String KEY_AUTOEXPOSURE = \"auto-exposure\";\n\n    public AutoExposureWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_AUTOEXPOSURE, R.drawable.ic_widget_autoexposure);\n        inflateFromXml(R.array.widget_autoexposure_values, R.array.widget_autoexposure_icons,\n                R.array.widget_autoexposure_hints);\n        getToggleButton().setHintText(R.string.widget_autoexposure);\n        restoreValueFromStorage(KEY_AUTOEXPOSURE);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        return super.isSupported(params) && mCamManager.isExposureAreaSupported();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/BurstModeWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.res.Resources;\nimport android.hardware.Camera;\nimport android.view.View;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.feats.BurstCapture;\n\n/**\n * Burst-shooting mode widget\n */\npublic class BurstModeWidget extends WidgetBase implements View.OnClickListener {\n    private WidgetOptionButton mBtnOff;\n    private WidgetOptionButton mBtn5;\n    private WidgetOptionButton mBtn10;\n    private WidgetOptionButton mBtn15;\n    private WidgetOptionButton mBtnInf;\n    private WidgetOptionButton mPreviousMode;\n    private CameraActivity mCameraActivity;\n    private BurstCapture mTransformer;\n    private final static String DRAWABLE_TAG = \"nemesis-burst-mode\";\n\n    public BurstModeWidget(CameraActivity activity) {\n        super(activity.getCamManager(), activity, R.drawable.ic_widget_burst);\n\n        mCameraActivity = activity;\n\n        // Create options\n        // XXX: Move that into an XML\n        mBtnOff = new WidgetOptionButton(R.drawable.ic_widget_burst_off, activity);\n        mBtn5 = new WidgetOptionButton(R.drawable.ic_widget_burst_5, activity);\n        mBtn10 = new WidgetOptionButton(R.drawable.ic_widget_burst_10, activity);\n        mBtn15 = new WidgetOptionButton(R.drawable.ic_widget_burst_15, activity);\n        mBtnInf = new WidgetOptionButton(R.drawable.ic_widget_burst_inf, activity);\n\n        getToggleButton().setHintText(R.string.widget_burstmode);\n        final Resources res = getWidget().getResources();\n        mBtn5.setHintText(String.format(res.getString(R.string.widget_burstmode_count_shots), 5));\n        mBtn10.setHintText(String.format(res.getString(R.string.widget_burstmode_count_shots), 10));\n        mBtn15.setHintText(String.format(res.getString(R.string.widget_burstmode_count_shots), 15));\n        mBtnOff.setHintText(R.string.widget_burstmode_off);\n        mBtnInf.setHintText(R.string.widget_burstmode_infinite);\n\n        addViewToContainer(mBtnOff);\n        addViewToContainer(mBtn5);\n        addViewToContainer(mBtn10);\n        addViewToContainer(mBtn15);\n        addViewToContainer(mBtnInf);\n\n        mBtnOff.setOnClickListener(this);\n        mBtn5.setOnClickListener(this);\n        mBtn10.setOnClickListener(this);\n        mBtn15.setOnClickListener(this);\n        mBtnInf.setOnClickListener(this);\n\n        mPreviousMode = mBtnOff;\n        mPreviousMode.activeImage(DRAWABLE_TAG + \"=off\");\n\n        mTransformer = new BurstCapture(activity);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        // Burst mode is supported by everything. If we are in photo mode that is.\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void onClick(View view) {\n        mPreviousMode.resetImage();\n\n        if (view == mBtnOff) {\n            // Disable the transformer\n            mCameraActivity.setCaptureTransformer(null);\n            mBtnOff.activeImage(DRAWABLE_TAG + \"=off\");\n            mPreviousMode = mBtnOff;\n        } else if (view == mBtn5) {\n            mTransformer.setBurstCount(5);\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtn5.activeImage(DRAWABLE_TAG + \"=5\");\n            mPreviousMode = mBtn5;\n        } else if (view == mBtn10) {\n            mTransformer.setBurstCount(10);\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtn10.activeImage(DRAWABLE_TAG + \"=10\");\n            mPreviousMode = mBtn10;\n        } else if (view == mBtn15) {\n            mTransformer.setBurstCount(15);\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtn15.activeImage(DRAWABLE_TAG + \"=15\");\n            mPreviousMode = mBtn15;\n        } else if (view == mBtnInf) {\n            // Infinite burst count\n            mTransformer.setBurstCount(0);\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtnInf.activeImage(DRAWABLE_TAG + \"=inf\");\n            mPreviousMode = mBtnInf;\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/EffectWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.util.Log;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\npublic class EffectWidget extends SimpleToggleWidget {\n    private static final String KEY_EFFECT = \"effect\";\n\n    public EffectWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_EFFECT, R.drawable.ic_widget_effect);\n        inflateFromXml(R.array.widget_effects_values, R.array.widget_effects_icons,\n                R.array.widget_effects_hints);\n        getToggleButton().setHintText(R.string.widget_effect);\n        restoreValueFromStorage(KEY_EFFECT);\n    }\n\n    @Override\n    public boolean filterDeviceSpecific(String value) {\n        if (Build.DEVICE.equals(\"mako\")) {\n            if (value.equals(\"whiteboard\") || value.equals(\"blackboard\")) {\n                // Hello Google, why do you report whiteboard/blackboard supported (in -values),\n                // when they are not? :(\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/EnhancementsWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.SeekBar;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SettingsStorage;\nimport org.cyanogenmod.focal.ui.CenteredSeekBar;\n\n/**\n * Contrast/sharpness/saturation setting widget (Qualcomm)\n */\npublic class EnhancementsWidget extends WidgetBase {\n    private static final String KEY_CONTRAST_PARAMETER = \"contrast\";\n    private static final String KEY_MAX_CONTRAST_PARAMETER = \"max-contrast\";\n    private static final String KEY_SHARPNESS_PARAMETER = \"sharpness\";\n    private static final String KEY_MAX_SHARPNESS_PARAMETER = \"max-sharpness\";\n    private static final String KEY_SATURATION_PARAMETER = \"saturation\";\n    private static final String KEY_MAX_SATURATION_PARAMETER = \"max-saturation\";\n\n    private static final int ROW_CONTRAST = 0;\n    private static final int ROW_SHARPNESS = 1;\n    private static final int ROW_SATURATION = 2;\n    private static final int ROW_COUNT = 3;\n\n    private WidgetOptionSeekBar[] mSeekBar;\n    private WidgetOptionLabel[] mValueLabel;\n\n    private class SeekBarListener implements SeekBar.OnSeekBarChangeListener {\n        private String mKey;\n\n        public SeekBarListener(String key) {\n            mKey = key;\n        }\n\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {\n            setValue(mKey, i);\n        }\n\n        @Override\n        public void onStartTrackingTouch(SeekBar seekBar) {\n\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n\n        }\n    }\n\n    public EnhancementsWidget(CameraManager cam, Context context) {\n        super(cam, context, R.drawable.ic_widget_contrast);\n\n        // Add views in the widget\n        mSeekBar = new WidgetOptionSeekBar[ROW_COUNT];\n        mValueLabel = new WidgetOptionLabel[ROW_COUNT];\n\n        for (int i = 0; i < ROW_COUNT; i++) {\n            int max = 0, val = 0;\n\n            switch (i) {\n                case ROW_CONTRAST:\n                    max = getValue(KEY_MAX_CONTRAST_PARAMETER);\n                    val = getValue(KEY_CONTRAST_PARAMETER);\n                    break;\n\n                case ROW_SATURATION:\n                    max = getValue(KEY_MAX_SATURATION_PARAMETER);\n                    val = getValue(KEY_SATURATION_PARAMETER);\n                    break;\n\n                case ROW_SHARPNESS:\n                    max = getValue(KEY_MAX_SHARPNESS_PARAMETER);\n                    val = getValue(KEY_SHARPNESS_PARAMETER);\n                    break;\n            }\n\n            mSeekBar[i] = new WidgetOptionSeekBar(context);\n            mValueLabel[i] = new WidgetOptionLabel(context);\n            mSeekBar[i].setMax(max);\n            mSeekBar[i].setProgress(val);\n\n            setViewAt(i, 2, 1, 3, mSeekBar[i]);\n            setViewAt(i, 1, 1, 1, mValueLabel[i]);\n\n            if (i == ROW_CONTRAST) {\n                setViewAt(i, 0, 1, 1, new WidgetOptionImage(\n                        R.drawable.ic_widget_contrast, context));\n            } else if (i == ROW_SATURATION) {\n                setViewAt(i, 0, 1, 1, new WidgetOptionImage(\n                        R.drawable.ic_widget_saturation, context));\n            } else if (i == ROW_SHARPNESS) {\n                setViewAt(i, 0, 1, 1, new WidgetOptionImage(\n                        R.drawable.ic_widget_sharpness, context));\n            }\n        }\n\n        mSeekBar[ROW_CONTRAST].setOnSeekBarChangeListener(\n                new SeekBarListener(KEY_CONTRAST_PARAMETER));\n        mSeekBar[ROW_SHARPNESS].setOnSeekBarChangeListener(\n                new SeekBarListener(KEY_SHARPNESS_PARAMETER));\n        mSeekBar[ROW_SATURATION].setOnSeekBarChangeListener(\n                new SeekBarListener(KEY_SATURATION_PARAMETER));\n\n        mValueLabel[ROW_CONTRAST].setText(restoreValueFromStorage(KEY_CONTRAST_PARAMETER));\n        mValueLabel[ROW_SHARPNESS].setText(restoreValueFromStorage(KEY_SHARPNESS_PARAMETER));\n        mValueLabel[ROW_SATURATION].setText(restoreValueFromStorage(KEY_SATURATION_PARAMETER));\n\n        getToggleButton().setHintText(R.string.widget_enhancements);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        if (params.get(KEY_CONTRAST_PARAMETER) != null\n                || params.get(KEY_SATURATION_PARAMETER) != null\n                || params.get(KEY_SHARPNESS_PARAMETER) != null) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    public int getValue(String key) {\n        Camera.Parameters params = mCamManager.getParameters();\n        String value = params.get(key);\n\n        if (value != null) {\n            try {\n                return Integer.parseInt(value);\n            } catch (NumberFormatException e) {\n                Log.e(TAG, value + \" is not a valid number, returning 0\");\n                return 0;\n            }\n        } else {\n            return 0;\n        }\n    }\n\n    public void setValue(String key, int value) {\n        String valueStr = Integer.toString(value);\n        mCamManager.setParameterAsync(key, valueStr);\n\n        if (key.equals(KEY_CONTRAST_PARAMETER)) {\n            mValueLabel[ROW_CONTRAST].setText(valueStr);\n            SettingsStorage.storeCameraSetting(getWidget().getContext(),\n                    mCamManager.getCurrentFacing(),KEY_CONTRAST_PARAMETER, valueStr);\n        } else if (key.equals(KEY_SHARPNESS_PARAMETER)) {\n            mValueLabel[ROW_SHARPNESS].setText(valueStr);\n            SettingsStorage.storeCameraSetting(getWidget().getContext(),\n                    mCamManager.getCurrentFacing(), KEY_SHARPNESS_PARAMETER, valueStr);\n        } else if (key.equals(KEY_SATURATION_PARAMETER)) {\n            mValueLabel[ROW_SATURATION].setText(valueStr);\n            SettingsStorage.storeCameraSetting(getWidget().getContext(),\n                    mCamManager.getCurrentFacing(), KEY_SATURATION_PARAMETER, valueStr);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/ExposureCompensationWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SettingsStorage;\nimport org.cyanogenmod.focal.ui.CenteredSeekBar;\n\n/**\n * Exposure compensation setup widget\n */\npublic class ExposureCompensationWidget extends WidgetBase implements\n        CenteredSeekBar.OnCenteredSeekBarChangeListener {\n    private static final String KEY_PARAMETER = \"exposure-compensation\";\n    private static final String KEY_MAX_PARAMETER = \"max-exposure-compensation\";\n    private static final String KEY_MIN_PARAMETER = \"min-exposure-compensation\";\n\n    private WidgetOptionCenteredSeekBar mSeekBar;\n    private WidgetOptionLabel mValueLabel;\n\n    public ExposureCompensationWidget(CameraManager cam, Context context) {\n        super(cam, context, R.drawable.ic_widget_exposure);\n\n        // Add views in the widget\n        mSeekBar = new WidgetOptionCenteredSeekBar(getMinExposureValue(),\n                getMaxExposureValue(), context);\n        mSeekBar.setNotifyWhileDragging(true);\n        mValueLabel = new WidgetOptionLabel(context);\n\n        addViewToContainer(mSeekBar);\n        addViewToContainer(mValueLabel);\n\n        mValueLabel.setText(restoreValueFromStorage(KEY_PARAMETER));\n        mSeekBar.setSelectedMinValue(Integer.parseInt(restoreValueFromStorage(KEY_PARAMETER)));\n        mSeekBar.setOnCenteredSeekBarChangeListener(this);\n\n        getToggleButton().setHintText(R.string.widget_exposure_compensation);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        return params != null && params.get(KEY_PARAMETER) != null;\n    }\n\n    public int getExposureValue() {\n        try {\n            return Integer.parseInt(mCamManager.getParameters().get(KEY_PARAMETER));\n        } catch (Exception e) {\n            return 0;\n        }\n    }\n\n    public int getMinExposureValue() {\n        try {\n            return Integer.parseInt(mCamManager.getParameters().get(KEY_MIN_PARAMETER));\n        } catch (Exception e) {\n            return 0;\n        }\n    }\n\n    public int getMaxExposureValue() {\n        try {\n            return Integer.parseInt(mCamManager.getParameters().get(KEY_MAX_PARAMETER));\n        } catch (Exception e) {\n            return 0;\n        }\n    }\n\n    public void setExposureValue(int value) {\n        if (getExposureValue() == value) return;\n\n        String valueStr = Integer.toString(value);\n\n        mCamManager.setParameterAsync(KEY_PARAMETER, valueStr);\n        SettingsStorage.storeCameraSetting(mWidget.getContext(), mCamManager.getCurrentFacing(),\n                KEY_PARAMETER, valueStr);\n        mValueLabel.setText(valueStr);\n    }\n\n    @Override\n    public void OnCenteredSeekBarValueChanged(CenteredSeekBar bar, Integer minValue) {\n        setExposureValue(minValue);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/FlashWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * Flash Widget, manages the flash settings\n */\npublic class FlashWidget extends SimpleToggleWidget {\n    private static final String KEY_REDEYE_REDUCTION = \"redeye-reduction\";\n    private static final String KEY_FLASH_MODE = \"flash-mode\";\n\n    public FlashWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_FLASH_MODE, R.drawable.ic_widget_flash_on);\n        inflateFromXml(R.array.widget_flash_values, R.array.widget_flash_icons,\n                R.array.widget_flash_hints);\n        getToggleButton().setHintText(R.string.widget_flash);\n        restoreValueFromStorage(KEY_FLASH_MODE);\n    }\n\n    /**\n     * When the flash is enabled, try to enable red-eye reduction (qualcomm)\n     *\n     * @param value The value set to the key\n     */\n    @Override\n    public void onValueSet(String value) {\n        if (value.equals(\"on\") || value.equals(\"auto\")) {\n            if (mCamManager.getParameters().get(KEY_REDEYE_REDUCTION) != null) {\n                mCamManager.setParameterAsync(KEY_REDEYE_REDUCTION, \"enable\");\n            }\n        } else {\n            if (mCamManager.getParameters().get(KEY_REDEYE_REDUCTION) != null) {\n                mCamManager.setParameterAsync(KEY_REDEYE_REDUCTION, \"disable\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/HdrWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\nimport java.util.List;\n\npublic class HdrWidget extends SimpleToggleWidget {\n    private final static String KEY_PARAMETER = \"ae-bracket-hdr\";\n\n    public HdrWidget(CameraManager cam, Context context) {\n        super(cam, context, \"ae-bracket-hdr\", R.drawable.ic_widget_hdr);\n        getToggleButton().setHintText(R.string.widget_hdr);\n\n        // Reminder: AOSP's HDR mode is scene-mode, so we did put that in scene-mode\n        // Here, it's for qualcomm's ae-bracket-hdr param. We filter out scene-mode\n        // HDR in priority though, for devices like the Nexus 4 which reports\n        // ae-bracket-hdr, but doesn't use it.\n        Camera.Parameters params = cam.getParameters();\n        if (params == null) {\n            return;\n        }\n\n        List<String> sceneModes = params.getSupportedSceneModes();\n\n        if (sceneModes != null && !sceneModes.contains(\"hdr\")) {\n            addValue(\"Off\", R.drawable.ic_widget_hdr_off, context.getString(R.string.disabled));\n            addValue(\"HDR\", R.drawable.ic_widget_hdr_on, context.getString(R.string.enabled));\n            addValue(\"AE-Bracket\", R.drawable.ic_widget_hdr_aebracket,\n                    context.getString(R.string.widget_hdr_aebracket));\n\n            restoreValueFromStorage(KEY_PARAMETER);\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/IsoWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * ISO widget, sets ISO sensitivity value\n */\npublic class IsoWidget extends SimpleToggleWidget {\n    private static final String KEY_ISO = \"iso\";\n\n    public IsoWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_ISO, R.drawable.ic_widget_iso);\n        inflateFromXml(R.array.widget_iso_values, R.array.widget_iso_icons,\n                R.array.widget_iso_hints);\n        getToggleButton().setHintText(R.string.widget_iso);\n        restoreValueFromStorage(KEY_ISO);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/SceneModeWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\npublic class SceneModeWidget extends SimpleToggleWidget {\n    private static final String KEY_SCENEMODE = \"scene-mode\";\n\n    public SceneModeWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_SCENEMODE, R.drawable.ic_widget_scenemode);\n        inflateFromXml(R.array.widget_scenemode_values, R.array.widget_scenemode_icons,\n                R.array.widget_scenemode_hints);\n        getToggleButton().setHintText(R.string.widget_scenemode);\n        restoreValueFromStorage(KEY_SCENEMODE);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/SettingsWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.app.AlertDialog;\nimport android.content.DialogInterface;\nimport android.hardware.Camera;\nimport android.media.CamcorderProfile;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.NumberPicker;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.CameraCapabilities;\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SettingsStorage;\nimport org.cyanogenmod.focal.SnapshotManager;\n\nimport java.text.DecimalFormat;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * Overflow settings widget\n */\npublic class SettingsWidget extends WidgetBase {\n    private static final String TAG = \"SettingsWidget\";\n\n    private static final String DRAWABLE_KEY_EXPO_RING = \"_Nemesis_ExposureRing=true\";\n    private static final String DRAWABLE_KEY_AUTO_ENHANCE = \"_Nemesis_AutoEnhance=true\";\n    private static final String DRAWABLE_KEY_RULE_OF_THIRDS = \"_Nemesis_RuleOfThirds=true\";\n    private static final String KEY_SHOW_EXPOSURE_RING = \"ShowExposureRing\";\n    private static final String KEY_ENABLE_AUTO_ENHANCE = \"AutoEnhanceEnabled\";\n    private static final String KEY_ENABLE_RULE_OF_THIRDS = \"RuleOfThirdsEnabled\";\n    private WidgetOptionButton mResolutionButton;\n    private WidgetOptionButton mToggleExposureRing;\n    private WidgetOptionButton mToggleAutoEnhancer;\n    private WidgetOptionButton mToggleWidgetsButton;\n    private WidgetOptionButton mToggleRuleOfThirds;\n    private CameraActivity mContext;\n    private CameraCapabilities mCapabilities;\n    private List<String> mResolutionsName;\n    private List<String> mVideoResolutions;\n    private List<Camera.Size> mResolutions;\n    private AlertDialog mResolutionDialog;\n    private AlertDialog mWidgetsDialog;\n    private NumberPicker mNumberPicker;\n    private int mInitialOrientation = -1;\n    private int mOrientation;\n\n    private final Comparator<? super Camera.Size> mResolutionsSorter =\n            new Comparator<Camera.Size>() {\n        @Override\n        public int compare(Camera.Size size, Camera.Size size2) {\n            if (size.width * size.height > size2.width * size2.height) {\n                return -1;\n            } else {\n                return 1;\n            }\n        }\n    };\n\n    private View.OnClickListener mExpoRingClickListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View view) {\n            mContext.setExposureRingVisible(!mContext.isExposureRingVisible());\n            if (mContext.isExposureRingVisible()) {\n                mToggleExposureRing.activeImage(DRAWABLE_KEY_EXPO_RING);\n            } else {\n                mToggleExposureRing.resetImage();\n            }\n\n            SettingsStorage.storeAppSetting(mContext, KEY_SHOW_EXPOSURE_RING,\n                    mContext.isExposureRingVisible() ? \"1\" : \"0\");\n        }\n    };\n\n    private View.OnClickListener mRuleOfThirdsClickListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View view) {\n            View rot = mContext.findViewById(R.id.rule_of_thirds);\n\n            if (rot.getVisibility() == View.GONE) {\n                mToggleRuleOfThirds.activeImage(DRAWABLE_KEY_RULE_OF_THIRDS);\n                rot.setVisibility(View.VISIBLE);\n            } else {\n                mToggleRuleOfThirds.resetImage();\n                rot.setVisibility(View.GONE);\n            }\n\n            SettingsStorage.storeAppSetting(mContext, KEY_ENABLE_RULE_OF_THIRDS,\n                    rot.getVisibility() == View.GONE ? \"0\" : \"1\");\n        }\n    };\n\n    private View.OnClickListener mAutoEnhanceClickListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View view) {\n            SnapshotManager snapMan = mContext.getSnapManager();\n            snapMan.setAutoEnhance(!snapMan.getAutoEnhance());\n\n            if (snapMan.getAutoEnhance()) {\n                mToggleAutoEnhancer.activeImage(DRAWABLE_KEY_AUTO_ENHANCE);\n            } else {\n                mToggleAutoEnhancer.resetImage();\n            }\n\n            SettingsStorage.storeAppSetting(mContext, KEY_ENABLE_AUTO_ENHANCE,\n                    snapMan.getAutoEnhance() ? \"1\" : \"0\");\n        }\n    };\n\n    private View.OnClickListener mResolutionClickListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View view) {\n            mInitialOrientation = -1;\n\n            mNumberPicker = new NumberPicker(mContext);\n            String[] names = new String[mResolutionsName.size()];\n            mResolutionsName.toArray(names);\n            if (names.length > 0) {\n                mNumberPicker.setDisplayedValues(names);\n\n                mNumberPicker.setMinValue(0);\n                mNumberPicker.setMaxValue(names.length - 1);\n            } else {\n                mNumberPicker.setMinValue(0);\n                mNumberPicker.setMaxValue(0);\n            }\n            mNumberPicker.setWrapSelectorWheel(false);\n            mNumberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);\n            mNumberPicker.setFormatter(new NumberPicker.Formatter() {\n                @Override\n                public String format(int i) {\n                    return mResolutionsName.get(i);\n                }\n            });\n\n            if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO){\n                // TODO set correct menu selection also for video\n                String actualSz = getActualProfileResolution();\n                if (mVideoResolutions != null) {\n                    for (int i = 0; i < mVideoResolutions.size(); i++) {\n                        if (mVideoResolutions.get(i).equals(actualSz)) {\n                            mNumberPicker.setValue(i);\n                            break;\n                        }\n                    }\n                }\n            } else {\n                Camera.Size actualSz = mCamManager.getParameters().getPictureSize();\n                if (mResolutions != null) {\n                    for (int i = 0; i < mResolutions.size(); i++) {\n                        if (mResolutions.get(i).equals(actualSz)) {\n                            mNumberPicker.setValue(i);\n                            break;\n                        }\n                    }\n                }\n            }\n\n            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);\n            builder.setView(mNumberPicker);\n            builder.setTitle(null);\n            builder.setCancelable(false);\n            builder.setPositiveButton(mContext.getString(R.string.ok),\n                    new DialogInterface.OnClickListener() {\n                @Override\n                public void onClick(DialogInterface dialogInterface, int i) {\n                    mInitialOrientation = -1;\n\n                    if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO\n                            || CameraActivity.getCameraMode()\n                            == CameraActivity.CAMERA_MODE_PICSPHERE) {\n                        // Set picture size\n                        Camera.Size size = mResolutions.get(mNumberPicker.getValue());\n                        // TODO: only one method\n                        mCamManager.setPictureSize(\"\"+size.width+\"x\"+size.height);\n\n                        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n                            SettingsStorage.storeCameraSetting(mContext, mCamManager\n                                    .getCurrentFacing(), \"picture-size\",\n                                    \"\"+size.width+\"x\"+size.height);\n                        } else {\n                            SettingsStorage.storeCameraSetting(mContext,\n                                    mCamManager.getCurrentFacing(), \"picsphere-picture-size\",\n                                    \"\"+size.width+\"x\"+size.height);\n                        }\n                    } else if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n                        // Set video size\n                        String size = mVideoResolutions.get(mNumberPicker.getValue());\n                        applyVideoResolution(size);\n                        \n                        String[] splat = size.split(\"x\");\n                        int width = Integer.parseInt(splat[0]);\n                        int height = Integer.parseInt(splat[1]);\n\n                        SettingsStorage.storeCameraSetting(mContext, mCamManager.getCurrentFacing(),\n                                \"video-size\", \"\"+width+\"x\"+height);\n                    }\n                }\n            });\n\n            mResolutionDialog = builder.create();\n            mResolutionDialog.show();\n            ((ViewGroup)mNumberPicker.getParent().getParent().getParent())\n                    .animate().rotation(mOrientation).setDuration(300).start();\n        }\n    };\n\n    public SettingsWidget(CameraActivity context, CameraCapabilities capabilities) {\n        super(context.getCamManager(), context, R.drawable.ic_widget_settings);\n        mContext = context;\n        mCapabilities = capabilities;\n\n        CameraManager cam = context.getCamManager();\n\n        getToggleButton().setHintText(R.string.widget_settings);\n\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO\n                || CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PICSPHERE) {\n            // Get the available photo size. Unlike AOSP app, we don't\n            // store manually each resolution in an XML, but we calculate it directly\n            // from the width and height of the picture size.\n            mResolutions = cam.getParameters().getSupportedPictureSizes();\n            mResolutionsName = new ArrayList<String>();\n\n            DecimalFormat df = new DecimalFormat();\n            df.setMaximumFractionDigits(1);\n            df.setMinimumFractionDigits(0);\n            df.setDecimalSeparatorAlwaysShown(false);\n\n            Collections.sort(mResolutions, mResolutionsSorter);\n\n            for (Camera.Size size : mResolutions) {\n                float megapixels = size.width * size.height / 1000000.0f;\n                mResolutionsName.add(df.format(megapixels) +\n                        \"MP (\" + size.width + \"x\" + size.height + \")\");\n            }\n\n            // Restore picture size if we have any\n            String resolution = \"\";\n            if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n                resolution = SettingsStorage.getCameraSetting(context,\n                        mCamManager.getCurrentFacing(), \"picture-size\", \"\"+mResolutions\n                        .get(0).width+\"x\"+mResolutions.get(0).height);\n            } else {\n                resolution = SettingsStorage.getCameraSetting(context,\n                        mCamManager.getCurrentFacing(), \"picsphere-picture-size\", \"640x480\");\n            }\n            mCamManager.setPictureSize(resolution);\n        } else if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_VIDEO) {\n            mResolutions = cam.getParameters().getSupportedVideoSizes();\n            mResolutionsName = new ArrayList<String>();\n            mVideoResolutions = new ArrayList<String>();\n\n            if (mResolutions != null) {\n                // We support a fixed set of video resolutions (pretty much like AOSP)\n                // because we need to take the CamcorderProfile (media_profiles.xml), which\n                // has a fixed set of values...\n                for (Camera.Size size : mResolutions) {\n                    if (size.width == 1920 && size.height == 1080\n                            || size.width == 1920 && size.height == 1088) {\n                        mResolutionsName.add(mContext.getString(R.string.video_res_1080p));\n                        mVideoResolutions.add(\"1920x1080\");\n                    } else if (size.width == 1280 && size.height == 720) {\n                        mResolutionsName.add(mContext.getString(R.string.video_res_720p));\n                        mVideoResolutions.add(\"1280x720\");\n                    } else if (size.width == 720 && size.height == 480) {\n                        mResolutionsName.add(mContext.getString(R.string.video_res_480p));\n                        mVideoResolutions.add(\"720x480\");\n                    } else if (size.width == 352 && size.height == 288) {\n                        mResolutionsName.add(mContext.getString(R.string.video_res_mms));\n                        mVideoResolutions.add(\"352x288\");\n                    }\n                }\n            } else if (mResolutions == null || mResolutions.size() == 0) {\n                // We detected no compatible video resolution! We add default ones.\n                mResolutionsName.add(mContext.getString(R.string.video_res_1080p));\n                mVideoResolutions.add(\"1920x1080\");\n                mResolutionsName.add(mContext.getString(R.string.video_res_720p));\n                mVideoResolutions.add(\"1280x720\");\n                mResolutionsName.add(mContext.getString(R.string.video_res_480p));\n                mVideoResolutions.add(\"720x480\");\n            }\n\n            // TODO: Restore video size if we have any\n            String resolution = SettingsStorage.getCameraSetting(context,\n                        mCamManager.getCurrentFacing(), \"video-size\", mVideoResolutions.get(0));\n            applyVideoResolution(resolution);\n        }\n\n        mResolutionButton = new WidgetOptionButton(\n                R.drawable.ic_widget_settings_resolution, context);\n        mResolutionButton.setOnClickListener(mResolutionClickListener);\n        mResolutionButton.setHintText(mContext.getString(R.string.widget_settings_picture_size));\n        addViewToContainer(mResolutionButton);\n\n        if (mCamManager.isExposureAreaSupported()\n                && CameraActivity.getCameraMode() != CameraActivity.CAMERA_MODE_PICSPHERE) {\n            mToggleExposureRing = new WidgetOptionButton(\n                    R.drawable.ic_widget_settings_exposurering, context);\n            mToggleExposureRing.setHintText(mContext.getString(\n                    R.string.widget_settings_exposure_ring));\n            mToggleExposureRing.setOnClickListener(mExpoRingClickListener);\n\n            // Restore exposure ring state\n            if (SettingsStorage.getAppSetting(mContext, KEY_SHOW_EXPOSURE_RING, \"0\").equals(\"1\")) {\n                mContext.setExposureRingVisible(true);\n                mToggleExposureRing.activeImage(DRAWABLE_KEY_EXPO_RING);\n            } else {\n                mContext.setExposureRingVisible(false);\n            }\n            addViewToContainer(mToggleExposureRing);\n        }\n\n        // Toggle auto enhancer\n        if (CameraActivity.getCameraMode() != CameraActivity.CAMERA_MODE_PICSPHERE) {\n            mToggleAutoEnhancer = new WidgetOptionButton(R.drawable.ic_enhancing, context);\n            mToggleAutoEnhancer.setOnClickListener(mAutoEnhanceClickListener);\n            mToggleAutoEnhancer.setHintText(mContext.getString(\n                    R.string.widget_settings_autoenhance));\n\n            // Restore auto enhancer state\n            if (SettingsStorage.getAppSetting(mContext, KEY_ENABLE_AUTO_ENHANCE, \"0\").equals(\"1\")) {\n                mContext.getSnapManager().setAutoEnhance(true);\n                mToggleAutoEnhancer.activeImage(DRAWABLE_KEY_AUTO_ENHANCE);\n            } else {\n                if (mContext.getSnapManager() != null) {\n                    mContext.getSnapManager().setAutoEnhance(false);\n                }\n            }\n\n            addViewToContainer(mToggleAutoEnhancer);\n\n            // Toggle rule of thirds\n            mToggleRuleOfThirds = new WidgetOptionButton(\n                    R.drawable.ic_widget_settings_rulethirds, context);\n            mToggleRuleOfThirds.setOnClickListener(mRuleOfThirdsClickListener);\n            mToggleRuleOfThirds.setHintText(mContext.getString(\n                    R.string.widget_settings_ruleofthirds));\n\n            // Restore rule of thirds visibility state\n            if (SettingsStorage.getAppSetting(mContext,\n                    KEY_ENABLE_RULE_OF_THIRDS, \"0\").equals(\"1\")) {\n                mContext.findViewById(R.id.rule_of_thirds).setVisibility(View.VISIBLE);\n                mToggleRuleOfThirds.activeImage(KEY_ENABLE_RULE_OF_THIRDS);\n            } else {\n                mContext.findViewById(R.id.rule_of_thirds).setVisibility(View.GONE);\n                mToggleRuleOfThirds.resetImage();\n            }\n\n            addViewToContainer(mToggleRuleOfThirds);\n        }\n\n        // Choose widgets to appear\n        mToggleWidgetsButton = new WidgetOptionButton(\n                R.drawable.ic_widget_settings_widgets, context);\n        mToggleWidgetsButton.setHintText(mContext.getString(\n                R.string.widget_settings_choose_widgets_button));\n        mToggleWidgetsButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                openWidgetsToggleDialog();\n            }\n        });\n        addViewToContainer(mToggleWidgetsButton);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        int mode = CameraActivity.getCameraMode();\n\n        return (mode == CameraActivity.CAMERA_MODE_PHOTO || mode == CameraActivity.CAMERA_MODE_VIDEO\n                || mode == CameraActivity.CAMERA_MODE_PICSPHERE);\n    }\n\n    @Override\n    public void notifyOrientationChanged(int orientation) {\n        if (mInitialOrientation == -1) mInitialOrientation = orientation;\n        if (mOrientation == orientation) return;\n\n        mOrientation = orientation;\n        if (mResolutionDialog != null) {\n            ((ViewGroup) mNumberPicker.getParent().getParent().getParent().getParent())\n                    .animate().rotation(orientation - mInitialOrientation).setDuration(300).start();\n        }\n\n        if (mWidgetsDialog != null) {\n            ((ViewGroup) mWidgetsDialog.getListView().getParent()\n                    .getParent().getParent().getParent()).animate().rotation(orientation\n                    - mInitialOrientation).setDuration(300).start();\n        }\n    }\n\n    private void applyVideoResolution(String resolution) {\n        if (resolution.equals(\"1920x1080\")) {\n            mContext.getSnapManager().setVideoProfile(\n                    CamcorderProfile.get(CamcorderProfile.QUALITY_1080P));\n        } else if (resolution.equals(\"1280x720\")) {\n            mContext.getSnapManager().setVideoProfile(\n                    CamcorderProfile.get(CamcorderProfile.QUALITY_720P));\n        } else if (resolution.equals(\"720x480\")) {\n            mContext.getSnapManager().setVideoProfile(\n                    CamcorderProfile.get(CamcorderProfile.QUALITY_480P));\n        } else if (resolution.equals(\"352x288\")) {\n            mContext.getSnapManager().setVideoProfile(\n                    CamcorderProfile.get(CamcorderProfile.QUALITY_CIF));\n        }\n    }\n\n    private String getActualProfileResolution() {\n        CamcorderProfile actualProfile = mContext.getSnapManager().getVideoProfile();\n        return \"\" + actualProfile.videoFrameWidth + \"x\" + actualProfile.videoFrameHeight;\n    }\n\n    private void openWidgetsToggleDialog() {\n        final List<WidgetBase> widgets = mCapabilities.getWidgets();\n        List<String> widgetsName = new ArrayList<String>();\n\n        // Construct a list of widgets\n        for (WidgetBase widget : widgets) {\n            if (widget.getClass().getName().contains(\"SettingsWidget\")) continue;\n            widgetsName.add(widget.getToggleButton().getHintText());\n        }\n\n        CharSequence[] items = widgetsName.toArray(new CharSequence[widgetsName.size()]);\n\n        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);\n        builder.setTitle(mContext.getString(R.string.widget_settings_choose_widgets));\n        builder.setMultiChoiceItems(items, null,\n                new DialogInterface.OnMultiChoiceClickListener() {\n                    // indexSelected contains the index of item (of which checkbox checked)\n                    @Override\n                    public void onClick(DialogInterface dialog, int indexSelected,\n                                        boolean isChecked) {\n                        widgets.get(indexSelected).setHidden(!isChecked);\n                    }\n                })\n                // Set the action buttons\n                .setPositiveButton(\"OK\", new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int id) {\n                        // Store the widget visibility status in SharedPreferences, using the\n                        // widget class name as key\n                        for (WidgetBase widget : widgets) {\n                            if (widget.getClass().getName().contains(\"SettingsWidget\")) {\n                                continue;\n                            }\n\n                            SettingsStorage.storeVisibilitySetting(mContext,\n                                    widget.getClass().getName(), !widget.isHidden());\n                        }\n                    }\n                })\n                .setNegativeButton(\"Cancel\", new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int id) {\n                        // Restore visibility status from storage\n                        for (WidgetBase widget : widgets) {\n                            if (widget.getClass().getName().contains(\"SettingsWidget\")) {\n                                continue;\n                            }\n\n                            widget.setHidden(!SettingsStorage.getVisibilitySetting(\n                                    mContext, widget.getClass().getName()));\n                        }\n                    }\n                });\n\n\n        mWidgetsDialog = builder.create();\n        mWidgetsDialog.show();\n        for (int i = 0; i < widgets.size(); i++) {\n            mWidgetsDialog.getListView().setItemChecked(i, !widgets.get(i).isHidden());\n        }\n        ((ViewGroup)mWidgetsDialog.getListView().getParent().getParent().getParent())\n                .animate().rotation(mOrientation).setDuration(300).start();\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/ShutterSpeedWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\nimport android.view.View;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport org.cyanogenmod.focal.SettingsStorage;\n\nimport fr.xplod.focal.R;\n\n/**\n * Shutter speed setup widget\n */\npublic class ShutterSpeedWidget extends WidgetBase {\n    // XXX We use shutter-speed here because the wrapper sets the ae-mode\n    // according to the value of shutter speed as well as ISO.\n    // What is done here (0-12, auto) is sony-specific anyway, so it\n    // makes sense to keep it this way\n    private static final String KEY_PARAMETER = \"sony-shutter-speed\";\n    private static final String KEY_MAX_PARAMETER = \"sony-max-shutter-speed\";\n    private static final String KEY_MIN_PARAMETER = \"sony-min-shutter-speed\";\n    private static final String KEY_AUTO_VALUE = \"auto\";\n\n    private WidgetOptionButton mMinusButton;\n    private WidgetOptionButton mPlusButton;\n    private WidgetOptionButton mAutoButton;\n    private WidgetOptionLabel mValueLabel;\n\n\n    private class MinusClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View view) {\n            if (getShutterSpeedRawValue().equals(KEY_AUTO_VALUE)) {\n                setShutterSpeedValue(getMaxShutterSpeedValue());\n            } else {\n                setShutterSpeedValue(Math.max(getShutterSpeedValue() - 1, getMinShutterSpeedValue()));\n            }\n        }\n    }\n\n    private class PlusClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View view) {\n            if (getShutterSpeedRawValue().equals(KEY_AUTO_VALUE)) {\n                setShutterSpeedValue(getMinShutterSpeedValue());\n            } else {\n                setShutterSpeedValue(Math.min(getShutterSpeedValue() + 1, getMaxShutterSpeedValue()));\n            }\n        }\n    }\n\n    private class AutoClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View view) {\n            setAutoShutterSpeed();\n        }\n    }\n\n    public ShutterSpeedWidget(CameraManager cam, Context context) {\n        super(cam, context, R.drawable.ic_widget_shutterspeed);\n\n        // Add views in the widget\n        mMinusButton = new WidgetOptionButton(R.drawable.ic_widget_timer_minus, context);\n        mPlusButton = new WidgetOptionButton(R.drawable.ic_widget_timer_plus, context);\n        mAutoButton = new WidgetOptionButton(R.drawable.ic_widget_iso_auto, context);\n        mValueLabel = new WidgetOptionLabel(context);\n\n        mMinusButton.setOnClickListener(new MinusClickListener());\n        mPlusButton.setOnClickListener(new PlusClickListener());\n        mAutoButton.setOnClickListener(new AutoClickListener());\n\n        addViewToContainer(mMinusButton);\n        addViewToContainer(mValueLabel);\n        addViewToContainer(mPlusButton);\n        addViewToContainer(mAutoButton);\n\n        mValueLabel.setText(getShutterSpeedDisplayValue(restoreValueFromStorage(KEY_PARAMETER)));\n        if ((restoreValueFromStorage(KEY_PARAMETER) != null) &&\n            (restoreValueFromStorage(KEY_PARAMETER).equals(KEY_AUTO_VALUE))) {\n            mAutoButton.activeImage(KEY_PARAMETER + \":\" + KEY_AUTO_VALUE);\n        }\n\n        getToggleButton().setHintText(R.string.widget_shutter_speed);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        return params.get(KEY_PARAMETER) != null;\n    }\n\n    public int getShutterSpeedValue() {\n        int retVal = 0;\n        String val = mCamManager.getParameters().get(KEY_PARAMETER);\n        if (val.equals(KEY_AUTO_VALUE) ){\n            retVal = getMaxShutterSpeedValue() + 1;\n        }\n        else {\n            retVal = Integer.parseInt(mCamManager.getParameters().get(KEY_PARAMETER));\n        }\n        return retVal;\n    }\n\n    public String getShutterSpeedRawValue() {\n        String val = mCamManager.getParameters().get(KEY_PARAMETER);\n        return val;\n    }\n\n    public int getMinShutterSpeedValue() {\n        return Integer.parseInt(mCamManager.getParameters().get(KEY_MIN_PARAMETER));\n    }\n\n    public int getMaxShutterSpeedValue() {\n        return Integer.parseInt(mCamManager.getParameters().get(KEY_MAX_PARAMETER));\n    }\n\n    public void setShutterSpeedValue(int value) {\n        String valueStr = Integer.toString(value);\n        mCamManager.setParameterAsync(KEY_PARAMETER, valueStr);\n        SettingsStorage.storeCameraSetting(mWidget.getContext(), mCamManager.getCurrentFacing(),\n                KEY_PARAMETER, valueStr);\n        mValueLabel.setText(getShutterSpeedDisplayValue(valueStr));\n        mAutoButton.resetImage();\n    }\n\n    public void setAutoShutterSpeed() {\n        mCamManager.setParameterAsync(KEY_PARAMETER, KEY_AUTO_VALUE);\n        SettingsStorage.storeCameraSetting(mWidget.getContext(), mCamManager.getCurrentFacing(),\n                KEY_PARAMETER, \"auto\");\n        mValueLabel.setText(getShutterSpeedDisplayValue(KEY_AUTO_VALUE));\n        mAutoButton.activeImage(KEY_PARAMETER + \":\" + KEY_AUTO_VALUE);\n    }\n\n\n    public String getShutterSpeedDisplayValue(String value) {\n        String[] values = mWidget.getContext().getResources().getStringArray(\n                R.array.widget_shutter_speed_display_values);\n        if (value == null || value.equals(KEY_AUTO_VALUE)) {\n            return values[0];\n        } else {\n            return values[Integer.parseInt(value) + 1];\n        }\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/SimpleToggleWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.hardware.Camera;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.CameraManager;\nimport org.cyanogenmod.focal.SettingsStorage;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Base for simple widgets simply changing a parameter\n * (eg. flash=on/off/auto, iso=100/200/300/500/800) from\n * a simple button.\n */\npublic class SimpleToggleWidget extends WidgetBase implements OnClickListener {\n    public final static String TAG = \"SimpleToggleWidget\";\n\n    private String mKey;\n    private boolean mVideoOnly;\n\n    private Map<WidgetBase.WidgetOptionButton, String> mButtonsValues;\n    private Context mContext;\n    private WidgetOptionButton mActiveButton;\n\n    public SimpleToggleWidget(CameraManager cam, Context context, int sidebarIcon) {\n        super(cam, context, sidebarIcon);\n\n        mButtonsValues = new HashMap<WidgetBase.WidgetOptionButton, String>();\n        mContext = context;\n    }\n\n    public SimpleToggleWidget(CameraManager cam, Context context, String key, int sidebarIcon) {\n        super(cam, context, sidebarIcon);\n\n        mButtonsValues = new HashMap<WidgetBase.WidgetOptionButton, String>();\n        mContext = context;\n        mKey = key;\n    }\n\n    /**\n     * Defines or redefines the key used for the settings\n     *\n     * @param key\n     */\n    protected void setKey(String key) {\n        mKey = key;\n    }\n\n    /**\n     * Sets whether or not this setting applies only in video mode\n     *\n     * @param videoOnly\n     */\n    protected void setVideoOnly(boolean videoOnly) {\n        mVideoOnly = videoOnly;\n    }\n\n    /**\n     * Add a value that can be toggled to the widget\n     * If the HAL reports a [key]-values array, it will check and filter\n     * the values against this array. Otherwise, all the values are added\n     * to the list.\n     * <p/>\n     * You might want to check for device-specific values that aren't\n     * reported in -values in the child class before doing addValue\n     *\n     * @param value The value that the key of this widget can take\n     * @param resId The icon that represents it\n     * @param hint  The hint text that appears when long-pressing the button\n     */\n    public void addValue(String value, int resId, String hint) {\n        Camera.Parameters params = mCamManager.getParameters();\n\n        if (params == null) return;\n\n        String values = params.get(mKey + \"-values\");\n\n        // If we don't have a -values provided, or if it contains the value, add it.\n        if ((values == null || Arrays.asList(values.split(\",\")).contains(value))\n                && filterDeviceSpecific(value)) {\n            WidgetBase.WidgetOptionButton button =\n                    new WidgetBase.WidgetOptionButton(resId, mContext);\n            button.setOnClickListener(this);\n            button.setHintText(hint);\n            mButtonsValues.put(button, value);\n            addViewToContainer(button);\n\n            String currentValue = params.get(mKey);\n            if (currentValue != null && currentValue.equals(value)) {\n                setButtonActivated(button, value);\n            }\n        } else {\n            Log.v(TAG, \"Device doesn't support \" + value + \" for setting \" + mKey);\n        }\n    }\n\n    /**\n     * Updates the currently selected button based on the current Camera parameters\n     */\n    public void updateSelectedButton() {\n        String value = getCameraValue(mKey);\n        updateSelectedButton(value);\n    }\n\n    public void updateSelectedButton(String value) {\n        Set<WidgetOptionButton> buttons = mButtonsValues.keySet();\n        for (WidgetOptionButton btn : buttons) {\n            if (mButtonsValues.get(btn).equals(value)) {\n                Log.v(TAG, \"Set button activated for value \" + value);\n                setButtonActivated(btn, value);\n                return;\n            }\n        }\n\n        setButtonActivated(null, value);\n    }\n\n    /**\n     * Restore the camera parameter from the stored settings, and update the selected\n     * button (method of WidgetBase overridden in SimpleToggleWidget)\n     * @param key\n     */\n    @Override\n    public String restoreValueFromStorage(String key) {\n        String value = super.restoreValueFromStorage(key);\n        updateSelectedButton(value);\n\n        return value;\n    }\n\n    /**\n     * This method can be overridden by each widgets. If some keys doesn't\n     * have a \"-values\" array, we can filter eventual device-specific incompatibilities\n     * in this method.\n     *\n     * @param value The value tested for support\n     * @return true if the value is supported, false if it's not\n     */\n    public boolean filterDeviceSpecific(String value) {\n        return true;\n    }\n\n    /**\n     * This method checks if the Key provided in the constructor\n     * is supported (as per {@link WidgetBase} docs), by checking\n     * if the key is in the Camera.Parameters array.\n     */\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        if (CameraActivity.getCameraMode() != CameraActivity.CAMERA_MODE_VIDEO && mVideoOnly) {\n            // This setting is only in video mode\n            return false;\n        }\n\n        if (mButtonsValues.isEmpty() || mButtonsValues.size() == 1) {\n            // There's only one setting, or it's empty, so not worth showing an icon/menu\n            return false;\n        }\n\n        if (params.get(mKey) != null) {\n            Log.v(TAG, \"The device supports '\" + mKey + \"'\");\n\n            String values = params.get(mKey + \"-values\");\n            if (values != null) {\n                Log.v(TAG, \"... and has these possible values: \" + values);\n\n                if (!values.contains(\",\")) {\n                    // There is only one value, no need to show that button\n                    return false;\n                }\n            } else {\n                Log.w(TAG, \"... but we don't know the possible values for \" + mKey);\n            }\n            return true;\n        } else {\n            Log.d(TAG, \"The device doesn't support '\" + mKey + \"'\");\n            return false;\n        }\n    }\n\n    @Override\n    public void onClick(View v) {\n        // We check what button was pressed, and enable it\n        WidgetBase.WidgetOptionButton button = (WidgetBase.WidgetOptionButton) v;\n        String value = mButtonsValues.get(button);\n\n        if (value != null) {\n            SettingsStorage.storeCameraSetting(mContext,\n                    mCamManager.getCurrentFacing(), mKey, value);\n            mCamManager.setParameterAsync(mKey, value);\n            setButtonActivated(button, value);\n        } else {\n            Log.e(TAG, \"Unknown value for this button!\");\n        }\n    }\n\n    private void setButtonActivated(WidgetOptionButton button, String value) {\n        if (mActiveButton != null) {\n            mActiveButton.resetImage();\n        }\n\n        mActiveButton = button;\n        if (button != null) {\n            mActiveButton.activeImage(mKey + \"=\" + value);\n        }\n    }\n\n    /**\n     * Inflate the buttons and icons from the provided values, icons and hints\n     * arrays.\n     *\n     * @param valuesArray\n     * @param iconsArray\n     * @param hintsArray\n     */\n    public void inflateFromXml(int valuesArray, int iconsArray, int hintsArray) {\n        String[] values = mContext.getResources().getStringArray(valuesArray);\n        String[] hints = mContext.getResources().getStringArray(hintsArray);\n        TypedArray icons = mContext.getResources().obtainTypedArray(iconsArray);\n\n        for (int i = 0; i < values.length; i++) {\n            addValue(values[i], icons.getResourceId(i, -1), hints[i]);\n        }\n    }\n\n    /**\n     * This method can be overridden by a child class, to do special actions\n     * when a value is set.\n     *\n     * @param value The value set to the key\n     */\n    public void onValueSet(String value) {\n\n    }\n\n    /**\n     * This checks if the widget is enabled\n     * This is basically a hash (key, value pair)\n     * @return  true if it enabled\n     */\n    public static boolean isWidgetEnabled(Context context,\n                                   CameraManager mCamManager,\n                                   String selectedKey, String value){\n        String out = SettingsStorage.getCameraSetting(context,\n                        mCamManager.getCurrentFacing(), selectedKey, \"null\");\n        return out.equals(value);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/SkinToneWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\nimport android.view.View;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SettingsStorage;\n\n/**\n * Skin Tone Enhancement widget (Qualcomm)\n * Most info (values, etc) from\n * https://codeaurora.org/cgit/quic/la/platform/packages/apps/Camera/commit/?id=9d48710275915ac0672cb7c1bc1b9aa14d70e8b0\n */\npublic class SkinToneWidget extends WidgetBase {\n    private static final String KEY_PARAMETER = \"skinToneEnhancement\";\n    private static final int MIN_SCE_FACTOR = -10;\n    private static final int MAX_SCE_FACTOR = +10;\n\n    private WidgetOptionButton mMinusButton;\n    private WidgetOptionButton mPlusButton;\n    private WidgetOptionLabel mValueLabel;\n\n\n    private class MinusClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View view) {\n            setToneValue(Math.max(getToneValue() - 1, MIN_SCE_FACTOR));\n        }\n    }\n\n    private class PlusClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View view) {\n            setToneValue(Math.min(getToneValue() + 1, MAX_SCE_FACTOR));\n        }\n    }\n\n    public SkinToneWidget(CameraManager cam, Context context) {\n        super(cam, context, R.drawable.ic_widget_skintone);\n\n        // Add views in the widget\n        mMinusButton = new WidgetOptionButton(R.drawable.ic_widget_timer_minus, context);\n        mPlusButton = new WidgetOptionButton(R.drawable.ic_widget_timer_plus, context);\n        mValueLabel = new WidgetOptionLabel(context);\n\n        mMinusButton.setOnClickListener(new MinusClickListener());\n        mPlusButton.setOnClickListener(new PlusClickListener());\n\n        addViewToContainer(mMinusButton);\n        addViewToContainer(mValueLabel);\n        addViewToContainer(mPlusButton);\n\n        restoreValueFromStorage(KEY_PARAMETER);\n        mValueLabel.setText(Integer.toString(getToneValue()));\n\n        getToggleButton().setHintText(R.string.widget_skintone);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        return params.get(KEY_PARAMETER) != null;\n    }\n\n    public int getToneValue() {\n        Camera.Parameters params = mCamManager.getParameters();\n        String value = params.get(KEY_PARAMETER);\n\n        if (value != null) {\n            return Integer.parseInt(value);\n        } else {\n            return 0;\n        }\n    }\n\n    public void setToneValue(int value) {\n        String valueStr = Integer.toString(value);\n\n        mCamManager.setParameterAsync(KEY_PARAMETER, valueStr);\n        SettingsStorage.storeCameraSetting(mWidget.getContext(), mCamManager.getCurrentFacing(),\n                KEY_PARAMETER, valueStr);\n        mValueLabel.setText(valueStr);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/SoftwareHdrWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.hardware.Camera;\nimport android.view.View;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.feats.SoftwareHdrCapture;\n\nimport java.util.List;\n\n/**\n * Software HDR widget\n * This is distinct from the hardware HDR to avoid putting everything in one same class\n * and have to hack around the XML inflation. This widget only shows up if neither HDR\n * scene-mode is present and there is no AE-Bracket-HDR setting. If there are new\n * methods of having HDR, you'll have to add them in arrays.xml.\n */\npublic class SoftwareHdrWidget extends WidgetBase implements View.OnClickListener {\n    private CameraActivity mContext;\n    private WidgetOptionButton mBtnOn;\n    private WidgetOptionButton mBtnOff;\n    private WidgetOptionButton mPreviousMode;\n    private SoftwareHdrCapture mTransformer;\n    private final static String DRAWABLE_TAG = \"nemesis-soft-hdr\";\n\n    public SoftwareHdrWidget(CameraActivity activity) {\n        super(activity.getCamManager(), activity, R.drawable.ic_widget_hdr);\n        mContext = activity;\n\n        mBtnOn = new WidgetOptionButton(R.drawable.ic_widget_hdr_on, mContext);\n        mBtnOff = new WidgetOptionButton(R.drawable.ic_widget_hdr_off, mContext);\n        mBtnOn.setOnClickListener(this);\n        mBtnOff.setOnClickListener(this);\n\n        addViewToContainer(mBtnOn);\n        addViewToContainer(mBtnOff);\n\n        mPreviousMode = mBtnOff;\n        mPreviousMode.activeImage(DRAWABLE_TAG + \"=off\");\n\n        mTransformer = new SoftwareHdrCapture(mContext);\n\n        getToggleButton().setHintText(R.string.widget_softwarehdr);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        // Software HDR only in photo mode!\n        if (CameraActivity.getCameraMode() != CameraActivity.CAMERA_MODE_PHOTO) {\n            mTransformer.tearDown();\n            return false;\n        }\n\n        // We hide the software HDR widget if either we have scene-mode=hdr,\n        // or any of the HDR setting key defined\n        List<String> sceneModes = params.getSupportedSceneModes();\n        if (sceneModes != null && sceneModes.contains(\"hdr\")) {\n            mTransformer.tearDown();\n            return false;\n        }\n\n        String[] keys = mContext.getResources().getStringArray(R.array.hardware_hdr_keys);\n        for (String key : keys) {\n            if (params.get(key) != null) {\n                mTransformer.tearDown();\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    public void onClick(View view) {\n        if (view == mBtnOn) {\n            mPreviousMode.resetImage();\n            mBtnOn.activeImage(DRAWABLE_TAG + \"=on\");\n            mPreviousMode = mBtnOn;\n            mContext.setCaptureTransformer(mTransformer);\n        } else if (view == mBtnOff) {\n            mPreviousMode.resetImage();\n            mBtnOff.activeImage(DRAWABLE_TAG + \"=off\");\n            mPreviousMode = mBtnOff;\n            mContext.setCaptureTransformer(null);\n        }\n    }\n\n    public void render() {\n\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/TimerModeWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.hardware.Camera;\nimport android.view.View;\n\nimport org.cyanogenmod.focal.CameraActivity;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.feats.TimerCapture;\n\n/**\n * Timer mode and voice shutter widget\n */\npublic class TimerModeWidget extends WidgetBase implements View.OnClickListener {\n    private final static String DRAWABLE_TAG = \"nemesis-timer-mode\";\n    private final static int TIMER_MIN_VALUE = 1;\n    private final static int TIMER_MAX_VALUE = 60;\n\n    private WidgetOptionButton mBtnToggle;\n    private WidgetOptionButton mBtnVoice;\n    private WidgetOptionButton mBtnPlus;\n    private WidgetOptionButton mBtnMinus;\n    private WidgetOptionLabel mLabel;\n    private CameraActivity mCameraActivity;\n    private TimerCapture mTransformer;\n    private boolean mIsEnabled;\n\n    public TimerModeWidget(CameraActivity activity) {\n        super(activity.getCamManager(), activity, R.drawable.ic_widget_timer);\n\n        mCameraActivity = activity;\n        mIsEnabled = false;\n\n        // Create options\n        // XXX: Move that into an XML\n        mBtnToggle = new WidgetOptionButton(R.drawable.ic_widget_timer, activity);\n        mBtnVoice = new WidgetOptionButton(R.drawable.ic_widget_timer_voice, activity);\n        mBtnMinus = new WidgetOptionButton(R.drawable.ic_widget_timer_minus, activity);\n        mBtnPlus = new WidgetOptionButton(R.drawable.ic_widget_timer_plus, activity);\n        mLabel = new WidgetOptionLabel(activity);\n\n        addViewToContainer(mBtnVoice);\n        addViewToContainer(mBtnToggle);\n        addViewToContainer(mBtnMinus);\n        addViewToContainer(mLabel);\n        addViewToContainer(mBtnPlus);\n\n        mBtnToggle.setOnClickListener(this);\n        mBtnVoice.setOnClickListener(this);\n        mBtnMinus.setOnClickListener(this);\n        mBtnPlus.setOnClickListener(this);\n\n        mTransformer = new TimerCapture(activity);\n        mLabel.setText(Integer.toString(mTransformer.getTimer()));\n\n        getToggleButton().setHintText(R.string.widget_timermode);\n    }\n\n    @Override\n    public boolean isSupported(Camera.Parameters params) {\n        // Timer mode is supported by everything. If we are in photo mode that is.\n        if (CameraActivity.getCameraMode() == CameraActivity.CAMERA_MODE_PHOTO) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    private void turnOn() {\n        if (!mIsEnabled || mTransformer.getTimer() == TimerCapture.VOICE_TIMER_VALUE) {\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtnToggle.activeImage(DRAWABLE_TAG + \"=on\");\n            mIsEnabled = true;\n\n            if (mTransformer.getTimer() == TimerCapture.VOICE_TIMER_VALUE) {\n                mTransformer.setTimer(5); // some default value\n                mBtnVoice.resetImage();\n            }\n        }\n    }\n\n    private void turnOff(boolean nullizeTransformer) {\n        if (mIsEnabled) {\n            if (nullizeTransformer) {\n                mCameraActivity.setCaptureTransformer(null);\n            }\n            mBtnToggle.resetImage();\n            mIsEnabled = false;\n        }\n    }\n\n    @Override\n    public void onClick(View view) {\n        if (view == mBtnToggle) {\n            // Toggle the transformer\n            if (mIsEnabled && mTransformer.getTimer() != TimerCapture.VOICE_TIMER_VALUE) {\n                turnOff(true);\n            } else {\n                turnOn();\n            }\n        } else if (view == mBtnMinus) {\n            turnOn();\n            mTransformer.setTimer(clampTimer(mTransformer.getTimer()-1));\n            mLabel.setText(Integer.toString(mTransformer.getTimer()));\n        } else if (view == mBtnPlus) {\n            turnOn();\n            mTransformer.setTimer(clampTimer(mTransformer.getTimer()+1));\n            mLabel.setText(Integer.toString(mTransformer.getTimer()));\n        } else if (view == mBtnVoice) {\n            if (mIsEnabled && mTransformer.getTimer() == TimerCapture.VOICE_TIMER_VALUE) {\n                mBtnVoice.resetImage();\n                turnOff(true);\n                return;\n            } else if (mIsEnabled && mTransformer.getTimer() != TimerCapture.VOICE_TIMER_VALUE) {\n                mBtnToggle.resetImage();\n            }\n\n            mTransformer.setTimer(TimerCapture.VOICE_TIMER_VALUE);\n            mCameraActivity.setCaptureTransformer(mTransformer);\n            mBtnVoice.activeImage(DRAWABLE_TAG + \"=voice\");\n            mIsEnabled = true;\n        }\n    }\n\n    private int clampTimer(int value) {\n        value = Math.max(TIMER_MIN_VALUE, value);\n        value = Math.min(TIMER_MAX_VALUE, value);\n        return value;\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/VideoFrWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\npublic class VideoFrWidget extends SimpleToggleWidget {\n\n    public VideoFrWidget(CameraManager cam, Context context) {\n        super(cam, context, \"video-hfr\", R.drawable.ic_widget_videofr);\n\n        // For video only\n        setVideoOnly(true);\n\n        // TODO: Needs filtering depending on resolution (see video-hfr-size)\n        addValue(\"off\", R.drawable.ic_widget_videofr_30, \"30 FPS\");\n        addValue(\"60\", R.drawable.ic_widget_videofr_60, \"60 FPS\");\n        addValue(\"90\", R.drawable.ic_widget_videofr_90, \"90 FPS\");\n        addValue(\"120\", R.drawable.ic_widget_videofr_120, \"120 FPS\");\n\n        getToggleButton().setHintText(R.string.widget_videofr);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/VideoHdrWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\nimport android.hardware.Camera;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * Video HDR widget\n * - On non-sony QCOM devices, video hdr is toggled by \"video-hdr\"\n * - On Sony devices, video hdr is toggled by \"sony-video-hdr\"\n */\npublic class VideoHdrWidget extends SimpleToggleWidget {\n    private static final String KEY_QCOM_VIDEO_HDR = \"video-hdr\";\n    private static final String KEY_SONY_VIDEO_HDR = \"sony-video-hdr\";\n\n    public VideoHdrWidget(CameraManager cam, Context context) {\n        super(cam, context, R.drawable.ic_widget_placeholder); // TODO: Icon, video hdr\n\n        setVideoOnly(true);\n\n        // We cannot inflate from XML here, because there are device-specific keys and values\n        Camera.Parameters params = mCamManager.getParameters();\n        if (params == null) {\n            return;\n        }\n\n        if (params.get(KEY_SONY_VIDEO_HDR) != null) {\n            // Use Sony values\n            setKey(KEY_SONY_VIDEO_HDR);\n            addValue(\"off\", R.drawable.ic_widget_hdr_off, context.getString(R.string.disabled));\n            addValue(\"on\", R.drawable.ic_widget_hdr_on, context.getString(R.string.enabled));\n        } else if (params.get(KEY_QCOM_VIDEO_HDR) != null) {\n            // Use Qcom values\n            setKey(KEY_QCOM_VIDEO_HDR);\n            addValue(\"0\", R.drawable.ic_widget_hdr_off, context.getString(R.string.disabled));\n            addValue(\"1\", R.drawable.ic_widget_hdr_on, context.getString(R.string.enabled));\n        }\n\n        getToggleButton().setHintText(R.string.widget_videohdr);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/WhiteBalanceWidget.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.content.Context;\n\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\n\n/**\n * White Balance Widget, manages the wb settings\n */\npublic class WhiteBalanceWidget extends SimpleToggleWidget {\n    private static final String KEY_WHITEBALANCE = \"whitebalance\";\n\n    public WhiteBalanceWidget(CameraManager cam, Context context) {\n        super(cam, context, KEY_WHITEBALANCE, R.drawable.ic_widget_wb_auto);\n        inflateFromXml(R.array.widget_whitebalance_values, R.array.widget_whitebalance_icons,\n                R.array.widget_whitebalance_hints);\n        getToggleButton().setHintText(R.string.widget_whitebalance);\n        restoreValueFromStorage(KEY_WHITEBALANCE);\n    }\n}\n"
  },
  {
    "path": "src/org/cyanogenmod/focal/widgets/WidgetBase.java",
    "content": "/*\n * Copyright (C) 2013 Guillaume Lesniak\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA  02110-1301, USA.\n */\n\npackage org.cyanogenmod.focal.widgets;\n\nimport android.animation.Animator;\nimport android.animation.Animator.AnimatorListener;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.hardware.Camera;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewPropertyAnimator;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.Button;\nimport android.widget.FrameLayout;\nimport android.widget.GridLayout;\nimport android.widget.ImageButton;\nimport android.widget.ImageView;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport org.cyanogenmod.focal.BitmapFilter;\nimport org.cyanogenmod.focal.CameraActivity;\nimport org.cyanogenmod.focal.CameraManager;\nimport fr.xplod.focal.R;\nimport org.cyanogenmod.focal.SettingsStorage;\nimport org.cyanogenmod.focal.Util;\nimport org.cyanogenmod.focal.ui.CenteredSeekBar;\nimport org.cyanogenmod.focal.ui.WidgetRenderer;\n\n/**\n * Base class for settings widget. Each setting widget\n * will extend this class.\n */\npublic abstract class WidgetBase {\n    public final static String TAG = \"WidgetBase\";\n\n    protected CameraManager mCamManager;\n    protected WidgetToggleButton mToggleButton;\n    protected WidgetToggleButton mShortcutButton;\n    protected WidgetContainer mWidget;\n    private int mWidgetMaxWidth;\n    private boolean mIsOpen;\n\n    private final static int WIDGET_FADE_DURATION_MS = 200;\n    private final static float WIDGET_FADE_SCALE = 0.75f;\n\n\n\n    private class ToggleClickListener implements View.OnClickListener {\n        @Override\n        public void onClick(View v) {\n            if (isOpen()) {\n                WidgetBase.this.close();\n            } else {\n                WidgetBase.this.open();\n            }\n        }\n    }\n\n    public WidgetBase(CameraManager cam, Context context, int iconResId) {\n        mCamManager = cam;\n        mWidget = new WidgetContainer(context);\n        mToggleButton = new WidgetToggleButton(context);\n        mShortcutButton = new WidgetToggleButton(context);\n        mShortcutButton.setShortcut(true);\n        mIsOpen = false;\n\n        // Setup the toggle button\n        mToggleButton.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0);\n        mShortcutButton.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0);\n        mWidget.setIconId(iconResId);\n\n        mWidgetMaxWidth = context.getResources().getInteger(R.integer.widget_default_max_width);\n\n        setHidden(!SettingsStorage.getVisibilitySetting(\n                context, this.getClass().getName()));\n    }\n\n    /**\n     * This method returns whether or not the widget settings are\n     * supported by the camera HAL or not.\n     * <p/>\n     * This method must be overridden by each widget, in order to hide\n     * widgets that are not supported by the Camera device.\n     *\n     * @param params The Camera parameters provided by HAL\n     * @return true if the widget is supported by the device\n     */\n    public abstract boolean isSupported(Camera.Parameters params);\n\n    /**\n     * Add a view to the container widget\n     *\n     * @param v The view to add\n     */\n    public void addViewToContainer(View v) {\n        if (mWidget.getGrid().getRowCount() * mWidget.getGrid().getColumnCount() < mWidget.getGrid().getChildCount() + 1) {\n            if (mWidget.getGrid().getColumnCount() == mWidgetMaxWidth) {\n                // Add a new line instead of a column to fit the max width\n                mWidget.getGrid().setRowCount(mWidget.getGrid().getRowCount() + 1);\n            } else {\n                mWidget.getGrid().setColumnCount(mWidget.getGrid().getColumnCount() + 1);\n            }\n        }\n\n        mWidget.getGrid().addView(v);\n    }\n\n    /**\n     * Force the size of the grid container to the specified\n     * row and column count. The views can then be added more precisely\n     * to the container using setViewAt()\n     *\n     * @param row\n     * @param column\n     */\n    public void forceGridSize(int row, int column) {\n        mWidget.getGrid().setColumnCount(column);\n        mWidget.getGrid().setRowCount(row);\n    }\n\n    /**\n     * Sets the view v at the specific row/column combo slot.\n     * Pretty much useful only if you used forceGridSize\n     *\n     * @param row\n     * @param column\n     * @param v\n     */\n    public void setViewAt(int row, int column, int rowSpan, int colSpan, View v) {\n        // Do some safety checks first\n        if (mWidget.getGrid().getRowCount() < row + rowSpan) {\n            mWidget.getGrid().setRowCount(row + rowSpan);\n        }\n        if (mWidget.getGrid().getColumnCount() < column + colSpan) {\n            mWidget.getGrid().setColumnCount(column + colSpan);\n        }\n\n        mWidget.getGrid().addView(v);\n\n        GridLayout.LayoutParams layoutParams = (GridLayout.LayoutParams) v.getLayoutParams();\n        layoutParams.columnSpec = GridLayout.spec(column, colSpan);\n        layoutParams.rowSpec = GridLayout.spec(row, rowSpan);\n        layoutParams.setGravity(Gravity.FILL);\n        layoutParams.width = colSpan * v.getMinimumWidth();\n        v.setLayoutParams(layoutParams);\n    }\n\n    public WidgetToggleButton getToggleButton() {\n        return mToggleButton;\n    }\n\n    public WidgetToggleButton getShortcutButton() {\n        return mShortcutButton;\n    }\n\n    public WidgetContainer getWidget() {\n        return mWidget;\n    }\n\n    public boolean isOpen() {\n        return mIsOpen;\n    }\n\n    /**\n     * Restores the value of this widget from the database to the Camera's preferences\n     */\n    public String restoreValueFromStorage(String key) {\n        Camera.Parameters params = mCamManager.getParameters();\n        if (params == null) {\n            return \"\";\n        }\n\n        String currentValue = params.get(key);\n        if (currentValue != null) {\n            String storageValue = SettingsStorage.getCameraSetting(mWidget.getContext(),\n                    mCamManager.getCurrentFacing(), key, currentValue);\n            mCamManager.setParameterAsync(key, storageValue);\n\n            return storageValue;\n        }\n\n        return currentValue;\n    }\n\n    /**\n     * @param key The name of the parameter to get\n     * @return The parameter value, or null if it doesn't exist\n     */\n    public String getCameraValue(String key) {\n        Camera.Parameters params = mCamManager.getParameters();\n        return params.get(key);\n    }\n\n    /**\n     * Notifies the WidgetBase that the orientation has changed. WidgetBase doesn't\n     * do anything, but specific widgets might need orientation information (such as\n     * SettingsWidget to rotate the dialog).\n     *\n     * @param orientation The screen orientation\n     */\n    public void notifyOrientationChanged(int orientation) {\n\n    }\n\n    /**\n     * Opens the widget, show the fade in animation\n     */\n    public void open() {\n        WidgetRenderer parent = (WidgetRenderer) mWidget.getParent();\n\n        if (parent == null) return;\n\n        mWidget.setVisibility(View.VISIBLE);\n        mWidget.invalidate();\n\n        mIsOpen = true;\n\n        parent.widgetOpened(mWidget);\n\n        // Set initial widget state\n        mWidget.setAlpha(0.0f);\n        mWidget.setScaleX(WIDGET_FADE_SCALE);\n        mWidget.setScaleY(WIDGET_FADE_SCALE);\n        mWidget.setX(0);\n\n        mWidget.setY(Util.dpToPx(mWidget.getContext(), 8));\n\n        mWidget.animate().alpha(1.0f).scaleX(1.0f).scaleY(1.0f)\n                .setDuration(WIDGET_FADE_DURATION_MS).start();\n\n        mShortcutButton.toggleBackground(true);\n        mToggleButton.toggleBackground(true);\n    }\n\n    public void close() {\n        WidgetRenderer parent = (WidgetRenderer) mWidget.getParent();\n        if (parent != null) {\n            parent.widgetClosed(mWidget);\n        }\n\n        mWidget.animate().alpha(0.0f).scaleX(WIDGET_FADE_SCALE).scaleY(WIDGET_FADE_SCALE)\n                .setDuration(WIDGET_FADE_DURATION_MS).start();\n\n        mShortcutButton.toggleBackground(false);\n        mToggleButton.toggleBackground(false);\n        mIsOpen = false;\n    }\n\n    public void setHidden(boolean hide) {\n        mToggleButton.setVisibility(hide ? View.GONE : View.VISIBLE);\n    }\n\n    public boolean isHidden() {\n        return mToggleButton.getVisibility() == View.GONE;\n    }\n\n    /**\n     * Represents the button that toggles the widget, in the\n     * side bar.\n     */\n    public class WidgetToggleButton extends Button {\n        private float mDownX;\n        private float mDownY;\n        private boolean mCancelOpenOnDown;\n        private String mHintText;\n        private boolean mIsShortcut = false;\n\n        public WidgetToggleButton(Context context, AttributeSet attrs,\n                                  int defStyle) {\n            super(context, attrs, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize();\n        }\n\n        public WidgetToggleButton(Context context, AttributeSet attrs) {\n            super(context, attrs, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize();\n        }\n\n        public WidgetToggleButton(Context context) {\n            super(context, null, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize();\n        }\n\n        private void initialize() {\n            Resources res = getResources();\n            if (res == null) return;\n\n            this.setClickable(true);\n\n            int pad = res.getDimensionPixelSize(R.dimen.widget_toggle_button_padding);\n            this.setPadding(pad, pad, pad, pad);\n\n            this.setOnClickListener(new ToggleClickListener());\n            this.setOnLongClickListener(new LongClickListener());\n\n            setTextSize(11);\n            setGravity(Gravity.CENTER);\n            setLines(2);\n\n            int pxSize = (int) Util.dpToPx(getContext(), 82);\n            setMaxWidth(pxSize);\n            setMinWidth(pxSize);\n        }\n\n        public void setHintText(String text) {\n            mHintText = text;\n            if (!mIsShortcut) {\n                setText(text);\n            }\n        }\n\n        public void setHintText(int resId) {\n            mHintText = getResources().getString(resId);\n            if (!mIsShortcut) {\n                setText(mHintText);\n            }\n        }\n\n        public String getHintText() {\n            return mHintText;\n        }\n\n        public void toggleBackground(boolean active) {\n            if (active) {\n                this.setBackgroundColor(getResources().getColor(R.color.widget_toggle_active));\n            } else {\n                this.setBackgroundColor(0);\n            }\n        }\n\n        @Override\n        public boolean onTouchEvent(MotionEvent event) {\n            // to dispatch click / long click event, we do it first\n            if (event.getActionMasked() == MotionEvent.ACTION_UP && mCancelOpenOnDown) {\n                toggleBackground(mIsOpen);\n                return true;\n            } else {\n                super.onTouchEvent(event);\n            }\n\n            // Handle the background color on touch\n            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {\n                this.setBackgroundColor(getResources().getColor(R.color.widget_toggle_pressed));\n                mCancelOpenOnDown = false;\n                mDownX = event.getX();\n                mDownY = event.getY();\n            } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {\n                // State is not updated yet, but after ACTION_UP is received the button\n                // will likely be clicked and its state will change.\n                toggleBackground(!mIsOpen);\n            } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {\n                if (!mIsOpen && (Math.abs(event.getX() - mDownX) > 8\n                        || Math.abs(event.getY() - mDownY) > 8)) {\n                    toggleBackground(false);\n                }\n            }\n\n            return true;\n        }\n\n        public void setShortcut(boolean b) {\n            mIsShortcut = b;\n            if (b) {\n                setText(\"\");\n                int pxSize = (int) Util.dpToPx(getContext(), 72);\n                setMaxWidth(pxSize);\n                setMinWidth(pxSize);\n                setMaxHeight(pxSize / 2);\n            }\n        }\n\n        private class LongClickListener implements OnLongClickListener {\n            @Override\n            public boolean onLongClick(View view) {\n                mCancelOpenOnDown = true;\n                if (mHintText != null && !mHintText.isEmpty()) {\n                    CameraActivity.notify(mHintText, 2000, getX(), Util.getScreenSize(null).y\n                            - getHeight() - getBottom());\n                }\n\n                toggleBackground(mIsOpen);\n                return true;\n            }\n        }\n\n\n    }\n\n    /**\n     * Interface that all widget options must implement. Give info\n     * about the layout of the widget.\n     */\n    public interface WidgetOption {\n        /**\n         * Returns the number of columns occupied by the widget.\n         * A button typically occupies 1 column, but specific widgets\n         * like text widget might occupy two columns in some cases.\n         *\n         * @return Number of columns\n         */\n        public int getColSpan();\n\n        /**\n         * Notifies the widget that its orientation changed\n         *\n         * @param orientation The new angle\n         */\n        public void notifyOrientationChanged(int orientation);\n    }\n\n    /**\n     * Represents a standard label (text view) put inside\n     * a {@link WidgetContainer}.\n     */\n    public class WidgetOptionLabel extends TextView implements WidgetOption {\n        public WidgetOptionLabel(Context context, AttributeSet attrs, int defStyle) {\n            super(context, attrs, defStyle);\n            initialize();\n        }\n\n        public WidgetOptionLabel(Context context, AttributeSet attrs) {\n            super(context, attrs);\n            initialize();\n        }\n\n        public WidgetOptionLabel(Context context) {\n            super(context);\n            initialize();\n        }\n\n        private void initialize() {\n            setMinimumWidth(getResources().getDimensionPixelSize(\n                    R.dimen.widget_option_button_size));\n            setMinimumHeight(getResources().getDimensionPixelSize(\n                    R.dimen.widget_option_button_size));\n            int padding = getResources().getDimensionPixelSize(\n                    R.dimen.widget_option_button_padding);\n            setPadding(padding, padding, padding, padding);\n            setGravity(Gravity.CENTER);\n\n            setTextSize(getResources().getInteger(R.integer.widget_option_label_font_size));\n        }\n\n        @Override\n        public void onFinishInflate() {\n            super.onFinishInflate();\n            GridLayout.LayoutParams params = (GridLayout.LayoutParams) this.getLayoutParams();\n            params.setGravity(Gravity.CENTER);\n        }\n\n        public void setSmall(boolean small) {\n            if (small) {\n                setTextSize(getResources().getInteger(\n                        R.integer.widget_option_label_font_size_small));\n            } else {\n                setTextSize(getResources().getInteger(R.integer.widget_option_label_font_size));\n            }\n        }\n\n        @Override\n        public int getColSpan() {\n            // TODO: Return a different colspan if label larger\n            return 1;\n        }\n\n        @Override\n        public void notifyOrientationChanged(int orientation) {\n\n        }\n    }\n\n    /**\n     * Represents a non-clickable imageview put inside\n     * a {@link WidgetContainer}.\n     */\n    public class WidgetOptionImage extends ImageView implements WidgetOption {\n        public WidgetOptionImage(int resId, Context context, AttributeSet attrs, int defStyle) {\n            super(context, attrs, defStyle);\n            initialize(resId);\n        }\n\n        public WidgetOptionImage(int resId, Context context, AttributeSet attrs) {\n            super(context, attrs);\n            initialize(resId);\n        }\n\n        public WidgetOptionImage(int resId, Context context) {\n            super(context);\n            initialize(resId);\n        }\n\n        private void initialize(int resId) {\n            Resources res = getResources();\n            if (res == null) return;\n\n            setMinimumWidth(res.getDimensionPixelSize(\n                    R.dimen.widget_option_button_size) / 2);\n            setMaxWidth(getMinimumWidth());\n\n            int padding = res.getDimensionPixelSize(\n                    R.dimen.widget_option_button_padding);\n            setPadding(padding, padding, padding, padding);\n            setImageResource(resId);\n        }\n\n        @Override\n        public int getColSpan() {\n            return 1;\n        }\n\n        @Override\n        public void notifyOrientationChanged(int orientation) {\n\n        }\n    }\n\n    /**\n     * Represents a standard setting button put inside\n     * a {@link WidgetContainer}.\n     * Note that you're not forced to use exclusively buttons,\n     * TextViews through WidgetOptionLabel can also be added (for Timer for instance)\n     */\n    public class WidgetOptionButton extends Button implements WidgetOption,\n            View.OnLongClickListener {\n        private float mTouchOffset = 0.0f;\n        private BitmapDrawable mOriginalDrawable;\n        private String mHintText;\n\n\n        public WidgetOptionButton(int resId, Context context, AttributeSet attrs,\n                                  int defStyle) {\n            super(context, attrs, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize(resId);\n        }\n\n        public WidgetOptionButton(int resId, Context context, AttributeSet attrs) {\n            super(context, attrs, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize(resId);\n\n        }\n\n        public WidgetOptionButton(int resId, Context context) {\n            super(context, null, android.R.style.Widget_DeviceDefault_Button_Borderless_Small);\n            initialize(resId);\n        }\n\n        public void resetImage() {\n            this.setCompoundDrawablesRelativeWithIntrinsicBounds(null, mOriginalDrawable, null, null);\n        }\n\n        public void setHintText(String hint) {\n            mHintText = hint;\n            this.setText(mHintText);\n        }\n\n        public void setHintText(int resId) {\n            Resources res = getResources();\n            if (res == null) return;\n\n            mHintText = res.getString(resId);\n            this.setText(mHintText);\n        }\n\n        public void activeImage(String key) {\n            Resources res = getResources();\n            if (res == null) return;\n\n            this.setCompoundDrawablesRelativeWithIntrinsicBounds(null,\n                    new BitmapDrawable(res,\n                            BitmapFilter.getSingleton().getGlow(key,\n                                    res.getColor(R.color.widget_option_active),\n                                    mOriginalDrawable.getBitmap())), null, null);\n        }\n\n        private void initialize(int resId) {\n            Resources res = getResources();\n            if (res == null) return;\n\n            mOriginalDrawable = (BitmapDrawable) res.getDrawable(resId);\n            this.setCompoundDrawablesRelativeWithIntrinsicBounds(null, mOriginalDrawable, null, null);\n            this.setGravity(Gravity.CENTER);\n            this.setLines(2);\n\n            int pxHeight = (int) Util.dpToPx(getContext(), 56);\n            int pxWidth = (int) Util.dpToPx(getContext(), 64);\n            this.setMinimumWidth(pxWidth);\n            this.setMinimumHeight(pxHeight);\n            this.setMaxWidth(pxWidth);\n            this.setMaxHeight(pxHeight);\n            this.setSelected(true);\n            this.setFadingEdgeLength((int) Util.dpToPx(getContext(), 6));\n            this.setHorizontalFadingEdgeEnabled(true);\n\n            this.setTextSize(10);\n            setOnLongClickListener(this);\n        }\n\n        @Override\n        public int getColSpan() {\n            return 1;\n        }\n\n        @Override\n        public void notifyOrientationChanged(int orientation) {\n\n        }\n\n        @Override\n        public boolean onLongClick(View view) {\n            if (mHintText != null && mHintText.length() > 0) {\n                CameraActivity.notify(mHintText, 2000, Util.dpToPx(getContext(), 8)\n                        + getX() + mWidget.getX(), Util.dpToPx(getContext(), 32)\n                        + Util.dpToPx(getContext(), 4) * mHintText.length());\n                return true;\n            } else {\n                return false;\n            }\n        }\n    }\n\n    /**\n     * Represents a centered seek bar put inside\n     * a {@link WidgetContainer}.\n     */\n    public class WidgetOptionCenteredSeekBar extends CenteredSeekBar implements WidgetOption {\n        public WidgetOptionCenteredSeekBar(Context context, AttributeSet attrs) {\n            super(context, attrs);\n            initialize();\n        }\n\n        public WidgetOptionCenteredSeekBar(Integer min, Integer max, Context context) {\n            super(min, max, context);\n            initialize();\n        }\n\n        private void initialize() {\n            Resources res = getResources();\n            if (res == null) return;\n\n            setMaxWidth(res.getDimensionPixelSize(\n                    R.dimen.widget_option_button_size) * 3);\n            setMaxHeight(res.getDimensionPixelSize(R.dimen.widget_option_button_size));\n\n            // Set padding\n            int pad = res.getDimensionPixelSize(R.dimen.widget_container_padding);\n            this.setPadding(pad, pad, pad, pad);\n        }\n\n        @Override\n        public void onFinishInflate() {\n            super.onFinishInflate();\n            GridLayout.LayoutParams params = (GridLayout.LayoutParams) this.getLayoutParams();\n\n            if (params != null) {\n                params.setGravity(Gravity.CENTER);\n            }\n        }\n\n        @Override\n        public int getColSpan() {\n            return 3;\n        }\n\n        @Override\n        public void notifyOrientationChanged(int orientation) {\n\n        }\n    }\n\n    /**\n     * Represents a normal seek bar put inside\n     * a {@link WidgetContainer}.\n     */\n    public class WidgetOptionSeekBar extends SeekBar implements WidgetOption {\n\n        public WidgetOptionSeekBar(Context context, AttributeSet attrs) {\n            super(context, attrs);\n            initialize();\n        }\n\n        public WidgetOptionSeekBar(Context context) {\n            super(context);\n            initialize();\n        }\n\n        private void initialize() {\n            Resources res = getResources();\n            if (res == null) return;\n\n            // Set padding\n            int pad = res.getDimensionPixelSize(R.dimen.widget_container_padding) * 2;\n            this.setPadding(pad, pad, pad, pad);\n            this.setMinimumHeight(res.getDimensionPixelSize(\n                    R.dimen.widget_option_button_size) * 3);\n        }\n\n        @Override\n        public void onFinishInflate() {\n            super.onFinishInflate();\n            GridLayout.LayoutParams params = (GridLayout.LayoutParams) this.getLayoutParams();\n\n            if (params != null) {\n                params.setMargins(120, 120, 120, 120);\n                this.setLayoutParams(params);\n            }\n        }\n\n        @Override\n        public int getColSpan() {\n            return 3;\n        }\n\n        @Override\n        public void notifyOrientationChanged(int orientation) {\n\n        }\n    }\n\n    /**\n     * Represents the floating window of a widget.\n     */\n    public class WidgetContainer extends FrameLayout {\n        private float mTouchOffsetY = 0.0f;\n        private float mTouchOffsetX = 0.0f;\n        private float mTouchDownX = 0.0f;\n        private float mTouchDownY = 0.0f;\n        private float mTargetY = 0.0f;\n        private ViewGroup mRootView;\n        private GridLayout mGridView;\n        private ImageView mWidgetIcon;\n        private Button mPinButton;\n        private ImageButton mResetButton;\n        private ViewPropertyAnimator mRootAnimator;\n\n        public WidgetContainer(Context context, AttributeSet attrs,\n                               int defStyle) {\n            super(context, attrs, defStyle);\n            initialize();\n        }\n\n        public WidgetContainer(Context context, AttributeSet attrs) {\n            super(context, attrs);\n            initialize();\n        }\n\n        public WidgetContainer(Context context) {\n            super(context);\n            initialize();\n        }\n\n        /**\n         * Animate the movement on X\n         *\n         * @param y\n         */\n        public void setYSmooth(float y) {\n            if (mTargetY != y) {\n                mRootAnimator.cancel();\n                mRootAnimator.y(y).setDuration(100).start();\n                mTargetY = y;\n            }\n        }\n\n        /**\n         * Returns the final X position, after the animation is done\n         *\n         * @return final X position\n         */\n        public float getFinalY() {\n            return mTargetY;\n        }\n\n        public void forceFinalY(float y) {\n            mTargetY = y;\n        }\n\n        public WidgetBase getWidgetBase() {\n            return WidgetBase.this;\n        }\n\n        @Override\n        public boolean onTouchEvent(MotionEvent event) {\n            boolean handle = false;\n            if (getAlpha() == 0.0f) return false;\n\n            if (event.getAction() == MotionEvent.ACTION_DOWN) {\n                handle = true;\n                mTouchOffsetY = getY() - event.getRawY();\n                mTouchOffsetX = getX() - event.getRawX();\n                mTouchDownX = event.getRawX();\n                mTouchDownY = event.getRawY();\n                WidgetRenderer parent = (WidgetRenderer) mWidget.getParent();\n                parent.widgetPressed(mWidget);\n            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {\n                float driftX = Math.abs(event.getRawX() - mTouchDownX);\n                float driftY = Math.abs(event.getRawY() - mTouchDownY);\n\n                if (driftX < driftY) {\n                    setX(0);\n                    setY(event.getRawY() + mTouchOffsetY);\n                } else {\n                    setY(mTargetY);\n                    setX(event.getRawX() + mTouchOffsetX);\n                }\n\n                setAlpha(1 - driftX / getWidth());\n                WidgetRenderer parent = (WidgetRenderer) mWidget.getParent();\n                parent.widgetMoved(mWidget);\n                forceFinalY(getY());\n                handle = true;\n            } else if (event.getAction() == MotionEvent.ACTION_UP) {\n                WidgetRenderer parent = (WidgetRenderer) mWidget.getParent();\n                parent.widgetDropped(mWidget);\n\n                if (event.getRawX() - mTouchDownX > getWidth() / 2) {\n                    animate().x(getWidth()).alpha(0).start();\n                    close();\n                    mToggleButton.toggleBackground(false);\n                } else {\n                    animate().x(0).alpha(1).start();\n                }\n            }\n\n            if (event.getAction() == MotionEvent.ACTION_UP) {\n                return handle;\n            } else {\n                return (super.onTouchEvent(event) || handle);\n            }\n        }\n\n        @Override\n        protected void onAttachedToWindow() {\n            super.onAttachedToWindow();\n\n            FrameLayout.LayoutParams layoutParam =\n                    (FrameLayout.LayoutParams) this.getLayoutParams();\n            if (layoutParam != null) {\n                layoutParam.width = FrameLayout.LayoutParams.WRAP_CONTENT;\n                layoutParam.height = FrameLayout.LayoutParams.WRAP_CONTENT;\n            }\n        }\n\n        private void initialize() {\n            Context context = getContext();\n            if (context == null) {\n                Log.e(TAG, \"Null context when initializing widget\");\n                return;\n            }\n            Resources res = getResources();\n            if (res == null) {\n                Log.e(TAG, \"Null resources when initializing widget\");\n                return;\n            }\n\n            // Inflate our widget layout\n            LayoutInflater inflater =\n                    (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n            mRootView = (ViewGroup) inflater.inflate(R.layout.widget_container, this);\n\n            if (mRootView == null) {\n                Log.e(TAG, \"Error inflating the widget layout XML\");\n                return;\n            }\n\n            // Grab all the views\n            mGridView = (GridLayout) mRootView.findViewById(R.id.widget_options_container);\n            mWidgetIcon = (ImageView) mRootView.findViewById(R.id.widget_icon);\n            mPinButton = (Button) mRootView.findViewById(R.id.btn_pin_widget);\n            //mResetButton = (ImageButton) mRootView.findViewById(R.id.btn_reset_widget);\n\n            mGridView.setColumnOrderPreserved(true);\n            mGridView.setRowCount(1);\n            mGridView.setColumnCount(0);\n\n            mGridView.setOnTouchListener(new OnTouchListener() {\n                @Override\n                public boolean onTouch(View view, MotionEvent motionEvent) {\n                    // We consume all events of the scrollview to avoid dragging the\n                    // widget by scrolling\n                    mGridView.onTouchEvent(motionEvent);\n                    return true;\n                }\n            });\n\n            mPinButton.setOnClickListener(new OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    Resources res = getResources();\n                    if (res == null) return;\n\n                    // Toggle shortcut (pinned) status\n                    if (SettingsStorage.getShortcutSetting(getContext(),\n                            WidgetBase.this.getClass().getCanonicalName())) {\n                        // It was a shortcut, remove it\n                        SettingsStorage.storeShortcutSetting(getContext(),\n                                WidgetBase.this.getClass().getCanonicalName(), false);\n                        mPinButton.setBackground(res.getDrawable(R.drawable.btn_pin_widget_inactive));\n                        mShortcutButton.setVisibility(View.GONE);\n                        mToggleButton.setVisibility(View.VISIBLE);\n                    } else {\n                        // Add it\n                        SettingsStorage.storeShortcutSetting(getContext(),\n                                WidgetBase.this.getClass().getCanonicalName(), true);\n                        mPinButton.setBackground(res.getDrawable(R.drawable.ic_pin_widget_active));\n                        mShortcutButton.setVisibility(View.VISIBLE);\n                        mToggleButton.setVisibility(View.GONE);\n                    }\n                }\n            });\n\n            // We default the window invisible, so we must respect the status\n            // obtained after the \"close\" animation\n            setAlpha(0.0f);\n            setScaleX(WIDGET_FADE_SCALE);\n            setScaleY(WIDGET_FADE_SCALE);\n            setVisibility(View.VISIBLE);\n\n            // Set padding\n            int pad = getResources().getDimensionPixelSize(R.dimen.widget_container_padding);\n            this.setPadding(pad, pad, pad, pad);\n\n            mRootAnimator = mRootView.animate();\n\n            if (mRootAnimator != null) {\n                mRootAnimator.setListener(new AnimatorListener() {\n                    @Override\n                    public void onAnimationCancel(Animator arg0) {\n\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator arg0) {\n                        if (!mIsOpen) {\n                            WidgetContainer.this.setVisibility(View.GONE);\n                        }\n                    }\n\n                    @Override\n                    public void onAnimationRepeat(Animator arg0) {\n                    }\n\n                    @Override\n                    public void onAnimationStart(Animator arg0) {\n                        WidgetContainer.this.setVisibility(View.VISIBLE);\n                    }\n                });\n            }\n        }\n\n        @Override\n        protected void onMeasure(int widthSpec, int heightSpec) {\n            super.onMeasure(widthSpec, heightSpec);\n\n            if (!isOpen()) {\n                setVisibility(View.GONE);\n            } else {\n                setVisibility(View.VISIBLE);\n            }\n        }\n\n        public void notifyOrientationChanged(int orientation, boolean fastforward) {\n            int buttonsCount = mGridView.getChildCount();\n\n            for (int i = 0; i < buttonsCount; i++) {\n                View child = mGridView.getChildAt(i);\n                WidgetOption option = (WidgetOption) child;\n\n                // Don't rotate seekbars\n                if (child instanceof WidgetOptionCenteredSeekBar) {\n                    continue;\n                }\n                if (child instanceof WidgetOptionSeekBar) {\n                    continue;\n                }\n\n                if (option != null) {\n                    option.notifyOrientationChanged(orientation);\n                }\n\n                if (fastforward) {\n                    child.setRotation(orientation);\n                } else {\n                    child.animate().rotation(orientation).setDuration(200).setInterpolator(\n                            new DecelerateInterpolator()).start();\n                }\n            }\n\n            WidgetBase.this.notifyOrientationChanged(orientation);\n        }\n\n        public GridLayout getGrid() {\n            return mGridView;\n        }\n\n        public void setIconId(int iconResId) {\n            mWidgetIcon.setImageResource(iconResId);\n        }\n    }\n}\n"
  }
]