[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea/**\n!.idea/codeStyleSettings.xml\n.DS_Store\nbuild\n/captures\ngoogle-services.json\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\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 GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\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\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions 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 convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\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\nstate 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 Affero General Public License as published\n    by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "<a href=\"https://play.google.com/store/apps/details?id=ai.saiy.android\" target=\"_blank\">\n  <img alt=\"Get it on Google Play\"\n       src=\"https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png\" height=\"60\"/>\n</a>\n\n# Saiy® for Android - Play Services Version\n\nHere lies the open source version of Saiy for Android, dependent on Google Play Services, which demonstrates how a Virtual Assistant functions, from start to finish.\n\n## About\n\nSaiy is a many times rebuilt version of its previous incarnation as utter! Countless attempts and therefore experience getting such an application to function on Android, has brought me to a point where I feel it's time to open source the code, for many reasons.\n\n## Why Open Source?\n\nAfter spending a few years rewriting the code base, I think I have finally got it to a stage where it could be considered 'scalable'. That may not be the correct terminology, given the infinite possibilities of natural language requests and the finite amount of actions that could be coded to resolve them - Nevertheless, having adapted the application to integrate numerous APIs for Text to Speech, Speech to Text, Natural Language Understanding, Machine Learning, Cognitive Services (such as emotion analysis and vocal identification) (in the least possible spaghetti way I could achieve) and written my own APIs for developers to integrate their applications, functions and services, it's a case of now or never to open source it; whilst the implementations to these connected services and APIs are functional and up-to-date.\n\nThe project itself is too large and the possibilities it presents are too great for just a lone developer and it would be great to see what the community can do with it - iterating at a pace that no other similar application can keep up with.\n\nAdditionally, most of us find AI and the future of our virtual assistants and smart tech pretty fascinating, but to get involved requires a number of technical stepping stones. I hope by publishing this code and the ease of which commands can be created and adapted, using either simple String matching, a cloud based solution or your own NLP implementation, it will allow many to dive straight in and therefore further their interest.\n\n## License & Copyright\n\nThe project is licensed under the GNU Affero General Public License V3. This is a copyleft license. See [LICENSE](https://github.com/brandall76/Saiy-PS/blob/master/LICENSE) \n\nI have selected this license over any other, in order to ensure that any adaptations or improvements to the code base, require to be published under the same license. This will protect any hard work from being adapted into closed sourced projects, without giving back.\n\nThe license grant is not for Saiy's trademarks, which include the logo designs. Saiy reserves all trademark and copyright rights in and to all [Saiy trademarks](https://trademarks.ipo.gov.uk/ipo-tmcase/page/Results/1/UK00003168669?legacySearch=False).\n\nCopyright © 2017 Saiy® Ltd.\n\n## Contributor License Agreements\n\nI need to clarify the most appropriate for the GNU Affero General Public License - will revisit very soon. Any suggestions welcome.\n\n## Features\n\n- Network & Embedded Text to Speech\n  - Embedded Android text to speech\n  - Nuance text to speech\n  \n  See Saiy TTS project for further integration examples.\n  \n- Network & Native Speech to Text\n  - Google native Android voice recognition\n  - Nuance voice recognition\n  - Microsoft voice recognition\n  - IBM voice recognition\n  - WIT voice recognition\n  \n- Offline Hotword\n  - PocketSphinx\n  \n- Natural Language Processing\n  - API.ai\n  - Wit.ai\n  - Nuance mix\n  - Microsoft\n  - IBM Bluemix\n  \n- Vocal Identification\n  - Microsoft (Project Oxford)\n  \n- Emotion Analysis\n  - Beyond Verbal\n  \n- Knowledge Base\n  - Wolfram Alpha\n  \n- Developer APIs\n  - [Example App](https://github.com/brandall76/API-Example-App)\n  \n## Getting Started\n\nThe project is built using Java 7 - Android SDK (API 26) - Android NDK\n\nUsing Android Studio, it can be imported as a new project via version control or the downloadable zip.\n\nThere is a direct dependency to the [Saiy Library project](https://github.com/brandall76/Saiy-Library). Once that project has compiled you'll need to add the generated aar file as a module to the main project, as described [here](https://stackoverflow.com/q/29826717/1256219).\n\nWithout stating the obvious, when testing on a physical device, the performance of the code is accentuated by the hardware specifications - more so than your average app, as there is a lot going on. \n\nInstalling the [Google Text to Speech Engine](https://play.google.com/store/apps/details?id=com.google.android.tts&hl=en_GB) on your test device is recommended, due to the features it provides.\n\nTo use free embedded and offline Voice Recognition, install [Google's 'Now'](https://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox&hl=en_GB) application. If you have a Samsung device, their Vlingo recognition service **does not** work correctly for external applications.\n\n## Providers\n\n - [Nuance Developers](https://developer.nuance.com) - Text to Speech - Speech to Text - NLU \n - [Microsoft Cognitive Services](https://azure.microsoft.com/en-gb/services/cognitive-services/) - Text to Speech - NLU - Translate API\n - [IBM Bluemix](https://www.ibm.com/watson/services/speech-to-text/) - Speech to Text  - NLU\n - [Wit](https://wit.ai/) - Speech to Text - NLU\n - [PocketSphinx](https://github.com/cmusphinx/pocketsphinx-android-demo) - Speech to Text\n - [Google Cloud Speech](https://cloud.google.com/speech/) - Speech to Text\n - [Google Chromium Speech](https://www.chromium.org/developers/how-tos/api-keys) - Speech to Text\n - [API AI](https://api.ai/) - Speech to Text - NLU\n - [Beyond Verbal](http://developers.beyondverbal.com) - Emotion analytics\n - [Wolfram Alpha](https://developer.wolframalpha.com/portal/signin.html) - Knowledge base\n\n## Troubleshooting\n\nPlease use the [Stack Overflow tag](https://stackoverflow.com/tags/saiy/info) for compiling related questions and errors.\n\nFor code issues and crashes, please open an issue.\n\nFor discussion, please use the [XDA development thread](https://forum.xda-developers.com/showthread.php?t=1508195) for now.\n\n## Navigating the Code\n\nIn all major areas of the code, I will attempt to add further README files to detail a more specific explanation - including TO-DOs, issues and required improvements. Check the subdirectories of the code to see if a README is present, or a placeholder, letting you know that one should be soon.\n\nBriefly, there are two major classes in the app, that direct and distribute work elsewhere:\n\n- [SelfAware](https://github.com/brandall76/Saiy-PS/blob/master/app/src/main/java/ai/saiy/android/service/SelfAware.java) is the main Foreground Service, responsible for managing the application state and channelling voice recognition, text to speech and other API requests.\n\n- [Quantum](https://github.com/brandall76/Saiy-PS/blob/master/app/src/main/java/ai/saiy/android/processing/Quantum.java) is the main processing class, where commands are locally resolved (if required), sensibility checked and actioned.\n\nUnderstanding the above two classes is essential to following the flow of the full application logic.\n\n- [MyLog](https://github.com/brandall76/Saiy-PS/blob/master/app/src/main/java/ai/saiy/android/utils/MyLog.java#L41) is a global verbose logging toggle. When enabled, the output will flow class to class, as well as display durations for time sensitive functions. \n\n## Credentials\n\nFor the sake of testing ease, the code points to static API keys and secrets held in the [configuration directory](https://github.com/brandall76/Saiy-PS/tree/master/app/src/main/java/ai/saiy/android/configuration). It should probably go without saying, don't do this in production code.\n\n## Strings for offline language processing?\n\nThis dates back to my original code written as a beginner for utter! That said, it is only relatively recently that cloud services such as API.ai or [embedded options](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/android/src/org/tensorflow/demo/SpeechActivity.java) became available/usable as well as manageable for an individual developer.\n\nUp until this point, Java libraries that attempted the equivalent, bloated and lagged the app to the point of stalling. They were not an option.\n\nI am also mindful, that I would like developers of any experience to be able to contribute to and manipulate the code with ease. Basic String operations can be converted by others over at [SaiyeyMcSaiyface](https://github.com/brandall76/SaiyeyMcSaiyface) \n\n## Theory\n\nI use the word, scalability, with caution. Whatever strides machine learning has taken up until now, there is still a requirement for a human to hard-code the ultimate action that is resolved to be performed. Whether this be turning up your heating, or the generic layout design of a weather request, someone still needs to write that code and the surrounding error handling.\n\nWhilst standardised templates, to organise the world of information around us, can assist to categorise the output to a set of static response mechanisms - we perhaps can't feasibly use the word scalable, until a machine can dynamically write code (or the equivalent) for itself.\n\nThe above is for another discussion, but the point to take is that development is currently consigned to the following:\n\n- Receive a command request\n  - No further explanation needed\n- Resolve the command to collection\n  - Bluetooth on/off/toggle - would be a 'Bluetooth collection'\n  - Weather conditions in location on date - would be 'Weather collection'\n  - None\n- Apply sensibility/boundary checks\n  - Turn the oven on to 3000 degrees for 8 years\n  - Remind me to go to the doctor yesterday\n  - What's the weather like in Primark\n  - Spell a\n  - Drive me over Niagara Falls\n- Action the request\n  - Deducing success/error/insufficiency\n  - Extract standardisation - _{ location:\"Berlin\" } { description:\"light drizzle\" }_\n  \nMuch of the above will need to be hard-coded. I state this only to manage expectations of currently how 'smart' we can hope to be...\n\n## What's the plan?\n\nInitially, I have published only the core of the application, so it may be critiqued in terms of its structure and quality of code. Much of the fundamental construct of the app and the code style/quality used, is repeated across the 500K+ lines still to be pushed.\n\nOnce there is general consensus on the application core, I will begin to upload the remaining code, with any suggested alterations already in place.\n\nI am entirely self taught in Java, so go easy on me!\n\n- Fundamentals\n  - Generally, how the construct of the application and way it interconnects can be improved. Both in terms of performance and readability.\n\n- Memory management\n  - Given that the application functions as a Foreground service, great detail must to be paid to memory allocation. Theoretically, the application could persist forever - there is no luxury of onDestroy() to wipe the footprint. The Garbage Collector is nudged at various points throughout the application, when micro delays are of no concern.\n\n- Threading strategy\n  - Whilst I've been mindful of how the threading functions across the code, using standard practices in terms of background/foreground/priority/pools etc - there remains no specific 'strategy' as to how these are managed. I would appreciate input from others here. \n\n- Micro-optimisation\n  - I've no issue with accepting pull requests for this type of optimisation. I'm looking forward to seeing how contributors can shave off a thousandth here and there. Please do provide a test to back-up any submissions.\n\n- Unit tests\n  - My own tests were not worthy of publishing. If this is your thing, help!\n  \n- Translation\n  - I hope to translate the application into every language available via both a voice recognition and text to speech provider. I'm using the [Crowdin Plaform](https://crowdin.com/project/saiy/invite?d=d5b5m4057517e563r44323n463) if you'd like to help.\n  \n- Visualisation\n  - Since API 23 introduced a comprehensive [Assistant Framework](https://developer.android.com/training/articles/assistant.html), visualising data has become an easier prospect. I have no current implementation of this, but it's now on the roadmap.\n  - An augmented reality visualisation of Saiy built using [ARCore](https://github.com/google-ar/arcore-android-sdk) can be found [here](https://github.com/brandall76/Saiy-AR).\n  \n- Localisation\n  - If a user is controlling Saiy in a language other than their native (presumably due to it being unsupported), standard String resource management, based on their device Locale, will point to the wrong destination and therefore fail. The resolution of this process is done using a [SupportedLanguage](https://github.com/brandall76/Saiy-PS/blob/master/app/src/main/java/ai/saiy/android/localisation/SupportedLanguage.java) object. Further explanation can be found in the [localisation directory](https://github.com/brandall76/Saiy-PS/tree/master/app/src/main/java/ai/saiy/android/localisation) README.\n  \n## Java 8\n  \nThe code was originally written in Java 8, but had to be reverted due to build [issues with the Jack compiler](https://issuetracker.google.com/issues/37127783)\n\nNow [Jack is deprecated](https://source.android.com/source/jack), I plan to revisit this soon.\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/assets.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"assets\">\n  <property name=\"assets.list.name\" value=\"assets.lst\"/>\n  <property name=\"assets.dir\" value=\"src/main/assets/sync\"/>\n  <property name=\"assets.hash.type\" value=\"md5\"/>\n  <property name=\"assets.ctl.files\"\n    value=\"**/*.${assets.hash.type},${assets.list.name}\"/>\n\n  <fileset id=\"assets\" dir=\"${assets.dir}\" excludes=\"${assets.ctl.files}\"/>\n\n  <target name=\"clean_assets\">\n    <delete>\n      <fileset dir=\"${assets.dir}\" includes=\"${assets.ctl.files}\"/>\n    </delete>\n  </target>\n\n  <target name=\"list\">\n    <pathconvert\n      dirsep=\"/\" pathsep=\"${line.separator}\"\n      refid=\"assets\" property=\"asset.list\">\n      <map from=\"${basedir}/${assets.dir}/\" to=\"\"/>\n    </pathconvert>\n    <echo message=\"${asset.list}\" file=\"${assets.dir}/${assets.list.name}\"/>\n  </target>\n\n  <target name=\"checksum\">\n    <checksum algorithm=\"${assets.hash.type}\">\n      <fileset refid=\"assets\"/>\n    </checksum>\n  </target>\n</project>\n"
  },
  {
    "path": "app/build.gradle",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\napply plugin: 'com.android.application'\napply plugin: 'com.google.protobuf'\n\nandroid {\n\n    dexOptions {\n        jumboMode true\n        preDexLibraries = false\n        maxProcessCount 4\n        javaMaxHeapSize \"6g\"\n    }\n\n    compileSdkVersion 27\n    buildToolsVersion '27.0.3'\n\n    defaultConfig {\n        applicationId \"ai.saiy.android\"\n        minSdkVersion 16\n        targetSdkVersion 27\n        versionCode 3\n        versionName \"1.0.3\"\n        multiDexEnabled true\n        vectorDrawables.useSupportLibrary = true\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n        incremental true\n    }\n\n    flavorDimensions 'tier'\n    productFlavors {\n\n        x86 {\n            ndk {\n                abiFilter \"x86\"\n            }\n        }\n        x86_64 {\n            ndk {\n                abiFilter \"x86_64\"\n            }\n        }\n        arm {\n            ndk {\n                abiFilters \"armeabi\"\n            }\n        }\n        arm7 {\n            ndk {\n                abiFilters \"armeabi-v7a\"\n            }\n        }\n        arm8 {\n            ndk {\n                abiFilters \"arm64-v8a\"\n            }\n        }\n        mips {\n            ndk {\n                abiFilter \"mips\"\n            }\n        }\n        mips_64 {\n            ndk {\n                abiFilter \"mips64\"\n            }\n        }\n\n        dev {\n            dimension 'tier'\n        }\n\n        prod {\n            dimension 'tier'\n            minSdkVersion 16\n        }\n    }\n\n    buildTypes {\n\n        release {\n            ndk {\n                abiFilters 'armeabi-v7a', 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'\n            }\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n\n        debug {\n            ndk {\n                abiFilters 'armeabi-v7a', 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'\n            }\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\nconfigurations {\n    implementation.exclude group: \"org.apache.httpcomponents\", module: \"httpclient\"\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar', '*.so'], dir: 'libs')\n    implementation 'com.android.support:multidex:1.0.2'\n    implementation 'com.android.support:appcompat-v7:27.1.0'\n    implementation 'com.android.support:design:27.1.0'\n    implementation 'com.android.support:support-annotations:27.1.0'\n    implementation 'com.android.support:cardview-v7:27.1.0'\n    implementation 'com.android.support.constraint:constraint-layout:1.0.2'\n    implementation('com.google.apis:google-api-services-translate:v2-rev45-1.21.0') {\n        exclude group: 'org.apache.httpcomponents', module: 'httpclient'\n        exclude group: 'com.android.support'\n        exclude module: 'support-annotations'\n        exclude group: 'com.google.guava'\n    }\n    implementation 'com.google.android.gms:play-services-location:12.0.0'\n    implementation 'com.google.android.gms:play-services-auth:12.0.0'\n    implementation 'com.nuance:speechkit:2.1.3@aar'\n    implementation 'com.google.guava:guava:20.0'\n    implementation 'com.google.code.gson:gson:2.8.0'\n    implementation 'com.google.http-client:google-http-client-gson:1.19.0'\n    implementation 'com.microsoft.projectoxford:speechrecognition:0.6.0'\n    implementation 'org.apache.commons:commons-lang3:3.6'\n    implementation 'commons-codec:commons-codec:20041127.091804'\n    implementation 'com.github.mpkorstanje:simmetrics-core:4.1.0'\n    implementation 'org.apache.commons:commons-collections4:4.1'\n    implementation 'ai.api:sdk:1.9.0@aar'\n    implementation 'commons-io:commons-io:2.4'\n    implementation 'org.java-websocket:Java-WebSocket:1.3.0'\n    implementation 'de.psdev.licensesdialog:licensesdialog:1.8.1'\n    implementation 'com.afollestad.material-dialogs:core:0.9.5.0'\n    implementation('org.simpleframework:simple-xml:2.7.1') {\n        exclude group: 'xpp3', module: 'xpp3'\n        exclude group: 'stax', module: 'stax-api'\n        exclude group: 'stax', module: 'stax'\n    }\n    implementation('io.grpc:grpc-okhttp:1.0.1') {\n        exclude module: 'jsr305'\n    }\n    implementation('io.grpc:grpc-protobuf:1.0.1') {\n        exclude module: 'jsr305'\n    }\n    implementation('io.grpc:grpc-stub:1.0.1') {\n        exclude module: 'jsr305'\n    }\n    implementation('io.grpc:grpc-auth:1.0.1') {\n        exclude module: 'jsr305'\n    }\n    implementation 'com.google.android.gms:play-services-location:12.0.0'\n    implementation('javax.annotation:javax.annotation-api:1.2') {\n        exclude module: 'annotation'\n    }\n    implementation('com.google.auth:google-auth-library-oauth2-http:0.3.0') {\n        exclude group: 'org.apache.httpcomponents'\n        exclude group: 'com.android.support'\n        exclude module: 'support-annotations'\n        exclude group: 'com.google.guava'\n        exclude module: 'jsr305'\n        exclude module: 'httpclient'\n    }\n    implementation project(':pocketsphinx-android-5prealpha-nolib')\n    implementation project(':lapp-debug')\n    testImplementation 'junit:junit:4.12'\n}\n\nprotobuf {\n    protoc {\n        artifact = 'com.google.protobuf:protoc:3.0.2'\n    }\n    plugins {\n        grpc {\n            artifact = 'io.grpc:protoc-gen-grpc-java:1.0.1'\n        }\n    }\n    generateProtoTasks {\n        all().each {\n            task ->\n                task.builtins {\n                    remove javanano\n                    java {\n                    }\n                }\n\n                task.plugins {\n                    grpc {\n                    }\n                }\n        }\n    }\n}\n\nant.importBuild 'assets.xml'\npreBuild.dependsOn(list, checksum)\nclean.dependsOn(clean_assets)\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Useful - https://github.com/krschultz/android-proguard-snippets/tree/master/libraries\n\n# Suppress warnings from gRPC dependencies\n-dontwarn com.google.common.**\n-dontwarn com.google.api.client.**\n-dontwarn com.google.protobuf.**\n-dontwarn io.grpc.**\n-dontwarn okio.**\n\n# ads\n-keep public class com.google.android.gms.ads.** {\n   public *;\n}\n\n-keep public class com.google.ads.** {\n   public *;\n}\n\n# Simple XML\n-keep interface org.simpleframework.xml.core.Label { public *;}\n-keep class * implements org.simpleframework.xml.core.Label { public *;}\n-keep interface org.simpleframework.xml.core.Parameter { public *;}\n-keep class * implements org.simpleframework.xml.core.Parameter { public *;}\n-keep interface org.simpleframework.xml.core.Extractor { public *;}\n-keep class * implements org.simpleframework.xml.core.Extractor { public *;}\n-keep interface org.simpleframework.xml.convert.Convert { public *;}\n-keep class * implements org.simpleframework.xml.convert.Converter { public *;}\n\n-dontwarn com.bea.xml.stream.**\n-dontwarn org.simpleframework.xml.stream.**\n-keepclassmembers,allowobfuscation class * {\n    @org.simpleframework.xml.* <fields>;\n    @org.simpleframework.xml.* <init>(...);\n}\n\n# Explicitly preserve all serialization members. The Serializable interface\n# is only a marker interface, so it wouldn't save them.\n# You can comment this out if your library doesn't use serialization.\n# If your code contains serializable classes that have to be backward\n# compatible, please refer to the manual.\n\n-keepclassmembers class * implements java.io.Serializable {\n    static final long serialVersionUID;\n    static final java.io.ObjectStreamField[] serialPersistentFields;\n    private void writeObject(java.io.ObjectOutputStream);\n    private void readObject(java.io.ObjectInputStream);\n    java.lang.Object writeReplace();\n    java.lang.Object readResolve();\n}\n\n-keepattributes *Annotation*\n-keepattributes InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,EnclosingMethod,Attribute,Element,Root\n-keepclassmembers class * {\n    @org.simpleframework.xml.* *;\n}\n\n#support libraries\n# Allow obfuscation of android.support.v7.internal.view.menu.**\n# to avoid problem on Samsung 4.2.2 devices with appcompat v23\n# see https://code.google.com/p/android/issues/detail?id=78377\n# http://stackoverflow.com/questions/24809580/noclassdeffounderror-android-support-v7-internal-view-menu-menubuilder/27254191\n-keep class android.support.v7.view.menu.*MenuBuilder*\n-keep class android.support.v7.** { *; }\n-keep interface android.support.v7.* { *; }\n\n-keep public class android.support.v7.widget.** { *; }\n-keep public class android.support.v7.internal.widget.** { *; }\n-keep public class android.support.v7.internal.view.menu.** { *; }\n\n-keep public class * extends android.support.v4.view.ActionProvider {\n    public <init>(android.content.Context);\n}\n\n# Volley\n-keep class com.android.volley.** { *; }\n\n#Guava\n-keep class com.google.common.io.Resources {\n    public static <methods>;\n}\n-keep class com.google.common.collect.Lists {\n    public static ** reverse(**);\n}\n-keep class com.google.common.base.Charsets {\n    public static <fields>;\n}\n\n# cardview\n# http://stackoverflow.com/questions/29679177/cardview-shadow-not-appearing-in-lollipop-after-obfuscate-with-proguard/29698051\n-keep class android.support.v7.widget.RoundRectDrawable { *; }\n\n-keep class com.google.common.base.Joiner {\n    public static com.google.common.base.Joiner on(java.lang.String);\n    public ** join(...);\n}\n\n-keep class com.google.common.collect.MapMakerInternalMap$ReferenceEntry\n-keep class com.google.common.cache.LocalCache$ReferenceEntry\n\n# http://stackoverflow.com/questions/9120338/proguard-configuration-for-guava-with-obfuscation-and-optimization\n-dontwarn javax.annotation.**\n-dontwarn javax.inject.**\n-dontwarn sun.misc.Unsafe\n\n# Guava 19.0\n-dontwarn java.lang.ClassValue\n-dontwarn com.google.j2objc.annotations.Weak\n-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement\n\n##---------------Begin: proguard configuration for Gson  ----------\n# Gson uses generic type information stored in a class file when working with fields. Proguard\n# removes such information by default, so configure it to keep all of it.\n-keepattributes Signature\n\n# For using GSON @Expose annotation\n-keepattributes *Annotation*\n\n# Gson specific classes\n-keep class sun.misc.Unsafe { *; }\n#-keep class com.google.gson.stream.** { *; }\n\n# Application classes that will be serialized/deserialized over Gson\n-keep class com.google.gson.examples.android.model.** { *; }\n\n# Prevent proguard from stripping interface information from TypeAdapterFactory,\n# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)\n-keep class * implements com.google.gson.TypeAdapterFactory\n-keep class * implements com.google.gson.JsonSerializer\n-keep class * implements com.google.gson.JsonDeserializer\n\n##---------------End: proguard configuration for Gson  ----------\n\n# Needed by google-api-client to keep generic types and @Key annotations accessed via reflection\n\n-keepclassmembers class * {\n  @com.google.api.client.util.Key <fields>;\n}\n\n-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault\n\n## Google Play Services 4.3.23 specific rules ##\n## https://developer.android.com/google/play-services/setup.html#Proguard ##\n-keep class * extends java.util.ListResourceBundle {\n    protected Object[][] getContents();\n}\n\n-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {\n    public static final *** NULL;\n}\n\n-keepnames @com.google.android.gms.common.annotation.KeepName class *\n-keepclassmembernames class * {\n    @com.google.android.gms.common.annotation.KeepName *;\n}\n\n-keepnames class * implements android.os.Parcelable {\n    public static final ** CREATOR;\n}\n\n# OkHttp\n-keepattributes Signature\n-keepattributes *Annotation*\n-keep class com.squareup.okhttp.** { *; }\n-keep interface com.squareup.okhttp.** { *; }\n-dontwarn com.squareup.okhttp.**\n\n# support design\n-dontwarn android.support.design.**\n-keep class android.support.design.** { *; }\n-keep interface android.support.design.** { *; }\n-keep public class android.support.design.R$* { *; }\n\n# apache commons\n-keep class org.apache.commons.logging.**\n-dontwarn org.apache.commons.logging.impl.**\n\n# apache commons codec\n-dontwarn org.apache.commons.codec.binary.**"
  },
  {
    "path": "app/src/androidTest/java/ai/saiy/android/ApplicationTest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<manifest\n    package=\"ai.saiy.android\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <permission\n        android:name=\"ai.saiy.android.permission.CONTROL_SAIY\"\n        android:description=\"@string/control_saiy_permission_description\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/control_saiy_permission_label\"\n        android:protectionLevel=\"dangerous\"/>\n\n    <uses-permission\n        android:name=\"ai.saiy.android.permission.CONTROL_SAIY\"\n        android:maxSdkVersion=\"22\"/>\n\n    <uses-permission\n        android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"18\"/>\n\n    <uses-permission\n        android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"18\"/>\n\n    <!--suppress DeprecatedClassUsageInspection -->\n    <uses-permission\n        android:name=\"android.permission.GET_TASKS\"\n        android:maxSdkVersion=\"20\"/>\n\n    <uses-permission\n        android:name=\"android.permission.PACKAGE_USAGE_STATS\"\n        tools:ignore=\"ProtectedPermissions\"/>\n\n    <!-- Permission Required -->\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>\n    <uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\"/>\n    <uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>\n\n    <!-- Protection Normal -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <uses-permission android:name=\"android.permission.VIBRATE\"/>\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>\n    <uses-permission android:name=\"android.permission.BIND_QUICK_SETTINGS_TILE\"/>\n    <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\"/>\n    <uses-permission android:name=\"android.permission.BIND_VOICE_INTERACTION\"/>\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n    <uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n\n    <!-- Non-Standard -->\n    <uses-permission android:name=\"com.google.android.gms.permission.ACTIVITY_RECOGNITION\"/>\n    <uses-permission android:name=\"net.dinglisch.android.tasker.PERMISSION_RUN_TASKS\"/>\n\n    <!-- HTC Idiosyncrasies -->\n    <uses-permission android:name=\"android.intent.action.QUICKBOOT_POWERON\"/>\n    <uses-permission android:name=\"com.htc.intent.action.QUICKBOOT_POWERON\"/>\n\n    <uses-feature\n        android:name=\"android.hardware.telephony\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.location.gps\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.nfc\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.camera\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.location\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.location.network\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.bluetooth\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.microphone\"\n        android:required=\"true\"/>\n    <uses-feature\n        android:name=\"android.hardware.touchscreen\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.camera.autofocus\"\n        android:required=\"false\"/>\n    <uses-feature\n        android:name=\"android.hardware.camera.flash\"\n        android:required=\"false\"/>\n\n    <supports-screens\n        android:anyDensity=\"true\"\n        android:largeScreens=\"true\"\n        android:normalScreens=\"true\"\n        android:smallScreens=\"true\"\n        android:xlargeScreens=\"true\"/>\n\n    <!--<compatible-screens>-->\n    <!--<screen-->\n    <!--android:screenSize=\"small\"-->\n    <!--android:screenDensity=\"xhdpi\"/>-->\n    <!--<screen-->\n    <!--android:screenSize=\"normal\"-->\n    <!--android:screenDensity=\"xhdpi\"/>-->\n    <!--<screen-->\n    <!--android:screenSize=\"large\"-->\n    <!--android:screenDensity=\"xhdpi\"/>-->\n    <!--<screen-->\n    <!--android:screenSize=\"xlarge\"-->\n    <!--android:screenDensity=\"xhdpi\"/>-->\n    <!--</compatible-screens>-->\n\n    <application\n        android:name=\"ai.saiy.android.utils.Global\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:largeHeap=\"true\"\n        android:supportsRtl=\"false\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n\n        <meta-data\n            android:name=\"com.google.android.gms.version\"\n            android:value=\"@integer/google_play_services_version\"/>\n\n        <activity\n            android:name=\"ai.saiy.android.ui.activity.ActivityHome\"\n            android:clearTaskOnLaunch=\"true\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".ui.activity.ActivityAssistProxy\"\n            android:clearTaskOnLaunch=\"true\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:enabled=\"false\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"false\"\n            android:launchMode=\"singleInstance\"\n            android:noHistory=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.ASSIST\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".ui.activity.ActivityAssistSettings\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"false\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\".recognition.provider.saiy.assist.SaiyInteractionService\"\n            android:enabled=\"false\"\n            android:exported=\"false\"\n            android:permission=\"android.permission.BIND_VOICE_INTERACTION\"\n            android:process=\":interaction\">\n            <meta-data\n                android:name=\"android.voice_interaction\"\n                android:resource=\"@xml/interaction_service\"/>\n            <intent-filter>\n                <action android:name=\"android.service.voice.VoiceInteractionService\"/>\n            </intent-filter>\n        </service>\n        <service\n            android:name=\".recognition.provider.saiy.assist.SaiyInteractionSessionService\"\n            android:enabled=\"false\"\n            android:exported=\"false\"\n            android:permission=\"android.permission.BIND_VOICE_INTERACTION\"\n            android:process=\":session\">\n        </service>\n\n        <activity\n            android:name=\"ai.saiy.android.ui.activity.ActivityPermissionDialog\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"true\"\n            android:theme=\"@style/AppTheme.ActivityDialogTheme\">\n        </activity>\n\n        <activity\n            android:name=\"ai.saiy.android.ui.activity.ActivityChooserDialog\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"true\"\n            android:theme=\"@style/AppTheme.ActivityDialogTheme\">\n        </activity>\n\n        <activity\n            android:name=\"ai.saiy.android.ui.activity.ActivityIssue\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"false\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n        </activity>\n\n        <activity\n            android:name=\".ui.activity.ActivityTilePreferences\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"false\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE_PREFERENCES\"/>\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\"ai.saiy.android.ui.activity.ActivityLauncherShortcut\"\n            android:clearTaskOnLaunch=\"true\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:excludeFromRecents=\"true\"\n            android:launchMode=\"singleInstance\"\n            android:noHistory=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\">\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"android.intent.action.SEARCH_LONG_PRESS\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"android.speech.action.VOICE_SEARCH_HANDS_FREE\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"android.intent.action.VOICE_COMMAND\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"android.intent.action.ASSIST\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n\n            <meta-data\n                android:name=\"com.android.systemui.action_assist_icon\"\n                android:resource=\"@mipmap/ic_launcher\"/>\n        </activity>\n\n        <service\n            android:name=\"ai.saiy.android.service.SelfAware\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:permission=\"ai.saiy.android.permission.CONTROL_SAIY\">\n        </service>\n\n        <service\n            android:name=\"ai.saiy.android.service.NotificationService\"\n            android:enabled=\"true\"\n            android:exported=\"false\"\n            android:protectionLevel=\"signature\">\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"ai.saiy.android.INTENT_CLICK\"/>\n            </intent-filter>\n        </service>\n\n        <service\n            android:name=\"ai.saiy.android.service.helper.AssistantIntentService\"\n            android:enabled=\"true\"\n            android:exported=\"false\"\n            android:protectionLevel=\"signature\">\n        </service>\n\n        <service\n            android:name=\"ai.saiy.android.cognitive.motion.provider.google.MotionIntentService\"\n            android:enabled=\"true\"\n            android:exported=\"false\">\n        </service>\n\n        <service\n            android:name=\"ai.saiy.android.ui.service.SaiyTileService\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/tile_label\"\n            android:permission=\"android.permission.BIND_QUICK_SETTINGS_TILE\">\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE\"/>\n            </intent-filter>\n            <meta-data\n                android:name=\"android.service.quicksettings.ACTIVE_TILE\"\n                android:value=\"true\"/>\n        </service>\n\n        <service\n            android:name=\"ai.saiy.android.accessibility.SaiyAccessibilityService\"\n            android:enabled=\"true\"\n            android:label=\"@string/app_name\"\n            android:permission=\"android.permission.BIND_ACCESSIBILITY_SERVICE\">\n            <intent-filter>\n                <action android:name=\"android.accessibilityservice.AccessibilityService\"/>\n            </intent-filter>\n\n            <meta-data\n                android:name=\"android.accessibilityservice\"\n                android:resource=\"@xml/accessibility_config\"/>\n        </service>\n\n        <receiver\n            android:name=\"ai.saiy.android.broadcast.BRBoot\"\n            android:enabled=\"true\">\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n                <action android:name=\"android.intent.action.LOCKED_BOOT_COMPLETED\"/>\n                <action android:name=\"android.intent.action.QUICKBOOT_POWERON\"/>\n                <action android:name=\"com.htc.intent.action.QUICKBOOT_POWERON\"/>\n            </intent-filter>\n        </receiver>\n\n        <receiver\n            android:name=\"ai.saiy.android.broadcast.BRRemote\"\n            android:enabled=\"true\"\n            android:permission=\"ai.saiy.android.permission.CONTROL_SAIY\">\n            <intent-filter android:priority=\"999\">\n                <action android:name=\"ai.saiy.android.SAIY_REQUEST_RECEIVER\"/>\n            </intent-filter>\n        </receiver>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/assets/sync/assets.lst",
    "content": "basic.dic\nen-us-ptm/README\nen-us-ptm/feat.params\nen-us-ptm/mdef\nen-us-ptm/means\nen-us-ptm/noisedict\nen-us-ptm/sendump\nen-us-ptm/transition_matrices\nen-us-ptm/variances\nhotwords.txt"
  },
  {
    "path": "app/src/main/assets/sync/basic.dic",
    "content": "okaygoogle OW K EY G UW G AH L\nokaygoogle(2) OW K EY G UW G OW L\nokaygoogle(3) OW K EY G UH G AH L\nokaygoogle(4) OW K EY G UH G OW L\nokaygoogle(5) OW K AH G UW G AH L\nokaygoogle(6) OW K AH G UW G OW L\nokaygoogle(7) OW K AH G UH G AH L\nokaygoogle(8) OW K AH G UH G OW L\nokaygoogle(9) OW K IY G UW G AH L\nokaygoogle(10) OW K IY G UW G OW L\nokaygoogle(11) OW K IY G UH G AH L\nokaygoogle(12) OW K IY G UH G OW L\nstoplistening S T AA P L IH S AH N IH NG\nstoplistening(2) S T AA P L IH S N IH NG\nstoplistening(3) S T AO P L IH S AH N IH NG\nstoplistening(4) S T AO P L IH S N IH NG\nwakeupsay W EY K AH P S EY\nwakeupsay(1) W EY K AH P S AY\nwakeupsay(2) W EY K AH P S IY\nwakeupsay(3) W AH K AH P S EY\nwakeupsay(4) W AH K AH P S AY\nwakeupsay(5) W AH K AH P S IY\nwakeupsay(6) W IY K AH P S EY\nwakeupsay(7) W IY K AH P S AY\nwakeupsay(8) W IY K AH P S IY"
  },
  {
    "path": "app/src/main/assets/sync/basic.dic.md5",
    "content": "56c42b4b20fe1edc953b9f3987e9af9f\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/README",
    "content": "/* ====================================================================\n * Copyright (c) 2015 Alpha Cephei Inc. All rights\n * reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY ALPHA CEPHEI INC. ``AS IS'' AND.\n * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,.\n * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ALPHA CEPHEI INC.\n * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT.\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,.\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY.\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT.\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE.\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * ====================================================================\n *\n */\n\nThis directory contains generic US english acoustic model trained with\nlatest sphinxtrain.\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/README.md5",
    "content": "45c80a79247b52dc6b48f2f07744d91d\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/feat.params",
    "content": "-lowerf 130\n-upperf 6800\n-nfilt 25\n-transform dct\n-lifter 22\n-feat 1s_c_d_dd\n-svspec 0-12/13-25/26-38\n-agc none\n-cmn current\n-varnorm no\n-model ptm\n-cmninit 40,10,10\n\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/feat.params.md5",
    "content": "ec1566c9d8cf37c2a01571bcaff0caaa\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/mdef.md5",
    "content": "ba13d30c2fee63e039e119ab449bd618\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/means.md5",
    "content": "d0ee21e7d0e03575f27497b2833c6f02\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/noisedict",
    "content": "<s> SIL\n</s> SIL\n<sil> SIL\n[NOISE] +NSN+\n[SPEECH] +SPN+\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/noisedict.md5",
    "content": "05034ffef21f4810d10d3c76a6f5e921\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/sendump.md5",
    "content": "75328625279cdbb72f800b315365ff45\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/transition_matrices.md5",
    "content": "7a63d8971f81eef2154ea38b8bdfe520\n"
  },
  {
    "path": "app/src/main/assets/sync/en-us-ptm/variances.md5",
    "content": "d4d6ba74707952aa7e00c3bc1e7d0fb4\n"
  },
  {
    "path": "app/src/main/assets/sync/hotwords.txt",
    "content": "wakeupsay /1e-20/\nokaygoogle /1e-30/\nstoplistening /1e-20/"
  },
  {
    "path": "app/src/main/assets/sync/hotwords.txt.md5",
    "content": "60940e64061013741db9a82f0ffc9793\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/accessibility/SaiyAccessibilityService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.accessibility;\n\nimport android.accessibilityservice.AccessibilityService;\nimport android.accessibilityservice.AccessibilityServiceInfo;\nimport android.app.Notification;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Parcelable;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Pair;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\n\nimport java.util.ArrayList;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.Resolve;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.recognition.RecognitionAction;\nimport ai.saiy.android.recognition.helper.RecognitionDefaults;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to handle accessibility service events.\n * Created by benrandall76@gmail.com on 03/08/2016.\n */\n\npublic class SaiyAccessibilityService extends AccessibilityService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyAccessibilityService.class.getSimpleName();\n\n    private static final long COMMAND_UPDATE_DELAY = 3000L;\n    private static final long UPDATE_TIMEOUT = 250L;\n    private long previousCommandTime;\n    private String previousCommandProcessed = null;\n    private String previousCommandInterimChecked = null;\n    private String previousCommandFinalChecked = null;\n\n    private final Pattern pGoogleNow = Pattern.compile(Installed.PACKAGE_NAME_GOOGLE_NOW, Pattern.CASE_INSENSITIVE);\n    private final Pattern pGoogleNowInterim = Pattern.compile(RecognitionDefaults.GOOGLE_NOW_INTERIM_FIELD, Pattern.CASE_INSENSITIVE);\n    private final Pattern pGoogleNowFinal = Pattern.compile(RecognitionDefaults.GOOGLE_NOW_FINAL_EDIT_TEXT, Pattern.CASE_INSENSITIVE);\n\n\n    private SupportedLanguage sl;\n    private Pattern pListening;\n\n    private boolean initInterceptGoogle;\n    private boolean initAnnounceNotifications;\n\n    private final boolean EXTRA_VERBOSE = false;\n\n    @Override\n    public void onCreate() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        sl = SupportedLanguage.getSupportedLanguage(SPH.getVRLocale(getApplicationContext()));\n        final SaiyResources sr = new SaiyResources(getApplicationContext(), sl);\n        final String listening = sr.getString(R.string.listening);\n        sr.reset();\n\n        pListening = Pattern.compile(\"^\" + listening + \".*?\", Pattern.CASE_INSENSITIVE);\n    }\n\n    @Override\n    protected void onServiceConnected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onServiceConnected\");\n        }\n\n        initInterceptGoogle = SPH.getInterceptGoogle(getApplicationContext());\n        initAnnounceNotifications = SPH.getAnnounceNotifications(getApplicationContext());\n\n        setDynamicContent();\n    }\n\n    /**\n     * Set the content this service should be receiving\n     */\n    private void setDynamicContent() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setDynamicContent: interceptGoogle: \" + initInterceptGoogle);\n            MyLog.i(CLS_NAME, \"setDynamicContent: announceNotifications: \" + initAnnounceNotifications);\n        }\n\n        if (!initInterceptGoogle && !initAnnounceNotifications) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setDynamicContent: none required: finishing\");\n            }\n\n//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n//                this.disableSelf();\n//            }\n//\n//            this.stopSelf();\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setDynamicContent: updating content\");\n            }\n\n            final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();\n\n            serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;\n            serviceInfo.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;\n            serviceInfo.notificationTimeout = UPDATE_TIMEOUT;\n\n            if (initInterceptGoogle && initAnnounceNotifications) {\n                serviceInfo.eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED\n                        | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED\n                        | AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;\n            } else if (initInterceptGoogle) {\n                serviceInfo.packageNames = new String[]{Installed.PACKAGE_NAME_GOOGLE_NOW};\n                serviceInfo.eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED\n                        | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED;\n            } else {\n                serviceInfo.eventTypes = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;\n            }\n\n            this.setServiceInfo(serviceInfo);\n        }\n    }\n\n    /**\n     * Check if we need to update the content we are receiving by comparing the current content values to the ones checked\n     * on the most recent accessibility event.\n     *\n     * @param interceptGoogle       if we should be intercepting Google Now commands\n     * @param announceNotifications if we should be announcing notification content\n     */\n    private void updateServiceInfo(final boolean interceptGoogle, final boolean announceNotifications) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"updateServiceInfo\");\n        }\n\n        if (initInterceptGoogle != interceptGoogle || initAnnounceNotifications != announceNotifications) {\n            initInterceptGoogle = interceptGoogle;\n            initAnnounceNotifications = announceNotifications;\n            setDynamicContent();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"updateServiceInfo: no change\");\n            }\n        }\n    }\n\n    @Override\n    public void onAccessibilityEvent(final AccessibilityEvent event) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onAccessibilityEvent\");\n        }\n\n        updateServiceInfo(SPH.getInterceptGoogle(getApplicationContext()),\n                SPH.getAnnounceNotifications(getApplicationContext()));\n\n        if (!initAnnounceNotifications && !initInterceptGoogle) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onAccessibilityEvent: not required\");\n            }\n            return;\n        }\n\n        if (event != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onAccessibilityEvent: contentDesc: \" + event.getContentDescription());\n                getEventType(event.getEventType());\n            }\n\n            AccessibilityNodeInfo source = null;\n\n            switch (event.getEventType()) {\n\n                case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:\n\n                    if (initAnnounceNotifications) {\n\n                        final Parcelable parcelable = event.getParcelableData();\n\n                        if (parcelable != null) {\n\n                            if (parcelable instanceof Notification) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"fast instance of Notification: continuing\");\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"fast not instance of Notification\");\n                                }\n                                return;\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"fast parcelable null\");\n                            }\n                            return;\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast not announcing notifications\");\n                        }\n                        return;\n                    }\n\n                    break;\n                case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:\n                case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:\n\n                    if (initInterceptGoogle) {\n\n                        if (event.getPackageName() != null && pGoogleNow.matcher(event.getPackageName()).matches()) {\n\n                            source = event.getSource();\n\n                            if (source == null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast: source null\");\n                                }\n                                return;\n                            }\n\n                            if (source.getClassName() == null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast: source className null\");\n                                }\n\n                                try {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast: recycling source\");\n                                    }\n                                    source.recycle();\n                                } catch (final IllegalStateException e) {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"onAccessibilityEvent: fast: IllegalStateException source recycle\");\n                                        e.printStackTrace();\n                                    }\n                                }\n\n                                return;\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast: checking for google: false\");\n                            }\n                            return;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: fast: not intercepting Google\");\n                        }\n                        return;\n                    }\n\n                    break;\n\n            }\n\n            switch (event.getEventType()) {\n\n                case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_WINDOW_CONTENT_CHANGED\");\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: checking for google: true\");\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: event.getPackageName: \" + event.getPackageName());\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: event.getClassName: \" + event.getClassName());\n                    }\n\n                    //noinspection ConstantConditions\n                    if (pGoogleNowInterim.matcher(source.getClassName()).matches()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: className interim: true\");\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: source.getClassName: \" + source.getClassName());\n                        }\n\n                        if (source.getText() != null) {\n\n                            final String text = source.getText().toString();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: interim text: \" + text);\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: interim contentDesc: \" + source.getContentDescription());\n                            }\n\n                            if (UtilsString.notNaked(text)) {\n\n                                if (!commandPreviousInterimChecked(text)) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: false\");\n                                    }\n\n                                    previousCommandInterimChecked = text;\n\n                                    if (interimMatch(text)) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: child: interim match: true\");\n                                        }\n\n                                        if (commandDelaySufficient(event.getEventTime())) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: true\");\n                                            }\n\n                                            if (!commandPreviousMatches(text)) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: false\");\n                                                }\n\n                                                previousCommandTime = event.getEventTime();\n                                                previousCommandProcessed = text;\n\n                                                killGoogle(true);\n                                                process(text);\n\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: true\");\n                                                }\n                                            }\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: false\");\n                                            }\n                                        }\n                                        break;\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: child: interim match: false\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: true\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: interim text: naked\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: interim text: null\");\n                            }\n                        }\n                    } else if (pGoogleNowFinal.matcher(source.getClassName()).matches()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: className final: true\");\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: source.getClassName: \" + source.getClassName());\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: source.getText: \" + source.getText());\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: source.contentDesc: \" + source.getContentDescription());\n                        }\n\n                        final int childCount = source.getChildCount();\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: childCount: \" + childCount);\n                        }\n\n                        if (childCount > 0) {\n                            for (int i = 0; i < childCount; i++) {\n\n                                final String text = examineChild(source.getChild(i));\n\n                                if (UtilsString.notNaked(text)) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: child text: \" + text);\n                                    }\n\n                                    if (!commandPreviousFinalChecked(text)) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: false\");\n                                        }\n\n                                        previousCommandFinalChecked = text;\n\n                                        if (finalMatch(text)) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: child: final match: true\");\n                                            }\n\n                                            if (commandDelaySufficient(event.getEventTime())) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: true\");\n                                                }\n\n                                                if (!commandPreviousMatches(text)) {\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: false\");\n                                                    }\n\n                                                    previousCommandTime = event.getEventTime();\n                                                    previousCommandProcessed = text;\n\n                                                    killGoogle(true);\n                                                    process(text);\n\n                                                } else {\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: true\");\n                                                    }\n                                                }\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: false\");\n                                                }\n                                            }\n                                            break;\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: child: final match: false\");\n                                            }\n                                        }\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: true\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: child text: naked\");\n                                    }\n                                }\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: className: unwanted \" + source.getClassName());\n                        }\n\n                        if (EXTRA_VERBOSE) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted contentDesc: \" + source.getContentDescription());\n                            }\n\n                            if (source.getText() != null) {\n\n                                final String text = source.getText().toString();\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted text: \" + text);\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted text: null\");\n                                }\n                            }\n\n                            final int childCount = source.getChildCount();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted childCount: \" + childCount);\n                            }\n\n                            if (childCount > 0) {\n\n                                for (int i = 0; i < childCount; i++) {\n\n                                    final String text = examineChild(source.getChild(i));\n\n                                    if (UtilsString.notNaked(text)) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted child text: \" + text);\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    break;\n\n                case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_TEXT_SELECTION_CHANGED\");\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: checking for google: true\");\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: event.getPackageName: \" + event.getPackageName());\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: event.getClassName: \" + event.getClassName());\n                    }\n\n                    //noinspection ConstantConditions\n                    if (pGoogleNowFinal.matcher(source.getClassName()).matches()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: className final editText: true\");\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: source.getClassName: \" + source.getClassName());\n                        }\n\n                        if (source.getText() != null) {\n\n                            final String text = source.getText().toString();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: final editText text: \" + text);\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: final editText contentDesc: \" + source.getContentDescription());\n                            }\n\n                            if (UtilsString.notNaked(text)) {\n\n                                if (!commandPreviousFinalChecked(text)) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: false\");\n                                    }\n\n                                    previousCommandFinalChecked = text;\n\n                                    if (finalMatch(text)) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: child: final match: true\");\n                                        }\n\n                                        if (commandDelaySufficient(event.getEventTime())) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: true\");\n                                            }\n\n                                            if (!commandPreviousMatches(text)) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: false\");\n                                                }\n\n                                                previousCommandTime = event.getEventTime();\n                                                previousCommandProcessed = text;\n\n                                                killGoogle(true);\n                                                process(text);\n\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousMatches: true\");\n                                                }\n                                            }\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandDelaySufficient: false\");\n                                            }\n                                        }\n                                        break;\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: final editText match: false\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: commandPreviousChecked: true\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: final editText: naked\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: final editText text: null\");\n                            }\n                        }\n                    }\n\n                    break;\n\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: not interested in type\");\n                    }\n\n                    if (EXTRA_VERBOSE) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted contentDesc: \" + source.getContentDescription());\n                        }\n\n                        if (source.getText() != null) {\n\n                            final String text = source.getText().toString();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted text: \" + text);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted text: null\");\n                            }\n                        }\n\n                        final int childCount = source.getChildCount();\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted childCount: \" + childCount);\n                        }\n\n                        if (childCount > 0) {\n\n                            for (int i = 0; i < childCount; i++) {\n\n                                final String text = examineChild(source.getChild(i));\n\n                                if (text != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"onAccessibilityEvent: unwanted child text: \" + text);\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    break;\n\n            }\n\n            try {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: recycling source\");\n                }\n                if (source != null) {\n                    source.recycle();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onAccessibilityEvent: IllegalStateException source recycle\");\n                    e.printStackTrace();\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onAccessibilityEvent: event null\");\n            }\n        }\n\n    }\n\n    /**\n     * Check if the previous command was actioned within the {@link #COMMAND_UPDATE_DELAY}\n     *\n     * @param currentTime the time of the current {@link AccessibilityEvent}\n     * @return true if the delay is sufficient to proceed, false otherwise\n     */\n    private boolean commandDelaySufficient(final long currentTime) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"commandDelaySufficient\");\n        }\n\n        final long delay = (currentTime - COMMAND_UPDATE_DELAY);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"commandDelaySufficient: delay: \" + delay);\n            MyLog.i(CLS_NAME, \"commandDelaySufficient: previousCommandTime: \" + previousCommandTime);\n        }\n\n        return delay > previousCommandTime;\n    }\n\n    /**\n     * Check if the previous command/text matches the previous text we processed\n     *\n     * @param text the current text\n     * @return true if the text matches the previous text we processed, false otherwise.\n     */\n    private boolean commandPreviousMatches(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"commandPreviousMatches\");\n        }\n\n        return previousCommandProcessed != null && previousCommandProcessed.matches(text);\n    }\n\n    /**\n     * Check if the previous command/text matches the current text we are considering processing\n     *\n     * @param text the current text\n     * @return true if the text matches the previous text we processed, false otherwise.\n     */\n    private boolean commandPreviousInterimChecked(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"commandPreviousInterimChecked\");\n        }\n\n        return previousCommandInterimChecked != null && previousCommandInterimChecked.matches(text);\n    }\n\n    /**\n     * Check if the previous command/text matches the current text we are considering processing\n     *\n     * @param text the current text\n     * @return true if the text matches the previous text we processed, false otherwise.\n     */\n    private boolean commandPreviousFinalChecked(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"commandPreviousFinalChecked\");\n        }\n\n        return previousCommandFinalChecked != null && previousCommandFinalChecked.matches(text);\n    }\n\n    /**\n     * Check if the interim text matches a command we want to intercept\n     *\n     * @param text the intercepted text\n     * @return true if the text matches a command false otherwise\n     */\n    private boolean interimMatch(@NonNull final String text) {\n\n        if (isListening(text)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"interimMatch: listening\");\n            }\n            return false;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"interimMatch: Processing: \" + text);\n            }\n        }\n\n        final ArrayList<String> toResolve = new ArrayList<>(1);\n        toResolve.add(text);\n\n        final float[] confidence = new float[1];\n        confidence[0] = 1f;\n\n        final ArrayList<Pair<CC, Float>> resolveArray = new Resolve(getApplicationContext(),\n                toResolve, confidence, sl, true).resolve();\n\n        if (DEBUG) {\n            if (UtilsList.notNaked(resolveArray)) {\n                MyLog.i(CLS_NAME, \"interimMatch: resolveArray size:  \" + resolveArray.size());\n                MyLog.i(CLS_NAME, \"interimMatch: resolveArray CC:  \" + resolveArray.get(0).first.name());\n            } else {\n                MyLog.i(CLS_NAME, \"interimMatch: resolveArray naked\");\n            }\n        }\n\n        return UtilsList.notNaked(resolveArray);\n    }\n\n    /**\n     * Check if the final text matches a command we want to intercept\n     *\n     * @param text the intercepted text\n     * @return true if the text matches a command false otherwise\n     */\n    private boolean finalMatch(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"finalMatch\");\n        }\n\n        if (isListening(text)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"finalMatch: listening\");\n            }\n            return false;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"finalMatch: Processing: \" + text);\n            }\n        }\n\n        final ArrayList<String> toResolve = new ArrayList<>(1);\n        toResolve.add(text);\n\n        final float[] confidence = new float[1];\n        confidence[0] = 1f;\n\n        final ArrayList<Pair<CC, Float>> resolveArray = new Resolve(getApplicationContext(),\n                toResolve, confidence, sl, false).resolve();\n\n        if (DEBUG) {\n            if (UtilsList.notNaked(resolveArray)) {\n                MyLog.i(CLS_NAME, \"finalMatch: resolveArray size:  \" + resolveArray.size());\n                MyLog.i(CLS_NAME, \"finalMatch: resolveArray CC:  \" + resolveArray.get(0).first.name());\n            } else {\n                MyLog.i(CLS_NAME, \"finalMatch: resolveArray naked\");\n            }\n        }\n\n        return UtilsList.notNaked(resolveArray);\n    }\n\n    /**\n     * Check if the text matches the standard 'listening...' text\n     *\n     * @param text the input text\n     * @return true if a match is found, false otherwise\n     */\n    private boolean isListening(@NonNull final String text) {\n        return pListening.matcher(text).matches();\n    }\n\n    /**\n     * Process the extracted text as identified as a command\n     *\n     * @param text the command to process\n     */\n    private void process(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"process\");\n        }\n\n        final Bundle bundle = new Bundle();\n\n        final ArrayList<String> voiceResults = new ArrayList<>(1);\n        voiceResults.add(text);\n\n        final float[] confidence = new float[1];\n        confidence[0] = 1f;\n\n        bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, voiceResults);\n        bundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, confidence);\n        bundle.putInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_GOOGLE_NOW);\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                new RecognitionAction(SaiyAccessibilityService.this.getApplicationContext(), SPH.getVRLocale(SaiyAccessibilityService.this.getApplicationContext()),\n                        SPH.getTTSLocale(SaiyAccessibilityService.this.getApplicationContext()), sl, bundle);\n            }\n        });\n    }\n\n    /**\n     * Kill or reset Google\n     */\n    private void killGoogle(final boolean terminate) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"killGoogle\");\n        }\n\n        if (terminate) {\n            AsyncTask.execute(new Runnable() {\n                @Override\n                public void run() {\n                    ExecuteIntent.googleNow(SaiyAccessibilityService.this.getApplicationContext(), \"\");\n\n                    try {\n                        Thread.sleep(150);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"killGoogle InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n\n                    ExecuteIntent.goHome(SaiyAccessibilityService.this.getApplicationContext());\n                    UtilsApplication.killPackage(SaiyAccessibilityService.this.getApplicationContext(), IntentConstants.PACKAGE_NAME_GOOGLE_NOW);\n                }\n            });\n        } else {\n            ExecuteIntent.googleNow(getApplicationContext(), \"\");\n        }\n    }\n\n    /**\n     * Recursively examine the {@link AccessibilityNodeInfo} object\n     *\n     * @param parent the {@link AccessibilityNodeInfo} parent object\n     * @return the extracted text or null if no text was contained in the child objects\n     */\n    private String examineChild(@Nullable final AccessibilityNodeInfo parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineChild\");\n        }\n\n        if (parent != null) {\n\n            for (int i = 0; i < parent.getChildCount(); i++) {\n\n                final AccessibilityNodeInfo nodeInfo = parent.getChild(i);\n\n                if (nodeInfo != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"examineChild: nodeInfo: getClassName: \" + nodeInfo.getClassName());\n                        MyLog.i(CLS_NAME, \"examineChild: nodeInfo: contentDesc: \" + nodeInfo.getContentDescription());\n                    }\n\n                    if (nodeInfo.getText() != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"examineChild: have text: returning: \" + nodeInfo.getText().toString());\n                        }\n                        return nodeInfo.getText().toString();\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"examineChild: text: null: recurse\");\n                        }\n\n                        final int childCount = nodeInfo.getChildCount();\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"examineChild: childCount: \" + childCount);\n                        }\n\n                        if (childCount > 0) {\n\n                            final String text = examineChild(nodeInfo);\n\n                            if (text != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"examineChild: have recursive text: returning: \" + text);\n                                }\n                                return text;\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"examineChild: recursive text: null\");\n                                }\n                            }\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"examineChild: nodeInfo null\");\n                    }\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"examineChild: parent null\");\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Check the event type for debugging\n     *\n     * @param eventType the Accessibility event type\n     * @return the Accessibility event type\n     */\n    private int getEventType(final int eventType) {\n\n        switch (eventType) {\n\n            case AccessibilityEvent.TYPE_ANNOUNCEMENT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_ANNOUNCEMENT\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_ASSIST_READING_CONTEXT\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_GESTURE_DETECTION_END\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_GESTURE_DETECTION_START\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_NOTIFICATION_STATE_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_TOUCH_EXPLORATION_GESTURE_END\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_TOUCH_EXPLORATION_GESTURE_START\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_TOUCH_INTERACTION_END\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_TOUCH_INTERACTION_START\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_ACCESSIBILITY_FOCUSED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_CLICKED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_CLICKED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_CONTEXT_CLICKED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_FOCUSED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_FOCUSED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_HOVER_ENTER\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_HOVER_EXIT\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_LONG_CLICKED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_SCROLLED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_SCROLLED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_SELECTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_SELECTED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_TEXT_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_TEXT_SELECTION_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_WINDOW_CONTENT_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_WINDOW_STATE_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPE_WINDOWS_CHANGED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPE_WINDOWS_CHANGED\");\n                }\n                break;\n            case AccessibilityEvent.TYPES_ALL_MASK:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccessibilityEvent: TYPES_ALL_MASK\");\n                }\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onAccessibilityEvent: default\");\n                }\n                break;\n        }\n\n        return eventType;\n    }\n\n    @Override\n    public void onInterrupt() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onInterrupt\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/Algorithm.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n\n/**\n * Class to hold our algorithm constants and utility methods.\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic enum Algorithm {\n\n    REGEX,\n    JARO_WINKLER,\n    LEVENSHTEIN,\n    SOUNDEX,\n    METAPHONE,\n    DOUBLE_METAPHONE,\n    FUZZY,\n    NEEDLEMAN_WUNCH,\n    MONGE_ELKAN;\n\n    public static final double JWD_LOWER_THRESHOLD = 0.88;\n    public static final double JWD_UPPER_THRESHOLD = 0.93;\n    public static final double JWD_MAX_THRESHOLD = 1.0;\n    public static final double LEV_UPPER_THRESHOLD = 3.9;\n    public static final double LEV_MAX_THRESHOLD = 0.0;\n    public static final double ME_UPPER_THRESHOLD = 0.90;\n    public static final double ME_MAX_THRESHOLD = 1.0;\n    public static final double FUZZY_MULTIPLIER = 2.25;\n    public static final double NW_UPPER_THRESHOLD = 0.85;\n    public static final double NW_MAX_THRESHOLD = 1.0;\n    public static final double SOUNDEX_UPPER_THRESHOLD = 2.9;\n    public static final double SOUNDEX_MAX_THRESHOLD = 4;\n\n    private static final double LENGTH_THRESHOLD = 0.75;\n\n    /**\n     * A utility method to compare the lengths of two strings and check the difference falls\n     * within {@link #LENGTH_THRESHOLD}.\n     *\n     * @param s1 first string\n     * @param s2 second string\n     * @return true if the length difference falls within {@link #LENGTH_THRESHOLD}. False otherwise.\n     */\n    public static boolean checkLength(@NonNull final String s1, @NonNull final String s2) {\n        if (s1.length() < s2.length()) {\n            return ((double) s1.length() / s2.length()) > LENGTH_THRESHOLD;\n        } else {\n            return ((double) s2.length() / s1.length()) > LENGTH_THRESHOLD;\n        }\n    }\n\n    /**\n     * Get the standard {@link Algorithm} list to use dependent on the {@link SupportedLanguage}.\n     * Advanced users may have selected or deselected algorithms from the standard list, so first we\n     * check {@link SPH#getAlgorithms(Context)} to see if a setting has been applied.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return a list of {@link Algorithm}\n     */\n    public static Algorithm[] getAlgorithms(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final String userAlgorithms = SPH.getAlgorithms(ctx);\n\n        if (UtilsString.notNaked(userAlgorithms)) {\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            return gson.fromJson(userAlgorithms, Algorithm[].class);\n        } else {\n\n            switch (sl) {\n                case ENGLISH:\n                case ENGLISH_US:\n                    return Algorithm.values();\n            }\n        }\n\n        return Algorithm.values();\n    }\n\n    /**\n     * Get the standard {@link Algorithm} list to use dependent on the {@link Locale}.\n     * Advanced users may have selected or deselected algorithms from the standard list, so first we\n     * check {@link SPH#getAlgorithms(Context)} to see if a setting has been applied.\n     *\n     * @param ctx    the application context\n     * @param locale the {@link Locale}\n     * @return a list of {@link Algorithm}\n     */\n    public static Algorithm[] getAlgorithms(@NonNull final Context ctx, @NonNull final Locale locale) {\n\n        final String userAlgorithms = SPH.getAlgorithms(ctx);\n\n        if (UtilsString.notNaked(userAlgorithms)) {\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            return gson.fromJson(userAlgorithms, Algorithm[].class);\n        } else {\n\n            if (locale.toString().toLowerCase(locale).startsWith(\"en\")) {\n                return Algorithm.values();\n            }\n        }\n\n        return Algorithm.values();\n    }\n\n    /**\n     * Set the standard {@link Algorithm} list to use dependent on the {@link SupportedLanguage}.\n     * An advanced user has initiated this method to select or deselect algorithms from the standard\n     * list containing only those supported by their {@link SPH#getAlgorithms(Context)}.\n     *\n     * @param ctx        the application context\n     * @param algorithms a list of {@link Algorithm}\n     */\n    public static void setAlgorithms(@NonNull final Context ctx, @NonNull final Algorithm[] algorithms) {\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        SPH.setAlgorithms(ctx, gson.toJson(algorithms));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/distance/EditDistance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.algorithms.distance;\n\n/**\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\n\n/**\n * Interface for <a href=\"http://en.wikipedia.org/wiki/Edit_distance\">Edit Distances</a>.\n * <p/>\n * A edit distance measures the similarity between two character sequences. Closer strings\n * have shorter distances, and vice-versa.\n * <p>\n * This is a BiFunction CharSequence, CharSequence.\n * <p>\n * The <code>apply</code> method accepts a pair of {@link CharSequence} parameters\n * and returns an <code>R</code> type similarity score.\n * </p>\n *\n * @param <R> The type of similarity score unit used by this EditDistance.\n */\npublic interface EditDistance<R> {\n\n    /**\n     * Compares two CharSequences.\n     *\n     * @param left  the first CharSequence\n     * @param right the second CharSequence\n     * @return the similarity score between two CharSequences\n     */\n    R apply(final CharSequence left, final CharSequence right);\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/distance/jarowinkler/JaroWinklerDistance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.algorithms.distance.jarowinkler;\n\nimport ai.saiy.android.algorithms.distance.EditDistance;\n\n/**\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class JaroWinklerDistance implements EditDistance<Double> {\n\n    /**\n     * The default prefix length limit set to four.\n     */\n    private static final int PREFIX_LENGTH_LIMIT = 4;\n    /**\n     * Represents a failed index search.\n     */\n    private static final int INDEX_NOT_FOUND = -1;\n\n    /**\n     * Find the Jaro Winkler Distance which indicates the similarity score\n     * between two CharSequences.\n     * <p/>\n     * <pre>\n     * distance.apply(null, null)          = IllegalArgumentException\n     * distance.apply(\"\",\"\")               = 0.0\n     * distance.apply(\"\",\"a\")              = 0.0\n     * distance.apply(\"aaapppp\", \"\")       = 0.0\n     * distance.apply(\"frog\", \"fog\")       = 0.93\n     * distance.apply(\"fly\", \"ant\")        = 0.0\n     * distance.apply(\"elephant\", \"hippo\") = 0.44\n     * distance.apply(\"hippo\", \"elephant\") = 0.44\n     * distance.apply(\"hippo\", \"zzzzzzzz\") = 0.0\n     * distance.apply(\"hello\", \"hallo\")    = 0.88\n     * distance.apply(\"ABC Corporation\", \"ABC Corp\") = 0.91\n     * distance.apply(\"D N H Enterprises Inc\", \"D &amp; H Enterprises, Inc.\") = 0.93\n     * distance.apply(\"My Gym Children's Fitness Center\", \"My Gym. Childrens Fitness\") = 0.94\n     * distance.apply(\"PENNSYLVANIA\", \"PENNCISYLVNIA\")    = 0.9\n     * </pre>\n     *\n     * @param left  the first String, must not be null\n     * @param right the second String, must not be null\n     * @return result distance\n     * @throws IllegalArgumentException if either String input {@code null}\n     */\n    @Override\n    public Double apply(CharSequence left, CharSequence right) {\n        final double defaultScalingFactor = 0.1;\n        final double percentageRoundValue = 100.0;\n\n        if (left == null || right == null) {\n            throw new IllegalArgumentException(\"Strings must not be null\");\n        }\n\n        final double jaro = score(left, right);\n        final int cl = commonPrefixLength(left, right);\n        return Math.round((jaro + defaultScalingFactor\n                * cl * (1.0 - jaro)) * percentageRoundValue) / percentageRoundValue;\n    }\n\n    /**\n     * Calculates the number of characters from the beginning of the strings\n     * that match exactly one-to-one, up to a maximum of four (4) characters.\n     *\n     * @param first  The first string.\n     * @param second The second string.\n     * @return A number between 0 and 4.\n     */\n    private static int commonPrefixLength(final CharSequence first,\n                                          final CharSequence second) {\n        final int result = getCommonPrefix(first.toString(), second.toString())\n                .length();\n\n        // Limit the result to 4.\n        return result > PREFIX_LENGTH_LIMIT ? PREFIX_LENGTH_LIMIT : result;\n    }\n\n    /**\n     * Compares all Strings in an array and returns the initial sequence of\n     * characters that is common to all of them.\n     * <p/>\n     * <p>\n     * For example,\n     * <code>getCommonPrefix(new String[] {\"i am a machine\", \"i am a robot\"}) -&gt; \"i am a \"</code>\n     * </p>\n     * <p/>\n     * <pre>\n     * getCommonPrefix(null) = \"\"\n     * getCommonPrefix(new String[] {}) = \"\"\n     * getCommonPrefix(new String[] {\"abc\"}) = \"abc\"\n     * getCommonPrefix(new String[] {null, null}) = \"\"\n     * getCommonPrefix(new String[] {\"\", \"\"}) = \"\"\n     * getCommonPrefix(new String[] {\"\", null}) = \"\"\n     * getCommonPrefix(new String[] {\"abc\", null, null}) = \"\"\n     * getCommonPrefix(new String[] {null, null, \"abc\"}) = \"\"\n     * getCommonPrefix(new String[] {\"\", \"abc\"}) = \"\"\n     * getCommonPrefix(new String[] {\"abc\", \"\"}) = \"\"\n     * getCommonPrefix(new String[] {\"abc\", \"abc\"}) = \"abc\"\n     * getCommonPrefix(new String[] {\"abc\", \"a\"}) = \"a\"\n     * getCommonPrefix(new String[] {\"ab\", \"abxyz\"}) = \"ab\"\n     * getCommonPrefix(new String[] {\"abcde\", \"abxyz\"}) = \"ab\"\n     * getCommonPrefix(new String[] {\"abcde\", \"xyz\"}) = \"\"\n     * getCommonPrefix(new String[] {\"xyz\", \"abcde\"}) = \"\"\n     * getCommonPrefix(new String[] {\"i am a machine\", \"i am a robot\"}) = \"i am a \"\n     * </pre>\n     *\n     * @param strs array of String objects, entries may be null\n     * @return the initial sequence of characters that are common to all Strings\n     * in the array; empty String if the array is null, the elements are\n     * all null or if there is no common prefix.\n     */\n    private static String getCommonPrefix(final String... strs) {\n        if (strs == null || strs.length == 0) {\n            return \"\";\n        }\n        final int smallestIndexOfDiff = indexOfDifference((CharSequence[]) strs);\n        if (smallestIndexOfDiff == INDEX_NOT_FOUND) {\n            // all strings were identical\n            if (strs[0] == null) {\n                return \"\";\n            }\n            return strs[0];\n        } else if (smallestIndexOfDiff == 0) {\n            // there were no common initial characters\n            return \"\";\n        } else {\n            // we found a common initial character sequence\n            return strs[0].substring(0, smallestIndexOfDiff);\n        }\n    }\n\n    /**\n     * This method returns the Jaro-Winkler score for string matching.\n     *\n     * @param first  the first string to be matched\n     * @param second the second string to be machted\n     * @return matching score without scaling factor impact\n     */\n    protected static double score(final CharSequence first,\n                                  final CharSequence second) {\n        String shorter;\n        String longer;\n\n        // Determine which String is longer.\n        if (first.length() > second.length()) {\n            longer = first.toString().toLowerCase();\n            shorter = second.toString().toLowerCase();\n        } else {\n            longer = second.toString().toLowerCase();\n            shorter = first.toString().toLowerCase();\n        }\n\n        // Calculate the half length() distance of the shorter String.\n        final int halflength = shorter.length() / 2 + 1;\n\n        // Find the set of matching characters between the shorter and longer\n        // strings. Note that\n        // the set of matching characters may be different depending on the\n        // order of the strings.\n        final String m1 = getSetOfMatchingCharacterWithin(shorter, longer,\n                halflength);\n        final String m2 = getSetOfMatchingCharacterWithin(longer, shorter,\n                halflength);\n\n        // If one or both of the sets of common characters is empty, then\n        // there is no similarity between the two strings.\n        if (m1.length() == 0 || m2.length() == 0) {\n            return 0.0;\n        }\n\n        // If the set of common characters is not the same size, then\n        // there is no similarity between the two strings, either.\n        if (m1.length() != m2.length()) {\n            return 0.0;\n        }\n\n        // Calculate the number of transposition between the two sets\n        // of common characters.\n        final int transpositions = transpositions(m1, m2);\n\n        final double defaultDenominator = 3.0;\n\n        // Calculate the distance.\n        return (m1.length() / ((double) shorter.length())\n                + m2.length() / ((double) longer.length()) + (m1.length() - transpositions)\n                / ((double) m1.length())) / defaultDenominator;\n    }\n\n    /**\n     * Calculates the number of transposition between two strings.\n     *\n     * @param first  The first string.\n     * @param second The second string.\n     * @return The number of transposition between the two strings.\n     */\n    private static int transpositions(final CharSequence first,\n                                      final CharSequence second) {\n        int transpositions = 0;\n        for (int i = 0; i < first.length(); i++) {\n            if (first.charAt(i) != second.charAt(i)) {\n                transpositions++;\n            }\n        }\n        return transpositions / 2;\n    }\n\n    /**\n     * Compares all CharSequences in an array and returns the index at which the\n     * CharSequences begin to differ.\n     * <p/>\n     * <p>\n     * For example,\n     * <code>indexOfDifference(new String[] {\"i am a machine\", \"i am a robot\"}) -&gt; 7</code>\n     * </p>\n     * <p/>\n     * <pre>\n     * distance.indexOfDifference(null) = -1\n     * distance.indexOfDifference(new String[] {}) = -1\n     * distance.indexOfDifference(new String[] {\"abc\"}) = -1\n     * distance.indexOfDifference(new String[] {null, null}) = -1\n     * distance.indexOfDifference(new String[] {\"\", \"\"}) = -1\n     * distance.indexOfDifference(new String[] {\"\", null}) = 0\n     * distance.indexOfDifference(new String[] {\"abc\", null, null}) = 0\n     * distance.indexOfDifference(new String[] {null, null, \"abc\"}) = 0\n     * distance.indexOfDifference(new String[] {\"\", \"abc\"}) = 0\n     * distance.indexOfDifference(new String[] {\"abc\", \"\"}) = 0\n     * distance.indexOfDifference(new String[] {\"abc\", \"abc\"}) = -1\n     * distance.indexOfDifference(new String[] {\"abc\", \"a\"}) = 1\n     * distance.indexOfDifference(new String[] {\"ab\", \"abxyz\"}) = 2\n     * distance.indexOfDifference(new String[] {\"abcde\", \"abxyz\"}) = 2\n     * distance.indexOfDifference(new String[] {\"abcde\", \"xyz\"}) = 0\n     * distance.indexOfDifference(new String[] {\"xyz\", \"abcde\"}) = 0\n     * distance.indexOfDifference(new String[] {\"i am a machine\", \"i am a robot\"}) = 7\n     * </pre>\n     *\n     * @param css array of CharSequences, entries may be null\n     * @return the index where the strings begin to differ; -1 if they are all\n     * equal\n     */\n    private static int indexOfDifference(final CharSequence... css) {\n        if (css == null || css.length <= 1) {\n            return INDEX_NOT_FOUND;\n        }\n        boolean anyStringNull = false;\n        boolean allStringsNull = true;\n        final int arrayLen = css.length;\n        int shortestStrLen = Integer.MAX_VALUE;\n        int longestStrLen = 0;\n\n        // find the min and max string lengths; this avoids checking to make\n        // sure we are not exceeding the length of the string each time through\n        // the bottom loop.\n        for (final CharSequence cs : css) {\n            if (cs == null) {\n                anyStringNull = true;\n                shortestStrLen = 0;\n            } else {\n                allStringsNull = false;\n                shortestStrLen = Math.min(cs.length(), shortestStrLen);\n                longestStrLen = Math.max(cs.length(), longestStrLen);\n            }\n        }\n\n        // handle lists containing all nulls or all empty strings\n        if (allStringsNull || longestStrLen == 0 && !anyStringNull) {\n            return INDEX_NOT_FOUND;\n        }\n\n        // handle lists containing some nulls or some empty strings\n        if (shortestStrLen == 0) {\n            return 0;\n        }\n\n        // find the position with the first difference across all strings\n        int firstDiff = -1;\n        for (int stringPos = 0; stringPos < shortestStrLen; stringPos++) {\n            final char comparisonChar = css[0].charAt(stringPos);\n            for (int arrayPos = 1; arrayPos < arrayLen; arrayPos++) {\n                if (css[arrayPos].charAt(stringPos) != comparisonChar) {\n                    firstDiff = stringPos;\n                    break;\n                }\n            }\n            if (firstDiff != -1) {\n                break;\n            }\n        }\n\n        if (firstDiff == -1 && shortestStrLen != longestStrLen) {\n            // we compared all of the characters up to the length of the\n            // shortest string and didn't find a match, but the string lengths\n            // vary, so return the length of the shortest string.\n            return shortestStrLen;\n        }\n        return firstDiff;\n    }\n\n    /**\n     * Gets a set of matching characters between two strings.\n     * <p/>\n     * <p>\n     * Two characters from the first string and the second string are\n     * considered matching if the character's respective positions are no\n     * farther than the limit value.\n     * </p>\n     *\n     * @param first  The first string.\n     * @param second The second string.\n     * @param limit  The maximum distance to consider.\n     * @return A string contain the set of common characters.\n     */\n    private static String getSetOfMatchingCharacterWithin(\n            final CharSequence first, final CharSequence second, final int limit) {\n        final StringBuilder common = new StringBuilder();\n        final StringBuilder copy = new StringBuilder(second);\n\n        for (int i = 0; i < first.length(); i++) {\n            final char ch = first.charAt(i);\n            boolean found = false;\n\n            // See if the character is within the limit positions away from the\n            // original position of that character.\n            for (int j = Math.max(0, i - limit); !found\n                    && j < Math.min(i + limit, second.length()); j++) {\n                if (copy.charAt(j) == ch) {\n                    found = true;\n                    common.append(ch);\n                    copy.setCharAt(j, '*');\n                }\n            }\n        }\n        return common.toString();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/distance/jarowinkler/JaroWinklerHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.distance.jarowinkler;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Jaro-Winkler algorithm.\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class JaroWinklerHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = JaroWinklerHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public JaroWinklerHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                             @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link JaroWinklerDistance} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double jwdUpperThreshold = SPH.getJaroWinklerUpper(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double distance;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = jwd.apply(phrase, vd);\n\n                if (distance > jwdUpperThreshold) {\n\n                    container.setUtterance(vd);\n                    container.setScore(distance);\n\n                    if (distance == Algorithm.JWD_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + phrase);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        toKeep.add(SerializationUtils.clone(container));\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.JARO_WINKLER);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link JaroWinklerDistance} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double jwdUpperThreshold = SPH.getJaroWinklerUpper(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = jwd.apply(genericLower, vd);\n\n                if (distance > jwdUpperThreshold) {\n\n                    container = new AlgorithmicContainer();\n                    container.setInput(vd);\n                    container.setGenericMatch(generic);\n                    container.setScore(distance);\n                    container.setAlgorithm(Algorithm.JARO_WINKLER);\n                    container.setParentPosition(i);\n\n                    if (distance == Algorithm.JWD_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + genericLower);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n                        break outer;\n                    } else {\n                        container.setExactMatch(false);\n                        toKeep.add(container);\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/distance/levenshtein/LevenshteinDistance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.algorithms.distance.levenshtein;\n\nimport java.util.Arrays;\n\nimport ai.saiy.android.algorithms.distance.EditDistance;\n\n/**\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class LevenshteinDistance implements EditDistance<Integer> {\n\n    /**\n     * Default instance.\n     */\n    private static final LevenshteinDistance DEFAULT_INSTANCE = new LevenshteinDistance();\n\n    /**\n     * Threshold.\n     */\n    private final Integer threshold;\n\n    /**\n     * <p>\n     * This returns the default instance that uses a version\n     * of the algorithm that does not use a threshold parameter.\n     * </p>\n     *\n     * @see LevenshteinDistance#getDefaultInstance()\n     */\n    public LevenshteinDistance() {\n        this(null);\n    }\n\n    /**\n     * <p>\n     * If the threshold is not null, distance calculations will be limited to a maximum length.\n     * If the threshold is null, the unlimited version of the algorithm will be used.\n     * </p>\n     *\n     * @param threshold If this is null then distances calculations will not be limited.\n     *                  This may not be negative.\n     */\n    public LevenshteinDistance(final Integer threshold) {\n        if (threshold != null && threshold < 0) {\n            throw new IllegalArgumentException(\"Threshold must not be negative\");\n        }\n        this.threshold = threshold;\n    }\n\n    /**\n     * <p>Find the Levenshtein distance between two Strings.</p>\n     * <p/>\n     * <p>A higher score indicates a greater distance.</p>\n     * <p/>\n     * <p>The previous implementation of the Levenshtein distance algorithm\n     * was from <a href=\"http://www.merriampark.com/ld.htm\">http://www.merriampark.com/ld.htm</a></p>\n     * <p/>\n     * <p>Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError\n     * which can occur when my Java implementation is used with very large strings.<br>\n     * This implementation of the Levenshtein distance algorithm\n     * is from <a href=\"http://www.merriampark.com/ldjava.htm\">http://www.merriampark.com/ldjava.htm</a></p>\n     * <p/>\n     * <pre>\n     * distance.apply(null, *)             = IllegalArgumentException\n     * distance.apply(*, null)             = IllegalArgumentException\n     * distance.apply(\"\",\"\")               = 0\n     * distance.apply(\"\",\"a\")              = 1\n     * distance.apply(\"aaapppp\", \"\")       = 7\n     * distance.apply(\"frog\", \"fog\")       = 1\n     * distance.apply(\"fly\", \"ant\")        = 3\n     * distance.apply(\"elephant\", \"hippo\") = 7\n     * distance.apply(\"hippo\", \"elephant\") = 7\n     * distance.apply(\"hippo\", \"zzzzzzzz\") = 8\n     * distance.apply(\"hello\", \"hallo\")    = 1\n     * </pre>\n     *\n     * @param left  the first string, must not be null\n     * @param right the second string, must not be null\n     * @return result distance, or -1\n     * @throws IllegalArgumentException if either String input {@code null}\n     */\n    public Integer apply(CharSequence left, CharSequence right) {\n        if (threshold != null) {\n            return limitedCompare(left, right, threshold);\n        } else {\n            return unlimitedCompare(left, right);\n        }\n    }\n\n    /**\n     * Gets the default instance.\n     *\n     * @return the default instace\n     */\n    private static LevenshteinDistance getDefaultInstance() {\n        return DEFAULT_INSTANCE;\n    }\n\n    /**\n     * Gets the distance threshold.\n     *\n     * @return the distance threshold\n     */\n    public Integer getThreshold() {\n        return threshold;\n    }\n\n    /**\n     * Find the Levenshtein distance between two CharSequences if it's less than or\n     * equal to a given threshold.\n     * <p/>\n     * <p>\n     * This implementation follows from Algorithms on Strings, Trees and\n     * Sequences by Dan Gusfield and Chas Emerick's implementation of the\n     * Levenshtein distance algorithm from <a\n     * href=\"http://www.merriampark.com/ld.htm\"\n     * >http://www.merriampark.com/ld.htm</a>\n     * </p>\n     * <p/>\n     * <pre>\n     * limitedCompare(null, *, *)             = IllegalArgumentException\n     * limitedCompare(*, null, *)             = IllegalArgumentException\n     * limitedCompare(*, *, -1)               = IllegalArgumentException\n     * limitedCompare(\"\",\"\", 0)               = 0\n     * limitedCompare(\"aaapppp\", \"\", 8)       = 7\n     * limitedCompare(\"aaapppp\", \"\", 7)       = 7\n     * limitedCompare(\"aaapppp\", \"\", 6))      = -1\n     * limitedCompare(\"elephant\", \"hippo\", 7) = 7\n     * limitedCompare(\"elephant\", \"hippo\", 6) = -1\n     * limitedCompare(\"hippo\", \"elephant\", 7) = 7\n     * limitedCompare(\"hippo\", \"elephant\", 6) = -1\n     * </pre>\n     *\n     * @param left      the first string, must not be null\n     * @param right     the second string, must not be null\n     * @param threshold the target threshold, must not be negative\n     * @return result distance, or -1\n     */\n    private static int limitedCompare(CharSequence left, CharSequence right, int threshold) {\n        if (left == null || right == null) {\n            throw new IllegalArgumentException(\"Strings must not be null\");\n        }\n        if (threshold < 0) {\n            throw new IllegalArgumentException(\"Threshold must not be negative\");\n        }\n\n        /*\n         * This implementation only computes the distance if it's less than or\n         * equal to the threshold value, returning -1 if it's greater. The\n         * advantage is performance: unbounded distance is O(nm), but a bound of\n         * k allows us to reduce it to O(km) time by only computing a diagonal\n         * stripe of width 2k + 1 of the cost table. It is also possible to use\n         * this to compute the unbounded Levenshtein distance by starting the\n         * threshold at 1 and doubling each time until the distance is found;\n         * this is O(dm), where d is the distance.\n         *\n         * One subtlety comes from needing to ignore entries on the border of\n         * our stripe eg. p[] = |#|#|#|* d[] = *|#|#|#| We must ignore the entry\n         * to the left of the leftmost member We must ignore the entry above the\n         * rightmost member\n         *\n         * Another subtlety comes from our stripe running off the matrix if the\n         * strings aren't of the same size. Since string s is always swapped to\n         * be the shorter of the two, the stripe will always run off to the\n         * upper right instead of the lower left of the matrix.\n         *\n         * As a concrete example, suppose s is of length 5, t is of length 7,\n         * and our threshold is 1. In this case we're going to walk a stripe of\n         * length 3. The matrix would look like so:\n         *\n         * <pre>\n         *    1 2 3 4 5\n         * 1 |#|#| | | |\n         * 2 |#|#|#| | |\n         * 3 | |#|#|#| |\n         * 4 | | |#|#|#|\n         * 5 | | | |#|#|\n         * 6 | | | | |#|\n         * 7 | | | | | |\n         * </pre>\n         *\n         * Note how the stripe leads off the table as there is no possible way\n         * to turn a string of length 5 into one of length 7 in edit distance of\n         * 1.\n         *\n         * Additionally, this implementation decreases memory usage by using two\n         * single-dimensional arrays and swapping them back and forth instead of\n         * allocating an entire n by m matrix. This requires a few minor\n         * changes, such as immediately returning when it's detected that the\n         * stripe has run off the matrix and initially filling the arrays with\n         * large values so that entries we don't compute are ignored.\n         *\n         * See Algorithms on Strings, Trees and Sequences by Dan Gusfield for\n         * some discussion.\n         */\n\n        int n = left.length(); // length of left\n        int m = right.length(); // length of right\n\n        // if one string is empty, the edit distance is necessarily the length\n        // of the other\n        if (n == 0) {\n            return m <= threshold ? m : -1;\n        } else if (m == 0) {\n            return n <= threshold ? n : -1;\n        }\n\n        if (n > m) {\n            // swap the two strings to consume less memory\n            final CharSequence tmp = left;\n            left = right;\n            right = tmp;\n            n = m;\n            m = right.length();\n        }\n\n        int[] p = new int[n + 1]; // 'previous' cost array, horizontally\n        int[] d = new int[n + 1]; // cost array, horizontally\n        int[] tempD; // placeholder to assist in swapping p and d\n\n        // fill in starting table values\n        final int boundary = Math.min(n, threshold) + 1;\n        for (int i = 0; i < boundary; i++) {\n            p[i] = i;\n        }\n        // these fills ensure that the value above the rightmost entry of our\n        // stripe will be ignored in following loop iterations\n        Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE);\n        Arrays.fill(d, Integer.MAX_VALUE);\n\n        // iterates through t\n        for (int j = 1; j <= m; j++) {\n            final char rightJ = right.charAt(j - 1); // jth character of right\n            d[0] = j;\n\n            // compute stripe indices, constrain to array size\n            final int min = Math.max(1, j - threshold);\n            final int max = j > Integer.MAX_VALUE - threshold ? n : Math.min(\n                    n, j + threshold);\n\n            // the stripe may lead off of the table if s and t are of different\n            // sizes\n            if (min > max) {\n                return -1;\n            }\n\n            // ignore entry left of leftmost\n            if (min > 1) {\n                d[min - 1] = Integer.MAX_VALUE;\n            }\n\n            // iterates through [min, max] in s\n            for (int i = min; i <= max; i++) {\n                if (left.charAt(i - 1) == rightJ) {\n                    // diagonally left and up\n                    d[i] = p[i - 1];\n                } else {\n                    // 1 + minimum of cell to the left, to the top, diagonally\n                    // left and up\n                    d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);\n                }\n            }\n\n            // copy current distance counts to 'previous row' distance counts\n            tempD = p;\n            p = d;\n            d = tempD;\n        }\n\n        // if p[n] is greater than the threshold, there's no guarantee on it\n        // being the correct\n        // distance\n        if (p[n] <= threshold) {\n            return p[n];\n        }\n        return -1;\n    }\n\n    /**\n     * <p>Find the Levenshtein distance between two Strings.</p>\n     * <p/>\n     * <p>A higher score indicates a greater distance.</p>\n     * <p/>\n     * <p>The previous implementation of the Levenshtein distance algorithm\n     * was from <a href=\"http://www.merriampark.com/ld.htm\">http://www.merriampark.com/ld.htm</a></p>\n     * <p/>\n     * <p>Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError\n     * which can occur when my Java implementation is used with very large strings.<br>\n     * This implementation of the Levenshtein distance algorithm\n     * is from <a href=\"http://www.merriampark.com/ldjava.htm\">http://www.merriampark.com/ldjava.htm</a></p>\n     * <p/>\n     * <pre>\n     * unlimitedCompare(null, *)             = IllegalArgumentException\n     * unlimitedCompare(*, null)             = IllegalArgumentException\n     * unlimitedCompare(\"\",\"\")               = 0\n     * unlimitedCompare(\"\",\"a\")              = 1\n     * unlimitedCompare(\"aaapppp\", \"\")       = 7\n     * unlimitedCompare(\"frog\", \"fog\")       = 1\n     * unlimitedCompare(\"fly\", \"ant\")        = 3\n     * unlimitedCompare(\"elephant\", \"hippo\") = 7\n     * unlimitedCompare(\"hippo\", \"elephant\") = 7\n     * unlimitedCompare(\"hippo\", \"zzzzzzzz\") = 8\n     * unlimitedCompare(\"hello\", \"hallo\")    = 1\n     * </pre>\n     *\n     * @param left  the first String, must not be null\n     * @param right the second String, must not be null\n     * @return result distance, or -1\n     * @throws IllegalArgumentException if either String input {@code null}\n     */\n    private static int unlimitedCompare(CharSequence left, CharSequence right) {\n        if (left == null || right == null) {\n            throw new IllegalArgumentException(\"Strings must not be null\");\n        }\n\n        /*\n           The difference between this impl. and the previous is that, rather\n           than creating and retaining a matrix of size s.length() + 1 by t.length() + 1,\n           we maintain two single-dimensional arrays of length s.length() + 1.  The first, d,\n           is the 'current working' distance array that maintains the newest distance cost\n           counts as we iterate through the characters of String s.  Each time we increment\n           the index of String t we are comparing, d is copied to p, the second int[].  Doing so\n           allows us to retain the previous cost counts as required by the algorithm (taking\n           the minimum of the cost count to the left, up one, and diagonally up and to the left\n           of the current cost count being calculated).  (Note that the arrays aren't really\n           copied anymore, just switched...this is clearly much better than cloning an array\n           or doing a System.arraycopy() each time  through the outer loop.)\n           Effectively, the difference between the two implementations is this one does not\n           cause an out of memory condition when calculating the LD over two very large strings.\n         */\n\n        int n = left.length(); // length of left\n        int m = right.length(); // length of right\n\n        if (n == 0) {\n            return m;\n        } else if (m == 0) {\n            return n;\n        }\n\n        if (n > m) {\n            // swap the input strings to consume less memory\n            final CharSequence tmp = left;\n            left = right;\n            right = tmp;\n            n = m;\n            m = right.length();\n        }\n\n        int[] p = new int[n + 1]; //'previous' cost array, horizontally\n        int[] d = new int[n + 1]; // cost array, horizontally\n        int[] tempD; //placeholder to assist in swapping p and d\n\n        // indexes into strings left and right\n        int i; // iterates through left\n        int j; // iterates through right\n\n        char rightJ; // jth character of right\n\n        int cost; // cost\n\n        for (i = 0; i <= n; i++) {\n            p[i] = i;\n        }\n\n        for (j = 1; j <= m; j++) {\n            rightJ = right.charAt(j - 1);\n            d[0] = j;\n\n            for (i = 1; i <= n; i++) {\n                cost = left.charAt(i - 1) == rightJ ? 0 : 1;\n                // minimum of cell to the left+1, to the top+1, diagonally left and up +cost\n                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);\n            }\n\n            // copy current distance counts to 'previous row' distance counts\n            tempD = p;\n            p = d;\n            d = tempD;\n        }\n\n        // our last action in the above loop was to switch d and p, so p now\n        // actually has the most recent cost counts\n        return p[n];\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/distance/levenshtein/LevenshteinHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.distance.levenshtein;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Levenshtein algorithm.\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class LevenshteinHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = LevenshteinHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public LevenshteinHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                             @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link LevenshteinDistance} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double levUpperThreshold = SPH.getLevenshteinUpper(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final LevenshteinDistance ld = new LevenshteinDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double distance;\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = ld.apply(phrase, vd);\n\n                if (distance <= levUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + phrase);\n                    }\n\n                    container.setUtterance(vd);\n                    container.setScore(distance);\n\n                    if (distance == Algorithm.LEV_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + phrase);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        toKeep.add(SerializationUtils.clone(container));\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c1.getScore(), c2.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.LEVENSHTEIN);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(LevenshteinHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link LevenshteinDistance} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double levUpperThreshold = SPH.getLevenshteinUpper(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final LevenshteinDistance ld = new LevenshteinDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = ld.apply(genericLower, vd);\n\n                if (distance <= levUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + genericLower);\n                    }\n\n                    container = new AlgorithmicContainer();\n                    container.setInput(vd);\n                    container.setGenericMatch(generic);\n                    container.setScore(distance);\n                    container.setAlgorithm(Algorithm.LEVENSHTEIN);\n                    container.setParentPosition(i);\n\n                    if (distance == Algorithm.LEV_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + genericLower);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n                        break outer;\n                    } else {\n                        container.setExactMatch(false);\n                        toKeep.add(container);\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c1.getScore(), c2.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(LevenshteinHelper.class.getSimpleName(), then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/doublemetaphone/DoubleMetaphoneHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.doublemetaphone;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.codec.language.DoubleMetaphone;\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerDistance;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Double Metaphone phonetic algorithm. To avoid false positives on partial phonetic\n * matches, we apply a further two filters - the first compares the String lengths to make sure they\n * fall within the {@link Algorithm#LENGTH_THRESHOLD} and the seconds runs the {@link JaroWinklerDistance}\n * algorithm to check it falls within the {@link Algorithm#JWD_LOWER_THRESHOLD}\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class DoubleMetaphoneHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = DoubleMetaphoneHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public DoubleMetaphoneHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                                 @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link DoubleMetaphone} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final DoubleMetaphone metaphone = new DoubleMetaphone();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double score;\n        boolean matches;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                matches = metaphone.isDoubleMetaphoneEqual(phrase, vd);\n\n                if (matches && Algorithm.checkLength(phrase, vd)) {\n                    score = jwd.apply(phrase, vd);\n\n                    if (score > jwdLowerThreshold) {\n\n                        container.setScore(score);\n                        container.setUtterance(vd);\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have a match\");\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.DOUBLE_METAPHONE);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases matched\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link DoubleMetaphone} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final DoubleMetaphone metaphone = new DoubleMetaphone();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double score;\n        boolean matches;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                matches = metaphone.isDoubleMetaphoneEqual(genericLower, vd);\n\n                if (matches && Algorithm.checkLength(genericLower, vd)) {\n                    score = jwd.apply(genericLower, vd);\n\n                    if (score > jwdLowerThreshold) {\n\n                        container = new AlgorithmicContainer();\n                        container.setInput(vd);\n                        container.setGenericMatch(generic);\n                        container.setScore(score);\n                        container.setAlgorithm(Algorithm.DOUBLE_METAPHONE);\n                        container.setParentPosition(i);\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n                        break outer;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have a match\");\n            }\n            container = toKeep.get(0);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/fuzzy/FuzzyHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.fuzzy;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerDistance;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply a fuzzy string match. To avoid false positives, we apply a further filter\n * checking the {@link JaroWinklerDistance} algorithm to check it falls within\n * the {@link Algorithm#JWD_LOWER_THRESHOLD}\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class FuzzyHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FuzzyHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public FuzzyHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                       @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link StringUtils#getFuzzyDistance(CharSequence, CharSequence, Locale)}\n     * within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n        final double fuzzyMultiplier = SPH.getFuzzyMultiplier(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double score;\n        double distance;\n\n        int size = genericData.size();\n\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = StringUtils.getFuzzyDistance(phrase, vd, loc);\n\n                if (distance > (vd.length() * fuzzyMultiplier)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Potential \" + phrase);\n                    }\n\n                    score = jwd.apply(phrase, vd);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Potential: double check JW \" + phrase + \" ~ \" + vd + \" \" + score);\n                    }\n\n                    if (score > jwdLowerThreshold) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Potential: double check JW: accepted\");\n                        }\n\n                        container.setScore(score);\n                        container.setUtterance(vd);\n                        toKeep.add(SerializationUtils.clone(container));\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.FUZZY);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(FuzzyHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link StringUtils#getFuzzyDistance(CharSequence, CharSequence, Locale)}\n     * within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n        final double fuzzyMultiplier = SPH.getFuzzyMultiplier(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n        double score;\n\n        int size = genericData.size();\n\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = StringUtils.getFuzzyDistance(genericLower, vd, loc);\n\n                if (distance > (vd.length() * fuzzyMultiplier)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Potential \" + genericLower);\n                    }\n\n                    score = jwd.apply(genericLower, vd);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Potential: double check JW \" + genericLower + \" ~ \" + vd + \" \" + score);\n                    }\n\n                    if (score > jwdLowerThreshold) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Potential: double check JW: accepted\");\n                        }\n\n                        container = new AlgorithmicContainer();\n                        container.setInput(vd);\n                        container.setGenericMatch(generic);\n                        container.setScore(score);\n                        container.setAlgorithm(Algorithm.FUZZY);\n                        container.setParentPosition(i);\n                        toKeep.add(container);\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(FuzzyHelper.class.getSimpleName(), then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/metaphone/MetaphoneHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.metaphone;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.codec.language.Metaphone;\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerDistance;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Metaphone phonetic algorithm. To avoid false positives on partial phonetic\n * matches, we apply a further two filters - the first compares the String lengths to make sure they\n * fall within the {@link Algorithm#LENGTH_THRESHOLD} and the seconds runs the {@link JaroWinklerDistance}\n * algorithm to check it falls within the {@link Algorithm#JWD_LOWER_THRESHOLD}\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class MetaphoneHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = MetaphoneHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public MetaphoneHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                           @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link Metaphone} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final Metaphone metaphone = new Metaphone();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double score;\n        boolean matches;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                matches = metaphone.isMetaphoneEqual(phrase, vd);\n\n                if (matches && Algorithm.checkLength(phrase, vd)) {\n                    score = jwd.apply(phrase, vd);\n\n                    if (score > jwdLowerThreshold) {\n\n                        container.setScore(score);\n                        container.setUtterance(vd);\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have a match\");\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setAlgorithm(Algorithm.METAPHONE);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases matched\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link Metaphone} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final Metaphone metaphone = new Metaphone();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double score;\n        boolean matches;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                matches = metaphone.isMetaphoneEqual(genericLower, vd);\n\n                if (matches && Algorithm.checkLength(genericLower, vd)) {\n                    score = jwd.apply(genericLower, vd);\n\n                    if (score > jwdLowerThreshold) {\n\n                        container = new AlgorithmicContainer();\n                        container.setInput(vd);\n                        container.setGenericMatch(generic);\n                        container.setScore(score);\n                        container.setAlgorithm(Algorithm.METAPHONE);\n                        container.setParentPosition(i);\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n\n                        break outer;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Matches: double check JW: rejected\");\n                        }\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have a match\");\n            }\n            container = toKeep.get(0);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/mongeelkan/MongeElkanHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.mongeelkan;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\nimport org.simmetrics.StringMetric;\nimport org.simmetrics.metrics.MongeElkan;\nimport org.simmetrics.metrics.SmithWatermanGotoh;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\nimport static org.simmetrics.builders.StringMetricBuilder.with;\nimport static org.simmetrics.tokenizers.Tokenizers.whitespace;\n\n/**\n * Class to apply the Monge Elkan algorithm.\n * <p/>\n * Created by benrandall76@gmail.com on 22/04/2016.\n */\npublic class MongeElkanHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = MongeElkanHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public MongeElkanHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                            @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link MongeElkan} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double meUpperThreshold = SPH.getMongeElkanUpper(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final StringMetric me = with(new MongeElkan(new SmithWatermanGotoh())).tokenize(\n                whitespace()).build();\n\n        String phrase;\n        CustomCommandContainer container;\n        double distance;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = me.compare(phrase, vd);\n\n                if (distance > meUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + phrase);\n                    }\n\n                    container.setUtterance(vd);\n                    container.setScore(distance);\n\n                    if (distance == Algorithm.ME_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + phrase);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        toKeep.add(SerializationUtils.clone(container));\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.MONGE_ELKAN);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(MongeElkanHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link MongeElkan} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double meUpperThreshold = SPH.getMongeElkanUpper(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final StringMetric me = with(new MongeElkan(new SmithWatermanGotoh())).tokenize(\n                whitespace()).build();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = me.compare(genericLower, vd);\n\n                if (distance > meUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + genericLower);\n                    }\n\n                    container = new AlgorithmicContainer();\n                    container.setInput(vd);\n                    container.setGenericMatch(generic);\n                    container.setScore(distance);\n                    container.setAlgorithm(Algorithm.MONGE_ELKAN);\n                    container.setParentPosition(i);\n\n                    if (distance == Algorithm.ME_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + genericLower);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n                        break outer;\n                    } else {\n                        container.setExactMatch(false);\n                        toKeep.add(container);\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(MongeElkanHelper.class.getSimpleName(), then);\n        }\n\n        return container;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/needlemanwunch/NeedlemanWunschHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.needlemanwunch;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\nimport org.simmetrics.StringMetric;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.needlemanwunch.simmetrics.NeedlemanWunch;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Needleman Wunsch algorithm. Although not commonly associated for use within\n * language Strings, the distance calculation stands up.\n * <p/>\n * Created by benrandall76@gmail.com on 22/04/2016.\n */\npublic class NeedlemanWunschHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NeedlemanWunschHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public NeedlemanWunschHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                                 @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link NeedlemanWunch} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double nwUpperThreshold = SPH.getNeedlemanWunschUpper(mContext);\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n        final StringMetric nw = new NeedlemanWunch();\n\n        String phrase;\n        CustomCommandContainer container;\n        double distance;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = nw.compare(phrase, vd);\n\n                if (distance > nwUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + phrase);\n                    }\n\n                    container.setUtterance(vd);\n                    container.setScore(distance);\n\n                    if (distance == Algorithm.NW_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + phrase);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(SerializationUtils.clone(container));\n                        break outer;\n                    } else {\n                        toKeep.add(SerializationUtils.clone(container));\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.NEEDLEMAN_WUNCH);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(NeedlemanWunschHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link NeedlemanWunch} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    public AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double nwUpperThreshold = SPH.getNeedlemanWunschUpper(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n        final StringMetric nw = new NeedlemanWunch();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            generic = (String) genericData.get(i);\n            genericLower = generic.toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                distance = nw.compare(genericLower, vd);\n\n                if (distance > nwUpperThreshold) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Keeping \" + genericLower);\n                    }\n\n                    container = new AlgorithmicContainer();\n                    container.setInput(vd);\n                    container.setGenericMatch(generic);\n                    container.setScore(distance);\n                    container.setAlgorithm(Algorithm.NEEDLEMAN_WUNCH);\n                    container.setParentPosition(i);\n\n                    if (distance == Algorithm.NW_MAX_THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Exact match \" + genericLower);\n                        }\n\n                        container.setExactMatch(true);\n                        toKeep.add(container);\n                        break outer;\n                    } else {\n                        container.setExactMatch(false);\n                        toKeep.add(container);\n                    }\n                }\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(NeedlemanWunschHelper.class.getSimpleName(), then);\n        }\n\n        return container;\n    }\n\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/needlemanwunch/simmetrics/NeedlemanWunch.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Copyright (C) 2014 - 2016 Simmetrics Authors\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.algorithms.needlemanwunch.simmetrics;\n\nimport org.simmetrics.StringMetric;\nimport org.simmetrics.metrics.SmithWaterman;\nimport org.simmetrics.metrics.SmithWatermanGotoh;\nimport org.simmetrics.metrics.functions.MatchMismatch;\nimport org.simmetrics.metrics.functions.Substitution;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\n/**\n * Applies the Needleman-Wunsch algorithm to calculate the similarity\n * between two strings. This implementation uses linear space.\n * <p/>\n * This class is immutable and thread-safe if its substitution function is.\n *\n * @see SmithWatermanGotoh\n * @see SmithWaterman\n * @see <a\n * href=\"https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm\">Wikipedia\n * - Needleman-Wunsch algorithm</a>\n * <p/>\n * Created by benrandall76@gmail.com on 22/04/2016.\n */\npublic final class NeedlemanWunch implements StringMetric {\n\n    private static final Substitution MATCH_0_MISMATCH_1 = new MatchMismatch(\n            0.0f, -1.0f);\n\n    private final Substitution substitution;\n\n    private final float gapValue;\n\n    /**\n     * Constructs a new Needleman-Wunch metric. Uses an gap of <code>-2.0</code>\n     * a <code>-1.0</code> substitution penalty for mismatches, <code>0</code>\n     * for matches.\n     */\n    public NeedlemanWunch() {\n        this(-2.0f, MATCH_0_MISMATCH_1);\n    }\n\n    /**\n     * Constructs a new Needleman-Wunch metric.\n     *\n     * @param gapValue     a non-positive penalty for gaps\n     * @param substitution a substitution function for mismatched characters\n     */\n    private NeedlemanWunch(float gapValue, Substitution substitution) {\n        checkArgument(gapValue <= 0.0f);\n        checkNotNull(substitution);\n        this.gapValue = gapValue;\n        this.substitution = substitution;\n    }\n\n    @Override\n    public float compare(String a, String b) {\n\n        if (a.isEmpty() && b.isEmpty()) {\n            return 1.0f;\n        }\n\n        float maxDistance = java.lang.Math.max(a.length(), b.length())\n                * java.lang.Math.max(substitution.max(), gapValue);\n        float minDistance = java.lang.Math.max(a.length(), b.length())\n                * java.lang.Math.min(substitution.min(), gapValue);\n\n        return (-needlemanWunch(a, b) - minDistance)\n                / (maxDistance - minDistance);\n\n    }\n\n    private float needlemanWunch(final String s, final String t) {\n\n        if (s == null || t == null || (s.equals(t))) {\n            return 0;\n        }\n\n        if (s.isEmpty()) {\n            return -gapValue * t.length();\n        }\n        if (t.isEmpty()) {\n            return -gapValue * s.length();\n        }\n\n        final int n = s.length();\n        final int m = t.length();\n\n        // We're only interested in the alignment penalty between s and t\n        // and not their actual alignment. This means we don't have to backtrack\n        // through the n-by-m matrix and can safe some space by reusing v0 for\n        // row i-1.\n        float[] v0 = new float[m + 1];\n        float[] v1 = new float[m + 1];\n\n        for (int j = 0; j <= m; j++) {\n            v0[j] = j;\n        }\n\n        for (int i = 1; i <= n; i++) {\n            v1[0] = i;\n\n            for (int j = 1; j <= m; j++) {\n                v1[j] = min(\n                        v0[j] - gapValue,\n                        v1[j - 1] - gapValue,\n                        v0[j - 1] - substitution.compare(s, i - 1, t, j - 1));\n            }\n\n            final float[] swap = v0;\n            v0 = v1;\n            v1 = swap;\n\n        }\n\n        // Because we swapped the results are in v0.\n        return v0[m];\n    }\n\n    @Override\n    public String toString() {\n        return \"NeedlemanWunch [costFunction=\" + substitution + \", gapCost=\"\n                + gapValue + \"]\";\n    }\n\n    private static float min(float a, float b, float c) {\n        return java.lang.Math.min(java.lang.Math.min(a, b), c);\n    }\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/regex/ContainsHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.regex;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 03/10/2016.\n */\n\npublic class ContainsHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ContainsHelper.class.getSimpleName();\n\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n    /**\n     * Constructor\n     *\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public ContainsHelper(@NonNull final ArrayList<?> genericData,\n                          @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using that are marked as {@link Algorithm#REGEX}\n     * specifically {@link ai.saiy.android.api.request.Regex#CONTAINS}\n     *\n     * @return a {@link CustomCommand} should the regular express be successful, otherwise null\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        CustomCommand customCommand = null;\n\n        String phrase;\n        CustomCommandContainer container;\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"contains: \" + vd + \" ~ \" + phrase);\n                }\n\n                if (vd.contains(phrase)) {\n\n                    final CustomCommandContainer ccc = SerializationUtils.clone(container);\n                    final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n                    customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n                    customCommand.setExactMatch(true);\n                    customCommand.setUtterance(vd);\n                    customCommand.setScore(1.0);\n                    customCommand.setAlgorithm(Algorithm.REGEX);\n\n                    break outer;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            return executeCustomCommand();\n        }\n\n        return null;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/regex/CustomHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.regex;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 03/10/2016.\n */\n\npublic class CustomHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CustomHelper.class.getSimpleName();\n\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n    /**\n     * Constructor\n     *\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public CustomHelper(@NonNull final ArrayList<?> genericData,\n                        @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using that are marked as {@link Algorithm#REGEX}\n     * specifically {@link ai.saiy.android.api.request.Regex#CUSTOM}\n     *\n     * @return a {@link CustomCommand} should the regular express be successful, otherwise null\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        CustomCommand customCommand = null;\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n\n        String phrase;\n        CustomCommandContainer container;\n        Pattern pattern;\n        Matcher matcher;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n            pattern = Pattern.compile(gson.fromJson(container.getSerialised(), CustomCommand.class)\n                    .getRegularExpression());\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n                matcher = pattern.matcher(vd);\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"custom: \" + vd + \" ~ \" + phrase);\n                }\n\n                if (matcher.matches()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"custom: matched \" + Pattern.quote(pattern.pattern()));\n                    }\n\n                    final CustomCommandContainer ccc = SerializationUtils.clone(container);\n                    customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n                    customCommand.setExactMatch(true);\n                    customCommand.setUtterance(vd);\n                    customCommand.setScore(1.0);\n                    customCommand.setAlgorithm(Algorithm.REGEX);\n\n                    break outer;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CustomHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            return executeCustomCommand();\n        }\n\n        return null;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/regex/EndsWithHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.regex;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 03/10/2016.\n */\n\npublic class EndsWithHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = EndsWithHelper.class.getSimpleName();\n\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n    /**\n     * Constructor\n     *\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public EndsWithHelper(@NonNull final ArrayList<?> genericData,\n                          @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using that are marked as {@link Algorithm#REGEX}\n     * specifically {@link ai.saiy.android.api.request.Regex#ENDS_WITH}\n     *\n     * @return a {@link CustomCommand} should the regular express be successful, otherwise null\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        CustomCommand customCommand = null;\n\n        String phrase;\n        CustomCommandContainer container;\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"ends with: \" + vd + \" ~ \" + phrase);\n                }\n\n                if (vd.endsWith(phrase)) {\n\n                    final CustomCommandContainer ccc = SerializationUtils.clone(container);\n                    final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n                    customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n                    customCommand.setExactMatch(true);\n                    customCommand.setUtterance(vd);\n                    customCommand.setScore(1.0);\n                    customCommand.setAlgorithm(Algorithm.REGEX);\n\n                    break outer;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(EndsWithHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            return executeCustomCommand();\n        }\n\n        return null;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/regex/StartsWithHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.regex;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 03/10/2016.\n */\n\npublic class StartsWithHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = StartsWithHelper.class.getSimpleName();\n\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n    /**\n     * Constructor\n     *\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public StartsWithHelper(@NonNull final ArrayList<?> genericData,\n                            @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using that are marked as {@link Algorithm#REGEX}\n     * specifically {@link ai.saiy.android.api.request.Regex#STARTS_WITH}\n     *\n     * @return a {@link CustomCommand} should the regular express be successful, otherwise null\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        CustomCommand customCommand = null;\n\n        String phrase;\n        CustomCommandContainer container;\n\n        int size = genericData.size();\n\n        outer:\n        for (int i = 0; i < size; i++) {\n            container = (CustomCommandContainer) genericData.get(i);\n            phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n            for (String vd : inputData) {\n                vd = vd.toLowerCase(loc).trim();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"starts with: \" + vd + \" ~ \" + phrase);\n                }\n\n                if (vd.startsWith(phrase)) {\n\n                    final CustomCommandContainer ccc = SerializationUtils.clone(container);\n                    final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n                    customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n                    customCommand.setExactMatch(true);\n                    customCommand.setUtterance(vd);\n                    customCommand.setScore(1.0);\n                    customCommand.setAlgorithm(Algorithm.REGEX);\n\n                    break outer;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(StartsWithHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            return executeCustomCommand();\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/algorithms/soundex/SoundexHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.algorithms.soundex;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.apache.commons.codec.EncoderException;\nimport org.apache.commons.codec.language.Soundex;\nimport org.apache.commons.lang3.SerializationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerDistance;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to apply the Soundex phonetic algorithm. To avoid false positives on partial phonetic\n * matches, we apply a further two filters - the first compares the String lengths to make sure they\n * fall within the {@link Algorithm#LENGTH_THRESHOLD} and the seconds runs the {@link JaroWinklerDistance}\n * algorithm to check it falls within the {@link Algorithm#JWD_LOWER_THRESHOLD}\n * <p/>\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class SoundexHelper implements Callable<Object> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SoundexHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final Locale loc;\n    private final ArrayList<?> genericData;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param genericData an array containing generic data\n     * @param inputData   an array of Strings containing the input comparison data\n     * @param loc         the {@link Locale} extracted from the {@link SupportedLanguage}\n     */\n    public SoundexHelper(@NonNull final Context mContext, @NonNull final ArrayList<?> genericData,\n                         @NonNull final ArrayList<String> inputData, @NonNull final Locale loc) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.loc = loc;\n    }\n\n    /**\n     * Method to iterate through the voice data and attempt to match the user's custom commands\n     * using the {@link Soundex} within ranges applied by the associated thresholds constants.\n     *\n     * @return the highest scoring {@link CustomCommand} or null if thresholds aren't satisfied\n     */\n    public CustomCommand executeCustomCommand() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n        final double soundexUpperThreshold = SPH.getSoundexUpper(mContext);\n\n        CustomCommand customCommand = null;\n        final ArrayList<CustomCommandContainer> toKeep = new ArrayList<>();\n\n        final Soundex soundex = new Soundex();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String phrase;\n        CustomCommandContainer container;\n        double score;\n        double distance;\n\n        int size = genericData.size();\n\n        try {\n\n            outer:\n            for (int i = 0; i < size; i++) {\n                container = (CustomCommandContainer) genericData.get(i);\n                phrase = container.getKeyphrase().toLowerCase(loc).trim();\n\n                for (String vd : inputData) {\n                    vd = vd.toLowerCase(loc).trim();\n\n                    distance = soundex.difference(phrase, vd);\n\n                    if (distance > soundexUpperThreshold && Algorithm.checkLength(phrase, vd)) {\n\n                        score = jwd.apply(phrase, vd);\n\n                        if (score > jwdLowerThreshold) {\n\n                            container.setUtterance(vd);\n                            container.setScore(score);\n\n                            if (distance == Algorithm.SOUNDEX_MAX_THRESHOLD) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"Exact match \" + phrase);\n                                }\n                                container.setExactMatch(true);\n                                toKeep.add(SerializationUtils.clone(container));\n                                break outer;\n                            } else {\n                                toKeep.add(SerializationUtils.clone(container));\n                            }\n\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Possible match: double check JW: rejected\");\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (final EncoderException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"EncoderException\");\n                e.printStackTrace();\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" phrase matches\");\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(toKeep, new Comparator<CustomCommandContainer>() {\n                @Override\n                public int compare(final CustomCommandContainer c1, final CustomCommandContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommandContainer c : toKeep) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n                MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getKeyphrase());\n            }\n\n            final CustomCommandContainer ccc = toKeep.get(0);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            customCommand = gson.fromJson(ccc.getSerialised(), CustomCommand.class);\n            customCommand.setExactMatch(ccc.isExactMatch());\n            customCommand.setUtterance(ccc.getUtterance());\n            customCommand.setScore(ccc.getScore());\n            customCommand.setAlgorithm(Algorithm.SOUNDEX);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom phrases above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return customCommand;\n    }\n\n    /**\n     * Method to iterate through the given input data and attempt to match the given String data\n     * using the {@link Soundex} within ranges applied by the associated thresholds constants.\n     *\n     * @return an {@link AlgorithmicContainer} or null if thresholds aren't satisfied\n     */\n    private AlgorithmicContainer executeGeneric() {\n\n        long then = System.nanoTime();\n\n        final double jwdLowerThreshold = SPH.getJaroWinklerLower(mContext);\n        final double soundexUpperThreshold = SPH.getSoundexUpper(mContext);\n\n        final ArrayList<AlgorithmicContainer> toKeep = new ArrayList<>();\n\n        final Soundex soundex = new Soundex();\n        final JaroWinklerDistance jwd = new JaroWinklerDistance();\n\n        String generic;\n        String genericLower;\n        AlgorithmicContainer container = null;\n        double distance;\n        double score;\n\n        int size = genericData.size();\n\n        try {\n\n            outer:\n            for (int i = 0; i < size; i++) {\n                generic = (String) genericData.get(i);\n                genericLower = generic.toLowerCase(loc).trim();\n\n                for (String vd : inputData) {\n                    vd = vd.toLowerCase(loc).trim();\n\n                    distance = soundex.difference(genericLower, vd);\n\n                    if (distance > soundexUpperThreshold && Algorithm.checkLength(genericLower, vd)) {\n\n                        score = jwd.apply(genericLower, vd);\n\n                        if (score > jwdLowerThreshold) {\n\n                            container = new AlgorithmicContainer();\n                            container.setInput(vd);\n                            container.setGenericMatch(generic);\n                            container.setScore(score);\n                            container.setAlgorithm(Algorithm.SOUNDEX);\n                            container.setParentPosition(i);\n\n                            if (distance == Algorithm.SOUNDEX_MAX_THRESHOLD) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"Exact match \" + genericLower);\n                                }\n\n                                container.setExactMatch(true);\n                                toKeep.add(container);\n                                break outer;\n                            } else {\n                                container.setExactMatch(false);\n                                toKeep.add(container);\n                            }\n\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Possible match: double check JW: rejected\");\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (final EncoderException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"EncoderException\");\n                e.printStackTrace();\n            }\n        }\n\n        if (UtilsList.notNaked(toKeep)) {\n            if (DEBUG) {\n                debugBefore(toKeep);\n            }\n\n            Collections.sort(toKeep, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer c1, final AlgorithmicContainer c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                debugAfter(toKeep);\n            }\n\n            container = toKeep.get(0);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no matches above threshold\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return container;\n    }\n\n    private void debugBefore(@NonNull final ArrayList<AlgorithmicContainer> toKeep) {\n        MyLog.i(CLS_NAME, \"Have \" + toKeep.size() + \" input matches\");\n        for (final AlgorithmicContainer c : toKeep) {\n            MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n        }\n    }\n\n    private void debugAfter(@NonNull final ArrayList<AlgorithmicContainer> toKeep) {\n        for (final AlgorithmicContainer c : toKeep) {\n            MyLog.i(CLS_NAME, \"after order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n        }\n        MyLog.i(CLS_NAME, \"would select: \" + toKeep.get(0).getGenericMatch());\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Object call() throws Exception {\n\n        if (UtilsList.notNaked(genericData)) {\n            if (genericData.get(0) instanceof String) {\n                return executeGeneric();\n            } else {\n                return executeCustomCommand();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/SaiyDefaults.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to map the {@link Defaults} exposed in the library to default values used by Saiy, which\n * may differ and offer further functionality.\n * <p>\n * Created by benrandall76@gmail.com on 12/08/2016.\n */\n\npublic class SaiyDefaults {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SaiyDefaults.class.getSimpleName();\n\n    public enum LanguageModel {\n\n        LOCAL(Defaults.LanguageModel.LOCAL),\n        NUANCE(Defaults.LanguageModel.NUANCE),\n        MICROSOFT(Defaults.LanguageModel.MICROSOFT),\n        API_AI(Defaults.LanguageModel.API_AI),\n        WIT(Defaults.LanguageModel.WIT),\n        IBM(null),\n        REMOTE(Defaults.LanguageModel.REMOTE);\n\n        private final Defaults.LanguageModel languageModel;\n\n        LanguageModel(final Defaults.LanguageModel languageModel) {\n            this.languageModel = languageModel;\n        }\n\n        public Defaults.LanguageModel getRemoteLanguageModel() {\n            return this.languageModel;\n        }\n\n        /**\n         * Convert a remote LanguageModel object to the local variant\n         *\n         * @param languageModel the remote {@link ai.saiy.android.api.Defaults.LanguageModel}\n         * @return the local variant of {@link SaiyDefaults.LanguageModel}\n         */\n        public static LanguageModel remoteToLocal(@NonNull final Defaults.LanguageModel languageModel) {\n\n            switch (languageModel) {\n\n                case LOCAL:\n                    return LanguageModel.LOCAL;\n                case NUANCE:\n                    return LanguageModel.NUANCE;\n                case MICROSOFT:\n                    return LanguageModel.MICROSOFT;\n                case API_AI:\n                    return LanguageModel.API_AI;\n                case WIT:\n                    return LanguageModel.WIT;\n                case REMOTE:\n                    return LanguageModel.REMOTE;\n                default:\n                    return LanguageModel.LOCAL;\n            }\n        }\n    }\n\n    public enum TTS {\n\n        LOCAL(Defaults.TTS.LOCAL),\n        NETWORK_NUANCE(Defaults.TTS.NETWORK_NUANCE);\n\n        private final Defaults.TTS tts;\n\n        TTS(final Defaults.TTS tts) {\n            this.tts = tts;\n        }\n\n        public Defaults.TTS getRemoteTTS() {\n            return this.tts;\n        }\n\n        /**\n         * Convert a remote TTS Default object to the local variant\n         *\n         * @param remoteTTS the remote {@link ai.saiy.android.api.Defaults.TTS}\n         * @return the local variant of {@link SaiyDefaults.TTS}\n         */\n        public static TTS remoteToLocal(@NonNull final Defaults.TTS remoteTTS) {\n\n            switch (remoteTTS) {\n\n                case LOCAL:\n                    return TTS.LOCAL;\n                case NETWORK_NUANCE:\n                    return TTS.NETWORK_NUANCE;\n                default:\n                    return TTS.LOCAL;\n            }\n        }\n    }\n\n    public enum VR {\n\n        NATIVE(Defaults.VR.NATIVE),\n        GOOGLE_CLOUD(Defaults.VR.GOOGLE_CLOUD),\n        GOOGLE_CHROMIUM(Defaults.VR.GOOGLE_CHROMIUM),\n        NUANCE(Defaults.VR.NUANCE),\n        MICROSOFT(Defaults.VR.MICROSOFT),\n        WIT(Defaults.VR.WIT),\n        IBM(Defaults.VR.IBM),\n        REMOTE(Defaults.VR.REMOTE),\n        MIC(null);\n\n        private final Defaults.VR vr;\n\n        VR(final Defaults.VR vr) {\n            this.vr = vr;\n        }\n\n        public Defaults.VR getRemoteVR() {\n            return vr;\n        }\n\n        /**\n         * Convert a remote VR Default object to the local variant\n         *\n         * @param remoteVR the remote {@link ai.saiy.android.api.Defaults.VR}\n         * @return the local variant of {@link SaiyDefaults.VR}\n         */\n        public static VR remoteToLocal(@NonNull final Defaults.VR remoteVR) {\n\n            switch (remoteVR) {\n\n                case NATIVE:\n                    return VR.NATIVE;\n                case GOOGLE_CLOUD:\n                    return VR.GOOGLE_CLOUD;\n                case GOOGLE_CHROMIUM:\n                    return VR.GOOGLE_CHROMIUM;\n                case NUANCE:\n                    return VR.NUANCE;\n                case MICROSOFT:\n                    return VR.MICROSOFT;\n                case WIT:\n                    return VR.WIT;\n                case IBM:\n                    return VR.IBM;\n                case REMOTE:\n                    return VR.REMOTE;\n                default:\n                    return VR.NATIVE;\n            }\n        }\n    }\n\n    public static TTS getProviderTTS(@Nullable final String name) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getProviderTTS: \" + name);\n        }\n\n        if (name != null) {\n\n            try {\n                return Enum.valueOf(TTS.class, name.trim());\n            } catch (final IllegalArgumentException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getProviderTTS: IllegalArgumentException\");\n                    e.printStackTrace();\n                }\n                return TTS.LOCAL;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProviderTTS: name null\");\n            }\n            return TTS.LOCAL;\n        }\n    }\n\n    public static VR getProviderVR(final String name) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getProviderVR: \" + name);\n        }\n\n        if (name != null) {\n\n            try {\n                return Enum.valueOf(VR.class, name.trim());\n            } catch (final IllegalArgumentException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getProviderVR: IllegalArgumentException\");\n                    e.printStackTrace();\n                }\n                return VR.NATIVE;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProviderVR: name null\");\n            }\n            return VR.NATIVE;\n        }\n    }\n\n    public static LanguageModel getLanguageModel(final String name) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getLanguageModel: \" + name);\n        }\n\n        if (name != null) {\n\n            try {\n                return Enum.valueOf(LanguageModel.class, name.trim());\n            } catch (final IllegalArgumentException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getLanguageModel: IllegalArgumentException\");\n                    e.printStackTrace();\n                }\n                return LanguageModel.LOCAL;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getLanguageModel: name null\");\n            }\n            return LanguageModel.LOCAL;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/helper/BlackList.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api.helper;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.ListIterator;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Helper class to store remote requests that have been denied due to a throttle limit.\n * <p>\n * Created by benrandall76@gmail.com on 06/03/2016.\n */\npublic class BlackList {\n\n    private transient static final boolean DEBUG = MyLog.DEBUG;\n    private transient static final String CLS_NAME = BlackList.class.getSimpleName();\n\n    private static final int MAX_ACQUIRE_REJECT = 10;\n    private static final int MAX_PAST_TIME = 30000;\n\n    private final String packageName;\n    private final int callingUid;\n    private final long requestTime;\n\n    /**\n     * Constructor\n     * <p>\n     * Creates a BlackList object to store for future reference\n     *\n     * @param packageName of the calling application\n     * @param callingUid  of the calling application\n     * @param requestTime at which the remote request was made\n     */\n    public BlackList(@NonNull final String packageName, final int callingUid, final long requestTime) {\n        this.packageName = packageName;\n        this.callingUid = callingUid;\n        this.requestTime = requestTime;\n    }\n\n    /**\n     * Get the Uid of the calling package\n     *\n     * @return the calling Uid\n     */\n    private int getCallingUid() {\n        return callingUid;\n    }\n\n    /**\n     * Get the package name of the remote caller\n     *\n     * @return the remote package name\n     */\n    public String getPackageName() {\n        return packageName;\n    }\n\n    /**\n     * Get the time the remote request was made\n     *\n     * @return the EPOCH time the request was made\n     */\n    private long getRequestTime() {\n        return requestTime;\n    }\n\n    /**\n     * Check if the remote package that is making the denied requests has done this too many times,\n     * calculated using {@link #MAX_ACQUIRE_REJECT} & {@link #MAX_PAST_TIME}\n     * <p>\n     * It would also be possible to extend this method to analyse the Uid, but that is perhaps a\n     * little too cautious?\n     *\n     * @param blackListArray the ArrayList of {@link BlackList}\n     * @return true if the package should be permanently blacklisted\n     */\n    public static synchronized boolean shouldBlackList(final ArrayList<BlackList> blackListArray) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shouldBlackList: \" + blackListArray.size());\n        }\n\n        final int blackListSize = blackListArray.size();\n\n        if (blackListSize > 5) {\n\n            final BlackList currentBlackList = blackListArray.get(blackListSize - 1);\n            final String currentPackageName = currentBlackList.getPackageName();\n            final int currentUid = currentBlackList.getCallingUid();\n            final long currentRequestTime = currentBlackList.getRequestTime();\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"shouldBlackList: currentPackageName: \" + currentPackageName);\n                MyLog.v(CLS_NAME, \"shouldBlackList: currentUid: \" + currentUid);\n                MyLog.v(CLS_NAME, \"shouldBlackList: currentRequestTime: \" + currentRequestTime);\n            }\n\n            BlackList blackList;\n            String packageName;\n            int matches = 0;\n\n            for (int i = 0; i < (blackListSize - 1); i++) {\n\n                blackList = blackListArray.get(i);\n                packageName = blackList.getPackageName();\n\n                if (packageName.matches(currentPackageName)) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"shouldBlackList: difference: \"\n                                + (currentRequestTime - blackList.getRequestTime()));\n                    }\n\n                    if ((currentRequestTime - blackList.getRequestTime()) < MAX_PAST_TIME) {\n                        matches++;\n                    }\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"shouldBlackList: matches: \" + matches);\n            }\n\n            final ListIterator itr = blackListArray.listIterator();\n\n            if (matches > MAX_ACQUIRE_REJECT) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"Blacklisted package: \" + currentPackageName);\n                }\n\n                while (itr.hasNext()) {\n                    blackList = (BlackList) itr.next();\n                    packageName = blackList.getPackageName();\n\n                    if (packageName.matches(currentPackageName)) {\n                        itr.remove();\n                    } else if ((currentRequestTime - blackList.getRequestTime()) > MAX_PAST_TIME) {\n                        itr.remove();\n                    }\n                }\n\n                return true;\n            } else {\n\n                while (itr.hasNext()) {\n                    blackList = (BlackList) itr.next();\n\n                    if ((currentRequestTime - blackList.getRequestTime()) > MAX_PAST_TIME) {\n                        itr.remove();\n                    }\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"shouldBlackList: blackListArray trimmed size: \" + blackListArray.size());\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/helper/BlackListHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api.helper;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.lang.reflect.Type;\nimport java.util.ArrayList;\n\nimport ai.saiy.android.custom.CustomCommandHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 14/07/2016.\n */\n\npublic class BlackListHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BlackListHelper.class.getSimpleName();\n\n    private final Type type = new TypeToken<ArrayList<String>>() {\n    }.getType();\n\n    /**\n     * Fetch an ArrayList of currently blacklisted application package names\n     *\n     * @param ctx the application context\n     * @return an ArrayList containing blacklisted application package names or an empty array\n     */\n    public ArrayList<String> fetch(@NonNull final Context ctx) {\n\n        final ArrayList<String> blackListArray;\n\n        if (haveBlacklist(ctx)) {\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n\n            try {\n                blackListArray = gson.fromJson(SPH.getBlacklistArray(ctx), type);\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"blackListArray: \" + gson.toJson(blackListArray));\n                }\n                return blackListArray;\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"blackListArray: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"blackListArray: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"blackListArray: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"blackListArray: empty\");\n            }\n        }\n\n        return new ArrayList<>();\n    }\n\n    /**\n     * Save the array list of blacklisted application package names into the the user's shared preferences,\n     * once it has been serialised.\n     * <p>\n     * Additionally, remove any current custom commands the application may have registered.\n     *\n     * @param ctx            the application context\n     * @param blackListArray the array list of blacklisted application package names\n     */\n    public void save(@NonNull final Context ctx, @Nullable final ArrayList<String> blackListArray) {\n\n        if (UtilsList.notNaked(blackListArray)) {\n\n            CustomCommandHelper.deleteCommandsForPackage(ctx, blackListArray);\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final String gsonString = gson.toJson(blackListArray, type);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"save: gsonString: \" + gsonString);\n            }\n\n            SPH.setBlacklistArray(ctx, gsonString);\n        } else {\n            SPH.setBlacklistArray(ctx, null);\n        }\n    }\n\n    /**\n     * Check if the calling package is currently blacklisted\n     *\n     * @param ctx         the application context\n     * @param packageName the calling package name\n     * @return true if the application is blacklisted, false otherwise\n     */\n    public boolean isBlacklisted(@NonNull final Context ctx, @NonNull final String packageName) {\n\n        final ArrayList<String> blacklistArray = fetch(ctx);\n\n        for (final String name : blacklistArray) {\n            if (name.matches(packageName)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if we currently have any applications blacklisted\n     *\n     * @param ctx the application context\n     * @return true if there are blacklisted applications, false otherwise\n     */\n    private boolean haveBlacklist(@NonNull final Context ctx) {\n        return SPH.getBlacklistArray(ctx) != null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/helper/Callback.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api.helper;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.api.RequestParcel;\n\n/**\n * Helper class to store remote request information.\n * <p>\n * Created by benrandall76@gmail.com on 06/03/2016.\n */\npublic class Callback {\n\n    private final RequestParcel parcel;\n    private final String packageName;\n    private final int callingUid;\n    private final long requestTime;\n\n    /**\n     * Constructor\n     *\n     * @param parcel      of request information sent via IPC\n     * @param packageName of the remote application\n     * @param callingUid  of the remote application\n     * @param requestTime of the remote request\n     */\n    public Callback(final RequestParcel parcel, @NonNull final String packageName,\n                    final int callingUid, final long requestTime) {\n        this.parcel = parcel;\n        this.packageName = packageName;\n        this.callingUid = callingUid;\n        this.requestTime = requestTime;\n    }\n\n    /**\n     * Get the Uid of the remote application\n     *\n     * @return the Uid\n     */\n    public int getCallingUid() {\n        return callingUid;\n    }\n\n    /**\n     * Get the package name of the remote application\n     *\n     * @return the package name\n     */\n    public String getPackageName() {\n        return packageName;\n    }\n\n    /**\n     * Get the time of the remote request\n     *\n     * @return the EPOCH time\n     */\n    public long getRequestTime() {\n        return requestTime;\n    }\n\n    /**\n     * Get the request parcel sent via IPC\n     *\n     * @return the {@link RequestParcel}\n     */\n    public RequestParcel getParcel() {\n        return parcel;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/helper/CallbackType.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api.helper;\n\nimport android.content.res.Resources;\n\n/**\n * Class to hold the constant values of callback types.\n * <p>\n * Created by benrandall76@gmail.com on 03/03/2016.\n */\npublic final class CallbackType {\n\n    /**\n     * Prevent instantiation\n     */\n    public CallbackType() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final int CB_UNKNOWN = 0;\n    public static final int CB_INTERRUPTED = 1;\n    public static final int CB_ERROR_NO_MATCH = 2;\n    public static final int CB_ERROR_NETWORK = 3;\n    public static final int CB_ERROR_BUSY = 4;\n    public static final int CB_ERROR_DENIED = 5;\n    public static final int CB_ERROR_SAIY = 6;\n    public static final int CB_ERROR_DEVELOPER = 7;\n\n    public static final int CB_UTTERANCE_COMPLETED = 99;\n    public static final int CB_RESULTS_RECOGNITION = 98;\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/api/helper/Validation.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.api.helper;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.net.Uri;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.Defaults;\nimport ai.saiy.android.api.RequestParcel;\nimport ai.saiy.android.configuration.NuanceConfiguration;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\nimport static ai.saiy.android.api.Defaults.ACTION.SPEAK_LISTEN;\nimport static ai.saiy.android.api.Defaults.ACTION.SPEAK_ONLY;\nimport static ai.saiy.android.api.Defaults.LanguageModel.API_AI;\nimport static ai.saiy.android.api.Defaults.LanguageModel.LOCAL;\nimport static ai.saiy.android.api.Defaults.VR.GOOGLE_CHROMIUM;\nimport static ai.saiy.android.api.Defaults.VR.GOOGLE_CLOUD;\nimport static ai.saiy.android.api.Defaults.VR.IBM;\nimport static ai.saiy.android.api.Defaults.VR.MICROSOFT;\nimport static ai.saiy.android.api.Defaults.VR.NATIVE;\nimport static ai.saiy.android.api.Defaults.VR.NUANCE;\nimport static ai.saiy.android.api.Defaults.VR.REMOTE;\nimport static ai.saiy.android.api.Defaults.VR.WIT;\n\n/**\n * Helper class to OTT check the remote request parameters and make sure they contain nothing\n * erroneous, null or anything in between, despite the library already attempting to do this....\n * Once checked, we can allow {@link SelfAware} to not have to apply such\n * checks and cause clutter.\n * <p/>\n * Whilst this class can confirm the above, it cannot however be sure that the supplied parameters\n * are correctly applied and will not be declined by the API they are to be used for. Such situations\n * will be dealt with elsewhere\n * <p/>\n * Created by benrandall76@gmail.com on 25/02/2016.\n */\npublic final class Validation {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Validation.class.getSimpleName();\n\n    private static final String _YOUR_ = \"_your_\";\n    public static final String ID_UNKNOWN = \"id_unknown\";\n\n    /**\n     * Prevent instantiation\n     */\n    public Validation() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * Validate the {@link RequestParcel} parameters received by the remote request. At minimum,\n     * they must contain a valid speech string and a request id.\n     * <p/>\n     * The request id may have been auto-generated by the library, rather than the caller applying it.\n     * <p/>\n     * If the caller intended no speech to be uttered, they needed to\n     * set {@link ai.saiy.android.api.request.SaiyRequestParams#SILENCE}\n     * <p/>\n     * If tests fail, there will be output in the log, that a developer can spot and we decline\n     * the request silently to the user.\n     *\n     * @param parcel the {@link RequestParcel} received from the remote request\n     * @return true if the values are configured correctly enough to make an API call with them.\n     */\n    public static boolean validateParams(@NonNull final Context ctx, @NonNull final RequestParcel parcel) {\n\n        final String words = parcel.getUtterance();\n        final String utteranceId = parcel.getRequestId();\n\n        if (UtilsString.notNaked(words)) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"validateParams: words: \" + words);\n            }\n\n            if (UtilsString.notNaked(utteranceId)) {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"validateParams: utteranceId: \" + utteranceId);\n                }\n                return true;\n            } else {\n                MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_requestid_invalid));\n            }\n        } else {\n            MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_utterance_invalid));\n        }\n\n        return false;\n    }\n\n    /**\n     * Validate the {@link RequestParcel} received by the remote request.\n     * <p/>\n     * If tests fail, there will be output in the log, that a developer can spot and we decline\n     * the request silently to the user.\n     *\n     * @param parcel the {@link RequestParcel} received from the remote request\n     * @return true if the values are configured correctly enough to make an API call with them.\n     */\n    public static boolean validateParcel(@NonNull final Context ctx, final RequestParcel parcel) {\n        return parcel != null && checkAction(ctx, parcel);\n    }\n\n    /**\n     * Check the {@link Defaults.ACTION} is correctly defined.\n     *\n     * @param ctx    the application context\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkAction(@NonNull final Context ctx, final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkAction\");\n        }\n\n        final Defaults.ACTION action = parcel.getAction();\n\n        switch (action) {\n\n            case SPEAK_ONLY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAction: \" + SPEAK_ONLY.name());\n                }\n                break;\n            case SPEAK_LISTEN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAction: \" + SPEAK_LISTEN.name());\n                }\n                break;\n            default:\n                MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_action_invalid));\n                return false;\n        }\n\n        return checkProviderTTS(ctx, parcel);\n    }\n\n    /**\n     * Check the {@link Defaults.TTS} are correctly defined.\n     *\n     * @param ctx    the application context\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkProviderTTS(@NonNull final Context ctx, final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkProviderTTS\");\n        }\n\n        final Defaults.TTS providerTTS = parcel.getProviderTTS();\n\n        switch (providerTTS) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkProviderTTS: Defaults.TTS.LOCAL\");\n                }\n                break;\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkProviderTTS: Defaults.TTS.NETWORK_NUANCE\");\n                }\n                break;\n            default:\n                MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_tts_invalid));\n                return false;\n        }\n\n        return checkVRProvider(ctx, parcel);\n    }\n\n    /**\n     * Check the {@link Defaults.VR} are correctly defined.\n     *\n     * @param ctx    the application context\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkVRProvider(@NonNull final Context ctx, final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkVRProvider\");\n        }\n\n        final Defaults.VR providerVR = parcel.getProviderVR();\n\n        switch (providerVR) {\n\n            case NATIVE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + NATIVE.name());\n                }\n                break;\n            case GOOGLE_CLOUD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + GOOGLE_CLOUD.name());\n                }\n                break;\n            case GOOGLE_CHROMIUM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + GOOGLE_CHROMIUM.name());\n                }\n                break;\n            case NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + NUANCE.name());\n                }\n                break;\n            case MICROSOFT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + MICROSOFT.name());\n                }\n                break;\n            case WIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + WIT.name());\n                }\n                break;\n            case IBM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + IBM.name());\n                }\n                break;\n            case REMOTE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkVRProvider: \" + REMOTE.name());\n                }\n                break;\n            default:\n                switch (parcel.getLanguageModel()) {\n\n                    case LOCAL:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkVRProvider: \" + LOCAL.name());\n                        }\n                        break;\n                    case NUANCE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkVRProvider: \" + NUANCE.name());\n                        }\n                        break;\n                    case MICROSOFT:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkVRProvider: \" + MICROSOFT.name());\n                        }\n                        break;\n                    case API_AI:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkVRProvider: \" + API_AI.name());\n                        }\n                        break;\n                    case REMOTE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkVRProvider: \" + REMOTE.name());\n                        }\n                        break;\n                    default:\n                        MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_vr_invalid));\n                        return false;\n                }\n        }\n\n        return checkAPIKey(ctx, parcel);\n    }\n\n    /**\n     * Check the API keys that will need to be used are correctly set.\n     *\n     * @param ctx    the application context\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkAPIKey(@NonNull final Context ctx, final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkAPIKey\");\n        }\n\n        final Defaults.TTS providerTTS = parcel.getProviderTTS();\n\n        switch (providerTTS) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: LOCAL: skipping\");\n                }\n                break;\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: Defaults.TTS.NETWORK_NUANCE: skipping\");\n                }\n\n                if (checkNuanceConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: Defaults.TTS.NETWORK_NUANCE: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_nuance_config));\n                    return false;\n                }\n        }\n\n        final Defaults.VR providerVR = parcel.getProviderVR();\n\n        switch (providerVR) {\n\n            case NATIVE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + NATIVE.name());\n                }\n                break;\n            case GOOGLE_CLOUD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + GOOGLE_CLOUD.name());\n                }\n\n                if (checkGoogleCloudConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_api_google_cloud));\n                    return false;\n                }\n            case GOOGLE_CHROMIUM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + GOOGLE_CHROMIUM.name());\n                }\n\n                if (checkGoogleChromiumConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_api_google_chromium));\n                    return false;\n                }\n            case NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + NUANCE.name());\n                }\n\n                if (checkNuanceConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_nuance_config));\n                    return false;\n                }\n            case MICROSOFT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + MICROSOFT.name());\n                }\n\n                if (checkMicrosoftConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_microsoft_config));\n                    return false;\n                }\n            case WIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + WIT.name());\n                }\n\n                if (checkWitConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_api_wit));\n                    return false;\n                }\n            case IBM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + IBM.name());\n                }\n\n                if (checkIBMConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_api_ibm));\n                    return false;\n                }\n            case REMOTE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + REMOTE.name());\n                }\n\n                if (checkRemoteConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_remote_config));\n                    return false;\n                }\n            default:\n                MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_vr_invalid));\n                return false;\n        }\n\n        switch (parcel.getLanguageModel()) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + LOCAL.name());\n                }\n                break;\n            case NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + NUANCE.name());\n                }\n\n                if (checkNuanceConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present \" + NUANCE.name());\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_nuance_config));\n                    return false;\n                }\n\n            case MICROSOFT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + MICROSOFT.name());\n                }\n\n                if (checkMicrosoftConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present \" + MICROSOFT.name());\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_microsoft_config));\n                    return false;\n                }\n            case API_AI:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + API_AI.name());\n                }\n\n                if (checkAPIAIConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present \" + API_AI.name());\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_api_ai_config));\n                    return false;\n                }\n            case WIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + Defaults.LanguageModel.WIT.name());\n                }\n\n                if (checkWitConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present\");\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_api_wit));\n                    return false;\n                }\n            case REMOTE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAPIKey: \" + REMOTE.name());\n                }\n\n                if (checkRemoteConfig(parcel)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkAPIKey: API key present \" + REMOTE.name());\n                    }\n                    break;\n                } else {\n                    MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(ai.saiy.android.R.string.error_remote_config));\n                    return false;\n                }\n            default:\n                MyLog.e(\"Remote Saiy Request\", ctx.getApplicationContext().getString(R.string.error_vr_invalid));\n                return false;\n\n        }\n\n        return true;\n    }\n\n    /**\n     * Check the remote credentials are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkRemoteConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkRemoteConfig\");\n        }\n\n        final String REMOTE_ACCESS_TOKEN = parcel.getREMOTE_ACCESS_TOKEN();\n        final Uri REMOTE_SERVER_URI = parcel.getREMOTE_SERVER_URI();\n\n        return UtilsString.notNaked(REMOTE_ACCESS_TOKEN)\n                && !REMOTE_ACCESS_TOKEN.startsWith(_YOUR_)\n                && REMOTE_SERVER_URI != null\n                && UtilsString.notNaked(REMOTE_SERVER_URI.toString())\n                && !REMOTE_SERVER_URI.toString().startsWith(_YOUR_);\n    }\n\n    /**\n     * Check the API AI credentials are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkAPIAIConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkAPIAIConfig\");\n        }\n\n        final String API_AI_CLIENT_ACCESS_TOKEN = parcel.getAPI_AI_CLIENT_ACCESS_TOKEN();\n\n        return UtilsString.notNaked(API_AI_CLIENT_ACCESS_TOKEN)\n                && !API_AI_CLIENT_ACCESS_TOKEN.startsWith(_YOUR_);\n    }\n\n    /**\n     * Check the IBM credentials are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkIBMConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkIBMConfig\");\n        }\n\n        final String IBM_SERVICE_USER_NAME = parcel.getIBM_SERVICE_USER_NAME();\n        final String IBM_SERVICE_PASSWORD = parcel.getIBM_SERVICE_PASSWORD();\n\n        return UtilsString.notNaked(IBM_SERVICE_USER_NAME)\n                && !IBM_SERVICE_USER_NAME.startsWith(_YOUR_)\n                && UtilsString.notNaked(IBM_SERVICE_PASSWORD)\n                && !IBM_SERVICE_PASSWORD.startsWith(_YOUR_);\n    }\n\n    /**\n     * Check the Wit credentials are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkWitConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkWitConfig\");\n        }\n\n        final String WIT_SERVER_ACCESS_TOKEN = parcel.getWIT_SERVER_ACCESS_TOKEN();\n\n        return UtilsString.notNaked(WIT_SERVER_ACCESS_TOKEN)\n                && !WIT_SERVER_ACCESS_TOKEN.startsWith(_YOUR_);\n    }\n\n    /**\n     * Check the Microsoft credentials are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkMicrosoftConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkMicrosoftConfig\");\n        }\n\n        final String OXFORD_KEY_1 = parcel.getOXFORD_KEY_1();\n        final String OXFORD_KEY_2 = parcel.getOXFORD_KEY_2();\n\n        if (UtilsString.notNaked(OXFORD_KEY_1) && !OXFORD_KEY_1.startsWith(_YOUR_)\n                && UtilsString.notNaked(OXFORD_KEY_2) && !OXFORD_KEY_2.startsWith(_YOUR_)) {\n\n            switch (parcel.getLanguageModel()) {\n\n                case MICROSOFT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkMicrosoftConfig\");\n                    }\n\n                    final String LUIS_APP_ID = parcel.getLUIS_APP_ID();\n                    final String LUIS_SUBSCRIPTION_ID = parcel.getLUIS_SUBSCRIPTION_ID();\n\n                    if (UtilsString.notNaked(LUIS_APP_ID) && !LUIS_APP_ID.startsWith(_YOUR_)\n                            && UtilsString.notNaked(LUIS_SUBSCRIPTION_ID) && !LUIS_SUBSCRIPTION_ID.startsWith(_YOUR_)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkMicrosoftConfig: Credentials present\");\n                        }\n                        return true;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"checkMicrosoftConfig: NLU parameters missing\");\n                        }\n                        return false;\n                    }\n                default:\n                    return true;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"checkMicrosoftConfig: API keys missing\");\n            }\n            return false;\n        }\n    }\n\n    /**\n     * Check the Google API key is correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkGoogleCloudConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkGoogleCloudConfig\");\n        }\n\n        final String GOOGLE_CLOUD_ACCESS_TOKEN = parcel.getGOOGLE_CLOUD_ACCESS_TOKEN();\n\n        return UtilsString.notNaked(GOOGLE_CLOUD_ACCESS_TOKEN) && !GOOGLE_CLOUD_ACCESS_TOKEN.startsWith(_YOUR_)\n                && parcel.getGOOGLE_CLOUD_ACCESS_EXPIRY() > 0;\n    }\n\n    /**\n     * Check the Google API key is correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkGoogleChromiumConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkGoogleChromiumConfig\");\n        }\n\n        final String GOOGLE_CHROMIUM_API_KEY = parcel.getGOOGLE_CHROMIUM_API_KEY();\n\n        return UtilsString.notNaked(GOOGLE_CHROMIUM_API_KEY) && !GOOGLE_CHROMIUM_API_KEY.startsWith(_YOUR_);\n    }\n\n    /**\n     * Check the required Nuance parameters are correctly defined\n     *\n     * @param parcel the {@link RequestParcel}\n     * @return true if the parameters are configured correctly.\n     */\n    private static boolean checkNuanceConfig(@NonNull final RequestParcel parcel) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkNuanceConfig\");\n        }\n\n        switch (parcel.getProviderTTS()) {\n\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkNuanceConfig\");\n                }\n\n                final String NUANCE_APP_KEY = parcel.getNUANCE_APP_KEY();\n                final Uri NUANCE_SERVER_URI = parcel.getNUANCE_SERVER_URI();\n\n                if (!UtilsString.notNaked(NUANCE_APP_KEY) || NUANCE_APP_KEY.startsWith(_YOUR_)) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"checkNuanceConfig: API key missing\");\n                    }\n                    return false;\n                } else if (!UtilsString.notNaked(NUANCE_SERVER_URI.toString())\n                        || NUANCE_SERVER_URI.toString().startsWith(_YOUR_)) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"checkNuanceConfig: URI missing\");\n                    }\n                    return false;\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkNuanceConfig: Credentials present\");\n                    }\n                    break;\n                }\n        }\n\n        switch (parcel.getLanguageModel()) {\n\n            case NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkNuanceConfig\");\n                }\n\n                final String NUANCE_CONTEXT_TAG = parcel.getNUANCE_CONTEXT_TAG();\n                final Uri NUANCE_SERVER_URI_NLU = parcel.getNUANCE_SERVER_URI_NLU();\n\n                if (!UtilsString.notNaked(NUANCE_CONTEXT_TAG) || NUANCE_CONTEXT_TAG.startsWith(_YOUR_)) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"checkNuanceConfig: CONTEXT TAG missing\");\n                    }\n                    return false;\n                } else if (!UtilsString.notNaked(NUANCE_SERVER_URI_NLU.toString())\n                        || NUANCE_SERVER_URI_NLU.toString().startsWith(_YOUR_)) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"checkNuanceConfig: URI missing\");\n                    }\n                    return false;\n                } else if (!NUANCE_SERVER_URI_NLU.toString().contains(NuanceConfiguration.SERVER_HOST_NLU)) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"checkNuanceConfig: HOST requires \"\n                                + NuanceConfiguration.SERVER_HOST_NLU);\n                    }\n                    return false;\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"checkNuanceConfig: Credentials present\");\n                    }\n\n                    break;\n                }\n\n            default:\n\n                switch (parcel.getProviderVR()) {\n\n                    case NUANCE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkNuanceConfig\");\n                        }\n\n                        final String NUANCE_APP_KEY = parcel.getNUANCE_APP_KEY();\n                        final Uri NUANCE_SERVER_URI = parcel.getNUANCE_SERVER_URI();\n\n                        if (!UtilsString.notNaked(NUANCE_APP_KEY) || NUANCE_APP_KEY.startsWith(_YOUR_)) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"checkNuanceConfig: API key missing\");\n                            }\n                            return false;\n                        } else if (!UtilsString.notNaked(NUANCE_SERVER_URI.toString())\n                                || NUANCE_SERVER_URI.toString().startsWith(_YOUR_)) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"checkNuanceConfig: URI missing\");\n                            }\n                            return false;\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"checkNuanceConfig: Credentials present\");\n                            }\n                            break;\n                        }\n                }\n        }\n\n        return true;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/applications/Install.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.applications;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.WorkerThread;\n\nimport com.google.android.gms.auth.GoogleAuthException;\nimport com.google.android.gms.auth.GoogleAuthUtil;\n\nimport java.io.IOException;\n\nimport ai.saiy.android.utils.Global;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class Install {\n\n    public enum Location {\n        PLAYSTORE, AMAZON\n    }\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Install.class.getSimpleName();\n\n    /**\n     * Depending on the version of Saiy, the install link needs to vary. This class handles all\n     * eventualities, so no changes will be needed elsewhere in the code.\n     *\n     * @param ctx         the application context\n     * @param packageName the package name to open\n     * @return true if the intent was successful. False otherwise.\n     */\n    public static boolean showInstallLink(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showInstall\");\n        }\n\n        switch (Global.installLocation) {\n\n            case PLAYSTORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"showInstall PLAYSTORE\");\n                }\n                return InstallPlayStore.showInstall(ctx, packageName);\n            case AMAZON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"showInstall AMAZON\");\n                }\n                return InstallAmazon.showInstall(ctx, packageName);\n            default:\n                return false;\n        }\n    }\n\n    /**\n     * Get the account type associated with the install location of the app\n     *\n     * @return the account type\n     */\n    public static String getAccountType() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAccountType\");\n        }\n\n        switch (Global.installLocation) {\n\n            case PLAYSTORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getAccountType PLAYSTORE\");\n                }\n                return GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE;\n            case AMAZON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getAccountType AMAZON\");\n                }\n                return \"\";\n            default:\n                return \"\";\n        }\n    }\n\n    /**\n     * Asynchronously get the account id associated with the email address\n     *\n     * @param ctx         the application context\n     * @param accountName the email address\n     * @return the account id or null if the operation failed\n     */\n    @WorkerThread\n    public static String getAccountId(@NonNull final Context ctx, @NonNull final String accountName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAccountId\");\n        }\n\n        try {\n\n            switch (Global.installLocation) {\n\n                case PLAYSTORE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getAccountId PLAYSTORE\");\n                    }\n                    final String accountId = GoogleAuthUtil.getAccountId(ctx, accountName);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getAccountId PLAYSTORE: \" + accountId);\n                    }\n                    return accountId;\n                case AMAZON:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getAccountId AMAZON\");\n                    }\n                default:\n                    break;\n            }\n\n        } catch (final GoogleAuthException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getAccountId GoogleAuthException\");\n                e.printStackTrace();\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getAccountId IOException\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Get the install link for Saiy\n     *\n     * @return the String install link\n     */\n    public static String getSaiyInstallLink(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSaiyInstallLink\");\n        }\n\n        switch (Global.installLocation) {\n\n            case PLAYSTORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSaiyInstallLink PLAYSTORE\");\n                }\n                return InstallPlayStore.getSaiyInstallLink(ctx);\n            case AMAZON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSaiyInstallLink AMAZON\");\n                }\n                return InstallAmazon.getSaiyInstallLink(ctx);\n            default:\n                return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/applications/InstallAmazon.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.applications;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class InstallAmazon {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = InstallAmazon.class.getSimpleName();\n\n    /**\n     * Open the Amazon App Store using the package name of the desired application. If the user has the\n     * Amazon application installed and defaulted, this URL will open directly in the application.\n     * Otherwise, it will default to a browser search.\n     *\n     * @param ctx         the application context\n     * @param packageName the package name to open\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean showInstall(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showInstall\");\n        }\n\n        return false;\n    }\n\n    /**\n     * Get the install link for Saiy\n     *\n     * @param ctx the application context\n     * @return the String install link\n     */\n    public static String getSaiyInstallLink(@NonNull final Context ctx) {\n        return IntentConstants.AMAZON_PACKAGE_URL + ctx.getPackageName();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/applications/InstallPlayStore.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.applications;\n\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class InstallPlayStore {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = InstallPlayStore.class.getSimpleName();\n\n    /**\n     * Open the Play Store using the package name of the desired application. If the user has the\n     * Play Store application installed and defaulted, this URL will open directly in the application.\n     * Otherwise, it will default to a browser search.\n     *\n     * @param ctx         the application context\n     * @param packageName the package name to open\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean showInstall(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showInstall\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(IntentConstants.PLAY_STORE_PACKAGE_URL +\n                packageName));\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"showInstall: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"showInstall: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Get the install link for Saiy\n     *\n     * @param ctx the application context\n     * @return the String install link\n     */\n    public static String getSaiyInstallLink(@NonNull final Context ctx) {\n        return IntentConstants.PLAY_STORE_PACKAGE_URL + ctx.getPackageName();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/applications/Installed.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.applications;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.BuildConfig;\nimport ai.saiy.android.Manifest;\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionProvider;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class Installed {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Installed.class.getSimpleName();\n\n    public static final String PACKAGE_FACEBOOK = \"com.facebook.katana\";\n    public static final String PACKAGE_TWITTER = \"com.twitter.android\";\n    public static final String PACKAGE_SNAPCHAT = \"com.snapchat.android\";\n    public static final String PACKAGE_WHATSAPP = \"com.whatsapp\";\n    public static final String PACKAGE_TINDER = \"com.tinder\";\n    public static final String PACKAGE_SHAZAM = \"com.shazam.android\";\n    public static final String PACKAGE_SHAZAM_ENCORE = \"com.shazam.encore.android\";\n    public static final String PACKAGE_SOUND_HOUND = \"com.melodis.midomiMusicIdentifier.freemium\";\n    public static final String PACKAGE_SOUND_HOUND_PREMIUM = \"com.melodis.midomiMusicIdentifier\";\n    public static final String PACKAGE_TRACK_ID = \"com.sonyericsson.trackid\";\n    public static final String PACKAGE_GOOGLE_SOUND_SEARCH = \"com.google.android.ears\";\n    public static final String PACKAGE_NAME_GOOGLE = \"com.google.android\";\n    public static final String PACKAGE_NAME_GOOGLE_NOW = \"com.google.android.googlequicksearchbox\";\n    public static final String PACKAGE_NAME_GOOGLE_NOW_LAUNCHER = \"com.google.android.googlequicksearchbox\";\n    public static final String PACKAGE_TASKER_DIRECT = \"net.dinglisch.android.tasker\";\n    public static final String PACKAGE_TASKER_MARKET = PACKAGE_TASKER_DIRECT + \"m\";\n    public static final String PACKAGE_WOLFRAM_ALPHA = \"com.wolfram.android.alpha\";\n\n    private static final Pattern pCONTROL_SAIY = Pattern.compile(Manifest.permission.CONTROL_SAIY);\n    private static final Pattern pSAIY_PACKAGE = Pattern.compile(BuildConfig.APPLICATION_ID);\n\n    /**\n     * Get a list of all of the installed applications that hold the {@link Manifest.permission#CONTROL_SAIY},\n     * regardless of whether or not the permission has been explicitly granted. The results exclude our own\n     * package.\n     * <p>\n     * This method is slower than {@link #declaresSaiyPermissionLegacy(Context)} but avoids the 'transaction\n     * too large' errors.\n     *\n     * @param ctx the application context\n     * @return an ArrayList containing a {@link Pair} with the first parameter containing the application name\n     * and the second the package name.\n     */\n    public static ArrayList<Pair<String, String>> declaresSaiyPermission(@NonNull final Context ctx) {\n\n        final long then = System.nanoTime();\n\n        final ArrayList<Pair<String, String>> holdsPermission = new ArrayList<>();\n        final PackageManager pm = ctx.getPackageManager();\n        final List<ApplicationInfo> apps = pm.getInstalledApplications(0);\n\n        CharSequence appNameChar;\n        String appName;\n        String[] permissions;\n        PackageInfo packageInfo;\n        for (final ApplicationInfo applicationInfo : apps) {\n\n            try {\n\n                packageInfo = pm.getPackageInfo(applicationInfo.packageName, PackageManager.GET_PERMISSIONS);\n\n                permissions = packageInfo.requestedPermissions;\n                if (permissions != null) {\n\n                    for (final String permission : permissions) {\n                        if (pCONTROL_SAIY.matcher(permission).matches()) {\n\n                            appNameChar = packageInfo.applicationInfo.loadLabel(pm);\n\n                            if (appNameChar != null) {\n                                appName = packageInfo.applicationInfo.loadLabel(pm).toString();\n\n                                if (UtilsString.notNaked(appName)) {\n                                    if (!pSAIY_PACKAGE.matcher(packageInfo.packageName).matches()) {\n                                        holdsPermission.add(new Pair<>(appName, packageInfo.packageName));\n                                    }\n                                    break;\n                                }\n                            }\n\n                            holdsPermission.add(new Pair<>(packageInfo.packageName, packageInfo.packageName));\n                        }\n                    }\n                }\n            } catch (final PackageManager.NameNotFoundException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"isPackageInstalled: NameNotFoundException\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(Installed.class.getSimpleName(), then);\n        }\n\n        return holdsPermission;\n    }\n\n    /**\n     * Get a list of all of the installed applications that hold the {@link Manifest.permission#CONTROL_SAIY},\n     * regardless of whether or not the permission has been explicitly granted. The results exclude our own\n     * package.\n     *\n     * @param ctx the application context\n     * @return an ArrayList containing a {@link Pair} with the first parameter containing the application name\n     * and the second the package name.\n     */\n    public static ArrayList<Pair<String, String>> declaresSaiyPermissionLegacy(@NonNull final Context ctx) {\n\n        final ArrayList<Pair<String, String>> holdsPermission = new ArrayList<>();\n        final PackageManager pm = ctx.getPackageManager();\n        final List<PackageInfo> apps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);\n\n        CharSequence appNameChar;\n        String appName;\n        String[] permissions;\n        for (final PackageInfo packageInfo : apps) {\n\n            permissions = packageInfo.requestedPermissions;\n            if (permissions != null) {\n\n                for (final String permission : permissions) {\n                    if (pCONTROL_SAIY.matcher(permission).matches()) {\n\n                        appNameChar = packageInfo.applicationInfo.loadLabel(pm);\n\n                        if (appNameChar != null) {\n                            appName = packageInfo.applicationInfo.loadLabel(pm).toString();\n\n                            if (UtilsString.notNaked(appName)) {\n                                if (!pSAIY_PACKAGE.matcher(packageInfo.packageName).matches()) {\n                                    holdsPermission.add(new Pair<>(appName, packageInfo.packageName));\n                                }\n                                break;\n                            }\n                        }\n\n                        holdsPermission.add(new Pair<>(packageInfo.packageName, packageInfo.packageName));\n                    }\n                }\n            }\n        }\n\n        return holdsPermission;\n    }\n\n    /**\n     * Check if the user has a package installed\n     *\n     * @param ctx         the application context\n     * @param packageName the application package name\n     * @return true if the package is installed\n     */\n    public static boolean isPackageInstalled(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isPackageInstalled\");\n        }\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(packageName, 0);\n            return true;\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isPackageInstalled: NameNotFoundException\");\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isPackageInstalled: NullPointerException\");\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isPackageInstalled: Exception\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if the user has either of the Shazam applications installed\n     *\n     * @param ctx the application context\n     * @return true if the package is installed\n     */\n    public static boolean shazamInstalled(@NonNull final Context ctx) {\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SHAZAM, 0);\n            return true;\n        } catch (final PackageManager.NameNotFoundException e) {\n            try {\n                ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SHAZAM_ENCORE, 0);\n                return true;\n            } catch (final PackageManager.NameNotFoundException ee) {\n                return false;\n            }\n        }\n    }\n\n    /**\n     * Check if the user has either of the Sound Hound applications installed\n     *\n     * @param ctx the application context\n     * @return true if the package is installed\n     */\n    public static boolean soundHoundInstalled(@NonNull final Context ctx) {\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SOUND_HOUND, 0);\n            return true;\n        } catch (final PackageManager.NameNotFoundException e) {\n            try {\n                ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SOUND_HOUND_PREMIUM, 0);\n                return true;\n            } catch (final PackageManager.NameNotFoundException ee) {\n                return false;\n            }\n        }\n    }\n\n    /**\n     * Check to see which supported applications are installed that can recognise music tracks.\n     * <p>\n     * We don't add both the freemium and premium packages, as they both respond to the same\n     * application specific intent.\n     * <p>\n     * In the future, only applications that respond to saiy.intent.action.MEDIA_RECOGNIZE will\n     * be supported.\n     *\n     * @param ctx the application context\n     * @return an {@link ArrayList} containing {@link SongRecognitionProvider} providers\n     */\n    public static ArrayList<SongRecognitionProvider> getSongRecognitionProviders(@NonNull final Context ctx) {\n\n        final ArrayList<SongRecognitionProvider> providers = new ArrayList<>();\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SHAZAM, 0);\n            providers.add(SongRecognitionProvider.SHAZAM);\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"Shazam installed\");\n            }\n        } catch (final PackageManager.NameNotFoundException e) {\n            try {\n                ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SHAZAM_ENCORE, 0);\n                providers.add(SongRecognitionProvider.SHAZAM);\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"Shazam Encore installed\");\n                }\n            } catch (final PackageManager.NameNotFoundException ee) {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"Shazam variant not installed\");\n                }\n            }\n        }\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SOUND_HOUND, 0);\n            providers.add(SongRecognitionProvider.SOUND_HOUND);\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"Sound Hound installed\");\n            }\n        } catch (final PackageManager.NameNotFoundException e) {\n            try {\n                ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_SOUND_HOUND_PREMIUM, 0);\n                providers.add(SongRecognitionProvider.SOUND_HOUND);\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"Sound Hound Premium installed\");\n                }\n            } catch (final PackageManager.NameNotFoundException ee) {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"Sound Hound variant not installed\");\n                }\n            }\n        }\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_GOOGLE_SOUND_SEARCH, 0);\n            providers.add(SongRecognitionProvider.GOOGLE);\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"Google Sound Search installed\");\n            }\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"Google Sound Search not installed\");\n            }\n        }\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(PACKAGE_TRACK_ID, 0);\n            providers.add(SongRecognitionProvider.TRACK_ID);\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"TrackID installed\");\n            }\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"TrackID not installed\");\n            }\n        }\n\n        return providers;\n    }\n\n    /**\n     * Get the package name of the user's default launcher\n     *\n     * @param ctx the application context\n     * @return the package name of the default launcher\n     */\n    public static String getDefaultLauncherPackage(@NonNull final Context ctx) {\n\n        final Intent intent = new Intent(Intent.ACTION_MAIN);\n        intent.addCategory(Intent.CATEGORY_HOME);\n\n        final ResolveInfo resolveInfo = ctx.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);\n\n        if (resolveInfo != null) {\n            return resolveInfo.activityInfo.packageName;\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Check if the user's default launcher is Google Now Launcher\n     *\n     * @param ctx the application context\n     * @return the package name of the default launcher\n     */\n    public static boolean isGoogleNowLauncherDefault(@NonNull final Context ctx) {\n\n        final String defaultLauncherPackage = getDefaultLauncherPackage(ctx);\n\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"isGoogleNowLauncherDefault: \" + defaultLauncherPackage);\n        }\n\n        return UtilsString.notNaked(defaultLauncherPackage)\n                && defaultLauncherPackage.matches(PACKAGE_NAME_GOOGLE_NOW_LAUNCHER);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/applications/UtilsApplication.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.applications;\n\nimport android.annotation.SuppressLint;\nimport android.app.ActivityManager;\nimport android.app.usage.UsageStats;\nimport android.app.usage.UsageStatsManager;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.RequiresApi;\nimport android.util.Pair;\n\nimport java.util.List;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Utility class for application related methods.\n * <p>\n * Created by benrandall76@gmail.com on 26/04/2016.\n */\npublic class UtilsApplication {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = UtilsApplication.class.getSimpleName();\n\n    public static final String USAGE_STATS_SERVICE = \"usagestats\";\n\n    /**\n     * Helper method to get an application name from a package name\n     *\n     * @param ctx         the application context\n     * @param packageName the application package name\n     * @return a {@link Pair} of which the first parameter denotes success and the second the application\n     * name or null if the process failed.\n     */\n    public static Pair<Boolean, String> getAppNameFromPackage(@NonNull final Context ctx, @NonNull final String packageName) {\n\n        final PackageManager packageManager = ctx.getPackageManager();\n        final ApplicationInfo applicationInfo;\n\n        try {\n            applicationInfo = packageManager.getApplicationInfo(packageName, 0);\n            return new Pair<>(true, packageManager.getApplicationLabel(applicationInfo).toString());\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getAppNameFromPackage: NameNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getAppNameFromPackage: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getAppNameFromPackage: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    /**\n     * Kill a specific application\n     *\n     * @param ctx         the application context\n     * @param packageName of the application to kill\n     * @return true if the process was killed successfully, false otherwise\n     */\n    public static boolean killPackage(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"killPackage\");\n        }\n\n        try {\n            ((ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE))\n                    .killBackgroundProcesses(packageName);\n            return true;\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"killPackage: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if a specific application package is installed\n     *\n     * @param ctx         the application context\n     * @param packageName of the application\n     * @return true if the application is installed, false otherwise\n     */\n    public static boolean isAppInstalled(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isAppInstalled\");\n        }\n\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(packageName, 0);\n            return true;\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isAppInstalled: NameNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isAppInstalled: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"isAppInstalled: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Launch an application from its package name\n     *\n     * @param ctx         the application context\n     * @param packageName of the application\n     * @return true if the application was successfully launched, false otherwise\n     */\n    public static boolean launchAppFromPackageName(final Context ctx, final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"launchAppFromPackageName\");\n        }\n\n        final Intent intent = ctx.getPackageManager().getLaunchIntentForPackage(packageName);\n\n        if (intent != null) {\n            try {\n                ctx.startActivity(intent);\n                return true;\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"launchAppFromPackageName: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n\n    }\n\n    /**\n     * Get the package name of the current foreground application\n     *\n     * @param ctx the application context\n     * @return the package name or null\n     */\n    @SuppressLint(\"InlinedApi\")\n    public static String getForegroundPackage(@NonNull final Context ctx, final long history) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getForegroundPackage\");\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            //noinspection NewApi\n            return getForegroundPackage21(ctx, history);\n        } else {\n            return getForegroundPackageDeprecated(ctx);\n        }\n    }\n\n    /**\n     * Get the package name of the current foreground application\n     *\n     * @param ctx the application context\n     * @return the package name or null\n     */\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    private static String getForegroundPackage21(@NonNull final Context ctx, final long history) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getForegroundPackage21\");\n        }\n\n        try {\n\n            @SuppressWarnings(\"WrongConstant\")\n            final UsageStatsManager mUsageStatsManager = (UsageStatsManager)\n                    ctx.getSystemService(USAGE_STATS_SERVICE);\n\n            final long currentTime = System.currentTimeMillis();\n\n            final List<UsageStats> statsList = mUsageStatsManager.queryUsageStats(\n                    UsageStatsManager.INTERVAL_DAILY, currentTime - history, currentTime);\n\n            if (UtilsList.notNaked(statsList)) {\n\n                final SortedMap<Long, UsageStats> sortedMap = new TreeMap<>();\n\n                for (final UsageStats usageStats : statsList) {\n                    sortedMap.put(usageStats.getLastTimeUsed(), usageStats);\n                }\n\n                if (!sortedMap.isEmpty()) {\n                    final String packageName = sortedMap.get(sortedMap.lastKey()).getPackageName();\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getForegroundPackage21: packageName: \" + packageName);\n                    }\n\n                    return packageName;\n                }\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackage21 NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final IndexOutOfBoundsException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackage21 IndexOutOfBoundsException\");\n                e.printStackTrace();\n            }\n        } catch (final SecurityException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackage21 SecurityException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackage21 Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n\n\n    /**\n     * Get the package name of the current foreground application\n     *\n     * @param ctx the application context\n     * @return the package name or null\n     */\n    @SuppressWarnings(\"deprecation\")\n    private static String getForegroundPackageDeprecated(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getForegroundPackageDeprecated\");\n        }\n\n        try {\n\n            final ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);\n\n            final List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);\n\n            if (taskInfo != null && taskInfo.size() > 0) {\n\n                final ActivityManager.RunningTaskInfo ti = taskInfo.get(0);\n\n                if (ti != null) {\n\n                    final PackageManager pm = ctx.getPackageManager();\n\n                    final ComponentName cn = ti.topActivity;\n\n                    if (cn != null) {\n\n                        final PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(cn.getPackageName(), 0);\n\n                        if (foregroundAppPackageInfo != null) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"getForegroundPackageDeprecated: packageName: \"\n                                        + foregroundAppPackageInfo.packageName);\n                            }\n\n                            return foregroundAppPackageInfo.packageName;\n                        }\n                    }\n                }\n            }\n\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackageDeprecated NameNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackageDeprecated NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final IndexOutOfBoundsException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackageDeprecated IndexOutOfBoundsException\");\n                e.printStackTrace();\n            }\n        } catch (final SecurityException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackageDeprecated SecurityException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getForegroundPackageDeprecated Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/AudioCompression.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Process;\nimport android.support.annotation.NonNull;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.zip.Deflater;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\nimport ai.saiy.android.cache.speech.IAudioCompression;\nimport ai.saiy.android.database.DBSpeech;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsFile;\n\n/**\n * Class to handle compression of audio bytes.\n * <p>\n * Created by benrandall76@gmail.com on 27/04/2016.\n */\npublic class AudioCompression {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = AudioCompression.class.getSimpleName();\n\n    /**\n     * Compress the audio bytes using GZIP\n     *\n     * @param listener the {@link IAudioCompression}\n     * @param bytes    the byte array to compress\n     */\n    public static void compressBytes(@NonNull final IAudioCompression listener, @NonNull final byte[] bytes) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"compressBytes: bytes size: \" + bytes.length);\n        }\n\n        final long then = System.nanoTime();\n\n        ByteArrayOutputStream byteArrayOutputStream = null;\n        GZIPOutputStream gzipOutputStream = null;\n        byte[] returnBytes = null;\n\n        try {\n\n            byteArrayOutputStream = new ByteArrayOutputStream(bytes.length);\n            gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream) {\n                {\n                    def.setLevel(Deflater.BEST_COMPRESSION);\n                }\n            };\n\n            gzipOutputStream.write(bytes);\n\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"compressBytes IOException1\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"compressBytes NullPointerException1\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"compressBytes Exception1\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            try {\n\n                if (gzipOutputStream != null) {\n                    gzipOutputStream.close();\n                }\n\n                if (byteArrayOutputStream != null) {\n                    byteArrayOutputStream.close();\n\n                    returnBytes = byteArrayOutputStream.toByteArray();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"compressBytes returnBytes size: \" + returnBytes.length);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"compressBytes byteArrayOutputStream: null\");\n                    }\n                }\n\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"compressBytes IOException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"compressBytes NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"compressBytes Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        listener.onCompressionCompleted(returnBytes);\n    }\n\n    /**\n     * Decompress the audio bytes\n     *\n     * @param ctx   the application context\n     * @param bytes the array of audio bytes\n     * @param rowId the row id of the {@link DBSpeech} they were stored in\n     * @return an array of decompressed audio bytes\n     */\n    public static byte[] decompressBytes(final Context ctx, final byte[] bytes, final long rowId) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"decompressBytes bytes size: \" + bytes.length);\n        }\n\n        final long then = System.nanoTime();\n\n        ByteArrayInputStream byteArrayInputStream = null;\n        GZIPInputStream gzipInputStream = null;\n        ByteArrayOutputStream byteArrayOutputStream = null;\n        byte[] returnBytes = null;\n\n        try {\n\n            byteArrayInputStream = new ByteArrayInputStream(bytes);\n            gzipInputStream = new GZIPInputStream(byteArrayInputStream);\n            byteArrayOutputStream = new ByteArrayOutputStream();\n\n            final byte[] buffer = new byte[1024];\n\n            int count;\n            while ((count = gzipInputStream.read(buffer)) > 0) {\n                byteArrayOutputStream.write(buffer, 0, count);\n            }\n\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytes IOException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytes NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytes Exception\");\n                e.printStackTrace();\n            }\n\n        } finally {\n\n            try {\n\n                if (byteArrayInputStream != null) {\n                    byteArrayInputStream.close();\n                }\n\n                if (gzipInputStream != null) {\n                    gzipInputStream.close();\n                }\n\n                if (byteArrayOutputStream != null) {\n                    byteArrayOutputStream.close();\n\n                    returnBytes = byteArrayOutputStream.toByteArray();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"decompressBytes returnBytes size: \" + returnBytes.length);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"decompressBytes byteArrayOutputStream: null\");\n                    }\n                }\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytes IOException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytes NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytes Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (returnBytes == null || returnBytes.length <= 0) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"decompressBytes null or empty: deleting entry\");\n            }\n\n            AsyncTask.execute(new Runnable() {\n                @Override\n                public void run() {\n                    Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);\n                    final DBSpeech dbs = new DBSpeech(ctx);\n                    dbs.deleteEntry(rowId);\n                }\n            });\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return returnBytes;\n    }\n\n    /**\n     * Decompress the audio bytes to a file\n     *\n     * @param ctx   the application context\n     * @param bytes the array of audio bytes\n     * @param rowId the row id of the {@link DBSpeech} they were stored in\n     * @return a file containing the decompressed audio bytes\n     */\n    public static File decompressBytesToFile(final Context ctx, final byte[] bytes, final long rowId) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"decompressBytesToFile bytes size: \" + bytes.length);\n        }\n\n        final long then = System.nanoTime();\n\n        File tempAudioFile = null;\n        ByteArrayInputStream byteArrayInputStream = null;\n        GZIPInputStream gzipInputStream = null;\n        ByteArrayOutputStream byteArrayOutputStream = null;\n        byte[] returnBytes = null;\n\n        try {\n\n            byteArrayInputStream = new ByteArrayInputStream(bytes);\n            gzipInputStream = new GZIPInputStream(byteArrayInputStream);\n            byteArrayOutputStream = new ByteArrayOutputStream();\n\n            final byte[] buffer = new byte[1024];\n\n            int count;\n            while ((count = gzipInputStream.read(buffer)) > 0) {\n                byteArrayOutputStream.write(buffer, 0, count);\n            }\n\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytesToFile IOException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytesToFile NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"decompressBytesToFile Exception\");\n                e.printStackTrace();\n            }\n\n        } finally {\n\n            try {\n\n                if (byteArrayInputStream != null) {\n                    byteArrayInputStream.close();\n                }\n\n                if (gzipInputStream != null) {\n                    gzipInputStream.close();\n                }\n\n                if (byteArrayOutputStream != null) {\n\n                    returnBytes = byteArrayOutputStream.toByteArray();\n\n                    if (returnBytes != null && returnBytes.length > 0) {\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"decompressBytesToFile returnBytes size: \" + returnBytes.length);\n                        }\n\n                        tempAudioFile = UtilsFile.getTempAudioFile(ctx);\n\n                        if (tempAudioFile != null) {\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"decompressBytesToFile: file name: \" + tempAudioFile.getName());\n                            }\n\n                            final FileOutputStream fos = new FileOutputStream(tempAudioFile);\n                            byteArrayOutputStream.writeTo(fos);\n                            byteArrayOutputStream.close();\n                            fos.write(bytes);\n                            fos.flush();\n                            fos.close();\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"tempAudioFile null\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"decompressBytesToFile returnBytes null or empty\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"decompressBytesToFile byteArrayOutputStream: null\");\n                    }\n                }\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytesToFile IOException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytesToFile NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"decompressBytesToFile Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (returnBytes == null || returnBytes.length <= 0) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"decompressBytesToFile null or empty: deleting entry\");\n            }\n\n            AsyncTask.execute(new Runnable() {\n                @Override\n                public void run() {\n                    Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);\n                    final DBSpeech dbs = new DBSpeech(ctx);\n                    dbs.deleteEntry(rowId);\n                }\n            });\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return tempAudioFile;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/AudioParameters.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.media.AudioFormat;\nimport android.media.MediaRecorder;\n\n/**\n * Created by benrandall76@gmail.com on 12/08/2016.\n */\n\npublic class AudioParameters {\n\n    private int nChannels;\n    private int bSamples;\n    private int audioSource;\n    private int sampleRateInHz;\n    private int channelConfig;\n    private int audioFormat;\n\n    public AudioParameters() {\n    }\n\n    public AudioParameters(final int audioFormat, final int audioSource, final int channelConfig,\n                           final int nChannels, final int sampleRateInHz, final int bSamples) {\n        this.audioFormat = audioFormat;\n        this.audioSource = audioSource;\n        this.channelConfig = channelConfig;\n        this.nChannels = nChannels;\n        this.sampleRateInHz = sampleRateInHz;\n        this.bSamples = bSamples;\n    }\n\n    public static AudioParameters getDefault() {\n        return new AudioParameters(AudioFormat.ENCODING_PCM_16BIT,\n                MediaRecorder.AudioSource.VOICE_RECOGNITION,\n                AudioFormat.CHANNEL_IN_MONO, 1, 16000, 16);\n    }\n\n    public static AudioParameters getDefaultBeyondVerbal(){\n        return new AudioParameters(AudioFormat.ENCODING_PCM_16BIT,\n                MediaRecorder.AudioSource.VOICE_RECOGNITION,\n                AudioFormat.CHANNEL_IN_MONO, 1, 8000, 16);\n    }\n\n    public static AudioParameters getDefaultMicrosoft(){\n        return new AudioParameters(AudioFormat.ENCODING_PCM_16BIT,\n                MediaRecorder.AudioSource.VOICE_RECOGNITION,\n                AudioFormat.CHANNEL_IN_MONO, 1, 16000, 16);\n    }\n\n    public int getbSamples() {\n        return bSamples;\n    }\n\n    public void setbSamples(final int bSamples) {\n        this.bSamples = bSamples;\n    }\n\n    public int getAudioFormat() {\n        return audioFormat;\n    }\n\n    public void setAudioFormat(final int audioFormat) {\n        this.audioFormat = audioFormat;\n    }\n\n    public int getAudioSource() {\n        return audioSource;\n    }\n\n    public void setAudioSource(final int audioSource) {\n        this.audioSource = audioSource;\n    }\n\n    public int getChannelConfig() {\n        return channelConfig;\n    }\n\n    public void setChannelConfig(final int channelConfig) {\n        this.channelConfig = channelConfig;\n    }\n\n    public int getnChannels() {\n        return nChannels;\n    }\n\n    public void setnChannels(final int nChannels) {\n        this.nChannels = nChannels;\n    }\n\n    public int getSampleRateInHz() {\n        return sampleRateInHz;\n    }\n\n    public void setSampleRateInHz(final int sampleRateInHz) {\n        this.sampleRateInHz = sampleRateInHz;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/IMic.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\n/**\n * Created by benrandall76@gmail.com on 12/08/2016.\n */\n\npublic interface IMic {\n\n    void onBufferReceived(final int bufferReadResult, final byte[] buffer);\n\n    void onError(final int error);\n\n    void onPauseDetected();\n\n    void onRecordingStarted();\n\n    void onRecordingEnded();\n\n    void onFileWriteComplete(final boolean success);\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/RecognitionMic.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.content.Context;\nimport android.media.AudioRecord;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.io.File;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport ai.saiy.android.audio.pause.PauseDetector;\nimport ai.saiy.android.audio.pause.PauseListener;\nimport ai.saiy.android.files.FileCreator;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 12/08/2016.\n */\n\npublic class RecognitionMic implements PauseListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionMic.class.getSimpleName();\n\n    public static final int SAMPLE_RATE_HZ_16000 = 16000;\n    public static final int SAMPLE_RATE_HZ_8000 = 8000;\n\n    private static final int ON_READY_FOR_SPEECH = 0;\n    private static final int ON_BEGINNING_OF_SPEECH = 1;\n    private static final int ON_ERROR = 2;\n    private static final int ON_END_OF_SPEECH = 3;\n\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private final AtomicBoolean isAvailable = new AtomicBoolean(true);\n    private final AtomicBoolean isInterrupted = new AtomicBoolean();\n    private final AtomicBoolean thrown = new AtomicBoolean();\n    private final SaiySoundPool ssp;\n\n    private FileCreator fileCreator;\n    private PauseDetector pauseDetector;\n    private volatile SaiyRecorder saiyRecorder;\n    private final Object audioLock = new Object();\n    private final Object errorLock = new Object();\n\n    private final SaiyRecognitionListener listener;\n    private final boolean pauseDetection;\n    private final AtomicBoolean writeToFile;\n    private IMic iMic;\n    private final Object lock = new Object();\n    private final Context mContext;\n\n    public RecognitionMic(@NonNull final Context mContext, @Nullable final SaiyRecognitionListener listener,\n                          @NonNull final AudioParameters audioParameters, final boolean pauseDetection,\n                          final long pauseIgnoreTime, final boolean enhance, final boolean writeToFile,\n                          @NonNull final SaiySoundPool ssp) {\n        this.mContext = mContext;\n        this.listener = listener;\n        this.pauseDetection = pauseDetection;\n        this.writeToFile = new AtomicBoolean(writeToFile);\n        this.ssp = ssp;\n\n        if (this.pauseDetection) {\n            pauseDetector = new PauseDetector(this, audioParameters.getSampleRateInHz(),\n                    audioParameters.getnChannels(), pauseIgnoreTime);\n        }\n\n        saiyRecorder = new SaiyRecorder(audioParameters.getAudioSource(),\n                audioParameters.getSampleRateInHz(), audioParameters.getChannelConfig(),\n                audioParameters.getAudioFormat(), enhance);\n\n        if (this.writeToFile.get()) {\n            fileCreator = new FileCreator(this.mContext, audioParameters.getnChannels(),\n                    audioParameters.getSampleRateInHz(), audioParameters.getbSamples());\n        }\n    }\n\n    public File getFile() {\n        return fileCreator.getDefaultFile();\n    }\n\n    public void setMicListener(@NonNull final IMic iMic) {\n        this.iMic = iMic;\n    }\n\n    public IMic getMicListener() {\n        return iMic;\n    }\n\n    public Object getLock() {\n        return lock;\n    }\n\n    public boolean isRecording() {\n        return isRecording.get();\n    }\n\n    public boolean isAvailable() {\n        return isAvailable.get();\n    }\n\n    public boolean isInterrupted() {\n        return isInterrupted.get();\n    }\n\n    public SaiyRecognitionListener getRecognitionListener() {\n        return listener;\n    }\n\n    public Context getContext() {\n        return this.mContext;\n    }\n\n    public void stopRecording() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopRecording\");\n        }\n\n        if (isRecording.get()) {\n            isInterrupted.set(true);\n            isRecording.set(false);\n            Recognition.setState(Recognition.State.IDLE);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"stopRecording: not recording\");\n            }\n        }\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            isRecording.set(false);\n            Recognition.setState(Recognition.State.IDLE);\n            iMic.onPauseDetected();\n        }\n    }\n\n    public void startRecording() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startRecording\");\n        }\n\n        isRecording.set(true);\n\n        if (iMic == null) {\n            throw new IllegalArgumentException(\"No iMic listener is set\");\n        }\n\n        new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);\n\n                ssp.play(ssp.getBeepStart());\n\n                if (pauseDetection) {\n                    pauseDetector.begin();\n                }\n\n                final int bufferSize = saiyRecorder.getBufferSize();\n\n                final byte[] buffer = new byte[bufferSize];\n\n                switch (saiyRecorder.initialise()) {\n\n                    case AudioRecord.STATE_INITIALIZED:\n\n                        try {\n\n                            switch (saiyRecorder.startRecording()) {\n\n                                case AudioRecord.RECORDSTATE_RECORDING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"AudioRecord.RECORDSTATE_RECORDING\");\n                                    }\n\n                                    int count = 0;\n                                    while (isRecording.get() && saiyRecorder != null\n                                            && saiyRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n\n                                        if (count == 0) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"Recording Started\");\n                                            }\n\n                                            Recognition.setState(Recognition.State.LISTENING);\n                                            recognitionListenerAction(ON_READY_FOR_SPEECH);\n                                            iMic.onRecordingStarted();\n                                            count++;\n                                        }\n\n                                        final int bufferReadResult = saiyRecorder.read(buffer);\n\n                                        if (pauseDetection && !pauseDetector.hasDetected()) {\n                                            pauseDetector.addLength(buffer, bufferReadResult);\n                                            pauseDetector.monitor();\n                                        }\n\n                                        if (writeToFile.get()) {\n                                            fileCreator.passBuffer(buffer);\n                                        }\n\n                                        iMic.onBufferReceived(bufferReadResult, buffer);\n                                    }\n\n                                    break;\n                                case AudioRecord.ERROR:\n                                default:\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"AudioRecord.ERROR\");\n                                    }\n                                    onError(SpeechRecognizer.ERROR_AUDIO);\n                                    break;\n                            }\n\n                        } catch (final IllegalStateException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"IllegalStateException\");\n                                e.printStackTrace();\n                            }\n                        } catch (final NullPointerException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"NullPointerException\");\n                                e.printStackTrace();\n                            }\n                        } catch (final Exception e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"Exception\");\n                                e.printStackTrace();\n                            }\n                        }\n\n                        audioShutdown();\n                        break;\n\n                    case AudioRecord.STATE_UNINITIALIZED:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"AudioRecord.STATE_UNINITIALIZED\");\n                        }\n                        onError(SpeechRecognizer.ERROR_AUDIO);\n                        break;\n                }\n            }\n        }.start();\n    }\n\n    private void onError(final int error) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onError\");\n        }\n\n        synchronized (errorLock) {\n            if (!thrown.get()) {\n                thrown.set(true);\n                recognitionListenerAction(ON_ERROR);\n                audioShutdown();\n                iMic.onError(error);\n            }\n        }\n    }\n\n    /**\n     * Force shutdown the microphone and release the resources, without calling any of the\n     * listeners\n     */\n    public void forceAudioShutdown() {\n\n        synchronized (audioLock) {\n\n            if (saiyRecorder != null) {\n                isAvailable.set(false);\n                Recognition.setState(Recognition.State.IDLE);\n                saiyRecorder.shutdown(CLS_NAME);\n                saiyRecorder = null;\n                fileCreator = null;\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"forceAudioShutdown: saiyRecorder already null\");\n                }\n            }\n\n            System.gc();\n        }\n    }\n\n    /**\n     * Shutdown the microphone and release the resources\n     */\n    private void audioShutdown() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"audioShutdown\");\n        }\n\n        synchronized (audioLock) {\n\n            if (saiyRecorder != null) {\n                isAvailable.set(false);\n                ssp.play(ssp.getBeepStop());\n                Recognition.setState(Recognition.State.IDLE);\n                saiyRecorder.shutdown(CLS_NAME);\n                saiyRecorder = null;\n                releaseLock();\n                recognitionListenerAction(ON_END_OF_SPEECH);\n                iMic.onRecordingEnded();\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"audioShutdown: saiyRecorder already null\");\n                }\n            }\n\n            if (writeToFile.get()) {\n                writeToFile.set(false);\n                iMic.onFileWriteComplete(fileCreator.completeWrite());\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"audioShutdown: finished synchronisation\");\n            }\n\n            System.gc();\n        }\n    }\n\n    /**\n     * Notify waiting threads\n     */\n    private void releaseLock() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"audioShutdown: releaseLock\");\n        }\n        synchronized (lock) {\n            lock.notifyAll();\n        }\n    }\n\n    /**\n     * Send callbacks through to our {@link SaiyRecognitionListener}\n     *\n     * @param action of the listener\n     */\n    private void recognitionListenerAction(final int action) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"recognitionListenerAction\");\n        }\n\n        if (listener == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListenerAction: listener not required\");\n            }\n            return;\n        }\n\n        switch (action) {\n\n            case ON_READY_FOR_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recognitionListenerAction: ON_READY_FOR_SPEECH\");\n                }\n                listener.onReadyForSpeech(null);\n                break;\n            case ON_BEGINNING_OF_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recognitionListenerAction: ON_BEGINNING_OF_SPEECH\");\n                }\n                listener.onBeginningOfSpeech();\n                break;\n            case ON_ERROR:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"recognitionListenerAction: ON_ERROR\");\n                }\n                listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                break;\n            case ON_END_OF_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recognitionListenerAction: ON_END_OF_SPEECH\");\n                }\n                listener.onEndOfSpeech();\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/SaiyAudio.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.media.audiofx.AcousticEchoCanceler;\nimport android.media.audiofx.AutomaticGainControl;\nimport android.media.audiofx.NoiseSuppressor;\nimport android.os.Build;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Wrapper around {@link AudioRecord} to set enhancers by default if they are available.\n * <p/>\n * Created by benrandall76@gmail.com on 12/02/2016.\n */\npublic class SaiyAudio extends AudioRecord {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyAudio.class.getSimpleName();\n\n    /**\n     * Class constructor.\n     * Though some invalid parameters will result in an {@link IllegalArgumentException} exception,\n     * other errors do not.  Thus you should call {@link #getState()} immediately after construction\n     * to confirm that the object is usable.\n     *\n     * @param audioSource       the recording source.\n     *                          See {@link MediaRecorder.AudioSource} for the recording source definitions.\n     * @param sampleRateInHz    the sample rate expressed in Hertz. 44100Hz is currently the only\n     *                          rate that is guaranteed to work on all devices, but other rates such as 22050,\n     *                          16000, and 11025 may work on some devices.\n     * @param channelConfig     describes the configuration of the audio channels.\n     *                          See {@link AudioFormat#CHANNEL_IN_MONO} and\n     *                          {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed\n     *                          to work on all devices.\n     * @param audioFormat       the format in which the audio data is to be returned.\n     *                          See {@link AudioFormat#ENCODING_PCM_8BIT}, {@link AudioFormat#ENCODING_PCM_16BIT},\n     *                          and {@link AudioFormat#ENCODING_PCM_FLOAT}.\n     * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written\n     *                          to during the recording. New audio data can be read from this buffer in smaller chunks\n     *                          than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum\n     *                          required buffer size for the successful creation of an AudioRecord instance. Using values\n     *                          smaller than getMinBufferSize() will result in an initialization failure.\n     * @param enhance           if audio enhancers should be added\n     */\n    public SaiyAudio(final int audioSource, final int sampleRateInHz, final int channelConfig,\n                     final int audioFormat, final int bufferSizeInBytes, final boolean enhance)\n            throws IllegalArgumentException {\n\n        super(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);\n\n        if (enhance && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"attempting audio enhancements\");\n            }\n\n            setEnhancers(getAudioSessionId());\n        }\n    }\n\n    /**\n     * Attempt to set enhancers available on modern devices.\n     * <p/>\n     * These are hardware dependent, not build version. Although the APIs weren't available to\n     * devices until API Level 16\n     */\n    @SuppressWarnings(\"NewApi\")\n    private void setEnhancers(final int sessionId) {\n\n        if (!DEBUG) {\n            NoiseSuppressor.create(sessionId);\n            AcousticEchoCanceler.create(sessionId);\n            AutomaticGainControl.create(sessionId);\n        } else {\n            if (NoiseSuppressor.create(sessionId) == null) {\n                MyLog.i(CLS_NAME, \"NoiseSuppressor null\");\n            } else {\n                MyLog.i(CLS_NAME, \"NoiseSuppressor success\");\n            }\n\n            if (AcousticEchoCanceler.create(sessionId) == null) {\n                MyLog.i(CLS_NAME, \"AcousticEchoCanceler null\");\n            } else {\n                MyLog.i(CLS_NAME, \"AcousticEchoCanceler success\");\n            }\n\n            if (AutomaticGainControl.create(sessionId) == null) {\n                MyLog.i(CLS_NAME, \"AutomaticGainControl null\");\n            } else {\n                MyLog.i(CLS_NAME, \"AutomaticGainControl success\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/SaiyAudioTrack.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.media.AudioFormat;\nimport android.media.AudioManager;\nimport android.media.AudioTrack;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.NoSuchElementException;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\n\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Wrapper class around the {@link AudioTrack} object to handle application specific eventualities\n * <p>\n * Created by benrandall76@gmail.com on 28/04/2016.\n */\npublic class SaiyAudioTrack extends AudioTrack {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SaiyAudioTrack.class.getSimpleName();\n\n    private static final int WAV_OFFSET = 44;\n    private static final int MAX_AUDIO_BUFFER_SIZE = 8192;\n    private static final int SAMPLE_RATE_HZ = 16000;\n    private static final int CHANNEL_OUT = AudioFormat.CHANNEL_OUT_MONO;\n    private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;\n\n    private static final int MIN_BUFFER_SIZE = AudioTrack.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_OUT, ENCODING);\n\n    private volatile SaiyProgressListener listener;\n\n    private final BlockingQueue<Pair<byte[], String>> byteQueue = new LinkedBlockingQueue<>();\n\n    /**\n     * Class constructor.\n     *\n     * @param streamType        the type of the audio stream. See\n     *                          {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},\n     *                          {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},\n     *                          {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}.\n     * @param sampleRateInHz    the initial source sample rate expressed in Hz.\n     * @param channelConfig     describes the configuration of the audio channels.\n     *                          See {@link AudioFormat#CHANNEL_OUT_MONO} and\n     *                          {@link AudioFormat#CHANNEL_OUT_STEREO}\n     * @param audioFormat       the format in which the audio data is represented.\n     *                          See {@link AudioFormat#ENCODING_PCM_16BIT},\n     *                          {@link AudioFormat#ENCODING_PCM_8BIT},\n     *                          and {@link AudioFormat#ENCODING_PCM_FLOAT}.\n     * @param bufferSizeInBytes the total size (in bytes) of the internal buffer where audio data is\n     *                          read from for playback. This should be a multiple of the frame size in bytes.\n     *                          <p> If the track's creation mode is {@link #MODE_STATIC},\n     *                          this is the maximum length sample, or audio clip, that can be played by this instance.\n     *                          <p> If the track's creation mode is {@link #MODE_STREAM},\n     *                          this should be the desired buffer size\n     *                          for the <code>AudioTrack</code> to satisfy the application's\n     *                          natural latency requirements.\n     *                          If <code>bufferSizeInBytes</code> is less than the\n     *                          minimum buffer size for the output sink, it is automatically increased to the minimum\n     *                          buffer size.\n     *                          The method {@link #getBufferSizeInFrames()} returns the\n     *                          actual size in frames of the native buffer created, which\n     *                          determines the frequency to write\n     *                          to the streaming <code>AudioTrack</code> to avoid under-run.\n     * @param mode              streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}\n     * @throws java.lang.IllegalArgumentException\n     */\n    public SaiyAudioTrack(final int streamType, final int sampleRateInHz, final int channelConfig,\n                          final int audioFormat, final int bufferSizeInBytes, final int mode) throws IllegalArgumentException {\n        super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);\n    }\n\n    @Override\n    public int write(@NonNull final byte[] audioData, final int offsetInBytes, final int sizeInBytes) {\n        return super.write(audioData, offsetInBytes, sizeInBytes);\n    }\n\n    /**\n     * Stops playing the audio data.\n     * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing\n     * after the last buffer that was written has been played. For an immediate stop, use\n     * {@link #pause()}, followed by {@link #flush()} to discard audio data that hasn't been played\n     * back yet.\n     *\n     * @throws IllegalStateException\n     */\n    @Override\n    public void stop() throws IllegalStateException {\n        super.stop();\n    }\n\n    /**\n     * Handles the two circumstances of stop being called at the end of standard playback, or an\n     * interrupt call, where the queue needs to be cleared. In the case that the queue needs to be\n     * cleared, an {@link IllegalStateException} is likely to be thrown, but handled gracefully\n     * in {@link #enqueue(byte[], String)} and here.\n     *\n     * @param interrupt true if all pending audio should be stopped.\n     */\n    public void stop(final boolean interrupt) {\n        if (interrupt) {\n            byteQueue.clear();\n\n            flush();\n            release();\n        }\n\n        try {\n            super.stop();\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stop: IllegalStateException\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Listener to notify of audio callbacks\n     *\n     * @param listener the {@link SaiyProgressListener}\n     */\n    public void setListener(@NonNull final SaiyProgressListener listener) {\n        this.listener = listener;\n    }\n\n    /**\n     * Add a byte[] of uncompressed audio to the queue to process. If the queue isn't currently\n     * processing any audio, it will be started.\n     *\n     * @param uncompressedBytes the uncompressed audio byte[]\n     * @param utteranceId       the utterance id\n     */\n    public void enqueue(@NonNull final byte[] uncompressedBytes, @NonNull final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"enqueue: queue size: \" + byteQueue.size());\n        }\n\n        synchronized (byteQueue) {\n            if (byteQueue.isEmpty()) {\n                byteQueue.add(new Pair<>(uncompressedBytes, utteranceId));\n                try {\n                    process();\n                } catch (final NoSuchElementException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"enqueue: process: NoSuchElementException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"enqueue: process: IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                }\n            } else {\n                byteQueue.add(new Pair<>(uncompressedBytes, utteranceId));\n            }\n        }\n    }\n\n    /**\n     * Process any pending audio\n     */\n    private synchronized void process() throws NoSuchElementException, IllegalStateException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"process\");\n        }\n\n        while (!byteQueue.isEmpty()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"processing: queue size: \" + byteQueue.size());\n            }\n\n            play();\n            listener.onStart(byteQueue.element().second);\n\n            int offset = SaiyAudioTrack.WAV_OFFSET;\n            int bytesToWrite;\n            String utteranceId = null;\n            while (byteQueue.element() != null && offset < byteQueue.element().first.length) {\n\n                utteranceId = byteQueue.element().second;\n                bytesToWrite = Math.min(SaiyAudioTrack.MAX_AUDIO_BUFFER_SIZE,\n                        byteQueue.element().first.length - offset);\n                write(byteQueue.element().first, offset, bytesToWrite);\n                offset += bytesToWrite;\n            }\n\n            byteQueue.remove();\n\n            if (byteQueue.isEmpty()) {\n                stop(false);\n            }\n            listener.onDone(utteranceId);\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"processing complete. Queue empty\");\n        }\n    }\n\n    /**\n     * Static helper method to create a {@link SaiyAudioTrack} object, as the Constructor parameters\n     * will always be the same.\n     *\n     * @return a new {@link SaiyAudioTrack} object\n     */\n    public static SaiyAudioTrack getSaiyAudioTrack() {\n\n        try {\n            return new SaiyAudioTrack(AudioManager.STREAM_MUSIC, SaiyAudioTrack.SAMPLE_RATE_HZ,\n                    SaiyAudioTrack.CHANNEL_OUT, SaiyAudioTrack.ENCODING, SaiyAudioTrack.MIN_BUFFER_SIZE,\n                    SaiyAudioTrack.MODE_STREAM);\n        } catch (final IllegalArgumentException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSaiyAudioTrack: IllegalArgumentException\");\n                e.printStackTrace();\n            }\n            return null;\n        }\n    }\n\n    /**\n     * Static helper method to create a {@link SaiyAudioTrack} object.\n     *\n     * @param stream the stream type\n     * @return a new {@link SaiyAudioTrack} object\n     */\n    public static SaiyAudioTrack getSaiyAudioTrack(final int stream) {\n\n        try {\n            return new SaiyAudioTrack(stream, SaiyAudioTrack.SAMPLE_RATE_HZ,\n                    SaiyAudioTrack.CHANNEL_OUT, SaiyAudioTrack.ENCODING, SaiyAudioTrack.MIN_BUFFER_SIZE,\n                    SaiyAudioTrack.MODE_STREAM);\n        } catch (final IllegalArgumentException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSaiyAudioTrack: IllegalArgumentException\");\n                e.printStackTrace();\n            }\n            return null;\n        }\n    }\n\n    public boolean streamMatches(final int stream) {\n        return stream == super.getStreamType();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/SaiyRecorder.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.os.Looper;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Wrapper around the {@link AudioRecord class} to handle errors and setup.\n * <p/>\n * Note - currently only configured for uncompressed recordings.\n * <p/>\n * Created by benrandall76@gmail.com on 15/02/2016.\n */\npublic class SaiyRecorder {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyRecorder.class.getSimpleName();\n\n    private SaiyAudio saiyAudio;\n\n    // Will use in future compressed recordings\n    private final short nChannels = 1;\n    private final short bSamples = 16;\n    private final int TIMER_INTERVAL = 120;\n    private int framePeriod;\n\n    private final int audioSource;\n    private final int sampleRateInHz;\n    private final int channelConfig;\n    private final int audioFormat;\n    private final int bufferSizeInBytes;\n    private final boolean enhance;\n\n    /**\n     * Constructor\n     * <p>\n     * Uses the most common application defaults\n     */\n    public SaiyRecorder() {\n        this.audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;\n        this.sampleRateInHz = 8000;\n        this.channelConfig = AudioFormat.CHANNEL_IN_MONO;\n        this.audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n        this.bufferSizeInBytes = calculateBufferSize();\n        this.enhance = true;\n    }\n\n    /**\n     * Constructor\n     *\n     * @param audioSource    the audio source\n     * @param sampleRateInHz the sampling rate in hertz\n     * @param channelConfig  the channel configuration\n     * @param audioFormat    the audio format\n     */\n    public SaiyRecorder(final int audioSource, final int sampleRateInHz, final int channelConfig,\n                        final int audioFormat, final boolean enhance) {\n        this.audioSource = audioSource;\n        this.sampleRateInHz = sampleRateInHz;\n        this.channelConfig = channelConfig;\n        this.audioFormat = audioFormat;\n        this.bufferSizeInBytes = calculateBufferSize();\n        this.enhance = enhance;\n    }\n\n    /**\n     * Constructor\n     *\n     * @param audioSource       the audio source\n     * @param sampleRateInHz    the sampling rate in hertz\n     * @param channelConfig     the channel configuration\n     * @param audioFormat       the audio format\n     * @param bufferSizeInBytes the buffer size in bytes\n     */\n    public SaiyRecorder(final int audioSource, final int sampleRateInHz, final int channelConfig,\n                        final int audioFormat, final int bufferSizeInBytes, final boolean enhance) {\n        this.audioSource = audioSource;\n        this.sampleRateInHz = sampleRateInHz;\n        this.channelConfig = channelConfig;\n        this.audioFormat = audioFormat;\n        this.bufferSizeInBytes = bufferSizeInBytes;\n        this.enhance = enhance;\n    }\n\n    /**\n     * Initialise the Voice Recorder\n     *\n     * @return The audio record initialisation state.\n     */\n    public int initialise() {\n\n        int count = 0;\n\n        while (count < 4) {\n            count++;\n\n            saiyAudio = new SaiyAudio(audioSource, sampleRateInHz, channelConfig, audioFormat,\n                    bufferSizeInBytes, enhance);\n\n            if (saiyAudio.getState() == AudioRecord.STATE_INITIALIZED) {\n                return AudioRecord.STATE_INITIALIZED;\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"SaiyAudio reinitialisation attempt ~ \" + count);\n                }\n\n                if (Looper.myLooper() != null && Looper.myLooper() != Looper.getMainLooper()) {\n\n                    // Give the audio object a small chance to sort itself out\n                    try {\n                        Thread.sleep(250);\n                    } catch (InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"SaiyAudio InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"SaiyAudio initialisation failed\");\n        }\n\n        return AudioRecord.STATE_UNINITIALIZED;\n    }\n\n\n    /**\n     * Reads audio data from the audio hardware for recording into a byte array.\n     *\n     * @param buffer the array to which the recorded audio data is written.\n     * @return the number of bytes that were read. The number of bytes will not exceed sizeInBytes.\n     */\n    public int read(@NonNull final byte[] buffer) {\n        return saiyAudio.read(buffer, 0, buffer.length);\n    }\n\n    /**\n     * Reads audio data from the audio hardware for recording into a short array.\n     *\n     * @param audioData short audio data\n     * @param offsetInShorts the offset to read from\n     * @param sizeInShorts size of the audio data\n     * @return size read\n     */\n    public int read(short[] audioData, int offsetInShorts, int sizeInShorts) {\n        return saiyAudio.read(audioData, offsetInShorts, sizeInShorts);\n    }\n\n    /**\n     * Get the recording state from the audio session.\n     *\n     * @return the audio session state\n     */\n    public int getRecordingState() {\n        return saiyAudio.getRecordingState();\n    }\n\n    public int getBufferSize() {\n        return bufferSizeInBytes;\n    }\n\n    /**\n     * Start the audio recording session.\n     */\n    public int startRecording() {\n\n        try {\n\n            saiyAudio.startRecording();\n\n            if (saiyAudio.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"AudioRecord == RECORDSTATE_RECORDING\");\n                }\n\n                return AudioRecord.RECORDSTATE_RECORDING;\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"AudioRecord != RECORDSTATE_RECORDING\");\n                }\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"AudioRecord IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"AudioRecord NullPointerException\");\n                e.printStackTrace();\n            }\n        }\n\n        return AudioRecord.ERROR;\n    }\n\n    /**\n     * Shutdown the audio session\n     *\n     * @param from a String label to identify the source of the request. Useful for logging only.\n     */\n    public void shutdown(@NonNull final String from) {\n\n        if (saiyAudio == null) {\n            return;\n        }\n\n        if (saiyAudio.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n\n            try {\n                saiyAudio.stop();\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"saiyAudio.stop(): IllegalStateException ~ \" + from);\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"saiyAudio.stop(): NullPointerException ~ \" + from);\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"saiyAudio.stop(): Exception ~ \" + from);\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"saiyAudio != AudioRecord.RECORDSTATE_RECORDING ~ \" + from);\n            }\n        }\n\n        try {\n            saiyAudio.release();\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"saiyAudio.release(): IllegalStateException ~ \" + from);\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"saiyAudio.release(): NullPointerException ~ \" + from);\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"saiyAudio.release(): Exception ~ \" + from);\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"saiyAudio set to NULL ~ \" + from);\n        }\n\n        saiyAudio = null;\n    }\n\n    /**\n     * Calculate the buffer size.\n     *\n     * @return the calculated buffer size\n     */\n    private int calculateBufferSize() {\n\n        framePeriod = sampleRateInHz * TIMER_INTERVAL / 1000;\n        int bufferSize = framePeriod * 2 * bSamples * nChannels / 8;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"bufferSize: \" + bufferSize);\n        }\n\n        final int minBuff = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);\n\n        switch (minBuff) {\n\n            case AudioRecord.ERROR:\n            case AudioRecord.ERROR_BAD_VALUE:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"AudioRecord.ERROR/ERROR_BAD_VALUE\");\n                }\n                break;\n            default:\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"minBuff: \" + minBuff);\n                }\n\n                if (bufferSize < minBuff) {\n                    bufferSize = minBuff;\n\n                    // Unused for now\n                    framePeriod = bufferSize / (2 * bSamples * nChannels / 8);\n                }\n\n                break;\n        }\n\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"bufferSize returning: \" + bufferSize);\n        }\n\n        return bufferSize;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/SaiySoundPool.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio;\n\nimport android.content.Context;\nimport android.media.AudioAttributes;\nimport android.media.AudioManager;\nimport android.media.SoundPool;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Wrapper class around {@link SoundPool} to handle short sounds\n * <p>\n * Created by benrandall76@gmail.com on 14/09/2016.\n */\n\npublic class SaiySoundPool implements SoundPool.OnLoadCompleteListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiySoundPool.class.getSimpleName();\n\n    public static final int VOICE_RECOGNITION = 1;\n    private static final int MAX_STREAMS = 2;\n\n    private volatile SoundPool sp;\n\n    private int beepStart;\n    private int beepStop;\n    private boolean beepStopInitialised;\n    private boolean beepStartInitialised;\n\n    /**\n     * Set up the {@link SoundPool} object, adding the relevant sounds automatically and\n     * generating their id for future use.\n     *\n     * @param ctx  the application context\n     * @param type the type of media the {@link SoundPool} will be used for\n     */\n    public SaiySoundPool setUp(@NonNull final Context ctx, final int type) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n\n            sp = new SoundPool.Builder().setAudioAttributes(new AudioAttributes.Builder()\n                    .setUsage(AudioAttributes.USAGE_MEDIA)\n                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)\n                    .build()).build();\n        } else {\n            //noinspection deprecation\n            sp = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);\n        }\n\n        sp.setOnLoadCompleteListener(this);\n\n        switch (type) {\n            case VOICE_RECOGNITION:\n                new Thread() {\n                    public void run() {\n                        beepStart = sp.load(ctx, R.raw.beep_high, 1);\n                        beepStop = sp.load(ctx, R.raw.beep_low, 1);\n                    }\n                }.start();\n                break;\n            default:\n                break;\n        }\n\n        return this;\n    }\n\n    /**\n     * Play an already loaded sound\n     *\n     * @param soundId the id of the sound generated in {@link #setUp(Context, int)}\n     * @return non-zero stream id if successful\n     */\n    public int play(final int soundId) {\n\n        if (sp == null) {\n            return 0;\n        }\n\n        return sp.play(soundId, 0.05f, 0.05f, 1, 0, 1f);\n    }\n\n    /**\n     * Release the {@link SoundPool} and resources\n     */\n    public void release() {\n        sp.release();\n        sp = null;\n    }\n\n    public int getBeepStart() {\n        return beepStart;\n    }\n\n    public int getBeepStop() {\n        return beepStop;\n    }\n\n    public boolean isBeepStartInitialised() {\n        return beepStartInitialised;\n    }\n\n    public boolean isBeepStopInitialised() {\n        return beepStopInitialised;\n    }\n\n    /**\n     * Called when a sound has completed loading.\n     *\n     * @param soundPool SoundPool object from the load() method\n     * @param sampleId  the sample ID of the sound loaded.\n     * @param status    the status of the load operation (0 = success)\n     */\n    @Override\n    public void onLoadComplete(final SoundPool soundPool, final int sampleId, final int status) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLoadComplete: \" + sampleId + \" ~ \" + status);\n        }\n\n        if (sampleId == beepStart) {\n            beepStartInitialised = true;\n        } else if (sampleId == beepStop) {\n            beepStopInitialised = true;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/pause/PauseDetector.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Copyright 2011-2016, Institute of Cybernetics at Tallinn University of Technology\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.audio.pause;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 15/02/2016.\n * <p/>\n * Class to detect when a user has stopped speaking adapted from the excellent library from the\n * author below.\n *\n * @author Kaarel Kaljurand\n */\npublic class PauseDetector {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = PauseDetector.class.getSimpleName();\n\n    private final int PAUSE_THRESHOLD = 7;\n    public static final long DEFAULT_PAUSE_IGNORE_TIME = 4000;\n    private final int MAX_RECORDING_LENGTH = 120;\n\n    private long pauseCheck;\n    private final long pauseIgnoreTime;\n\n    private final short RESOLUTION_IN_BYTES = 2;\n    private final int mOneSec;\n\n    // TODO: use: mRecording.length instead\n    // TODO: synchronisation and atomics\n    private volatile int mRecordedLength = 0;\n    private final PauseListener pauseListener;\n    private volatile boolean hasDetected;\n    private final byte[] mRecording;\n    private volatile double mAvgEnergy = 0;\n    private final int maxSize;\n\n    /**\n     * Constructor for the PauseDetector\n     *\n     * @param pauseListener  the listener connected to the recognition object\n     * @param sampleRateInHz the sampling rate in hertz\n     * @param nChannels      the number of channels\n     */\n    public PauseDetector(final PauseListener pauseListener, final int sampleRateInHz,\n                         final int nChannels, final long pauseIgnoreTime) {\n        this.pauseListener = pauseListener;\n        this.pauseIgnoreTime = pauseIgnoreTime;\n\n        mOneSec = RESOLUTION_IN_BYTES * nChannels * sampleRateInHz;\n        mRecording = new byte[mOneSec * MAX_RECORDING_LENGTH];\n        hasDetected = false;\n        maxSize = mRecording.length;\n    }\n\n    /**\n     * Start the pause detection\n     */\n    public void begin() {\n        pauseCheck = System.currentTimeMillis();\n    }\n\n    /**\n     * Add information from the buffer\n     *\n     * @param buffer           the audio buffer\n     * @param bufferReadResult the previous read result\n     */\n    public void addLength(final byte[] buffer, final int bufferReadResult) {\n\n        synchronized (this) {\n\n            if (!hasDetected) {\n                new Thread() {\n                    public void run() {\n\n                        mRecordedLength += buffer.length;\n\n                        if (mRecordedLength <= maxSize) {\n                            System.arraycopy(buffer, 0, mRecording, mRecordedLength, bufferReadResult);\n                        } else {\n                            hasDetected = true;\n                        }\n                    }\n                }.start();\n            }\n        }\n    }\n\n    /**\n     * Check if a pause has been detected\n     */\n    public boolean hasDetected() {\n        return hasDetected;\n    }\n\n    /**\n     * Monitor the audio data\n     */\n    public void monitor() {\n\n        synchronized (this) {\n\n            if (!hasDetected) {\n\n                new Thread() {\n                    public void run() {\n\n                        double pauseScore = getPauseScore();\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Pause score: \" + pauseScore);\n                        }\n\n                        if (System.currentTimeMillis() - pauseCheck < pauseIgnoreTime) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"PAUSE_IGNORE_TIME: too early\");\n                            }\n                        } else {\n                            if (pauseScore > PAUSE_THRESHOLD) {\n                                hasDetected = true;\n                                pauseListener.onPauseDetected();\n                            }\n                        }\n                    }\n                }.start();\n\n                if (DEBUG) {\n                    int running = 0;\n                    for (final Thread thread : Thread.getAllStackTraces().keySet()) {\n                        if (thread.getState() == Thread.State.RUNNABLE) {\n                            running++;\n                        }\n                    }\n\n                    MyLog.v(CLS_NAME, \"Threads running: \" + running);\n                }\n            }\n        }\n    }\n\n    /**\n     * In order to calculate if the user has stopped speaking we take the\n     * data from the last second of the recording, map it to a number\n     * and compare this number to the numbers obtained previously. We\n     * return a confidence score (0-INF) of a longer pause having occurred in the\n     * speech input.\n     * <p/>\n     * TODO: base the implementation on some well-known technique.\n     * TODO: behaves very differently depending on the {@link android.media.MediaRecorder.AudioSource}\n     *\n     * @return positive value which the caller can use to determine if there is a pause\n     */\n    private double getPauseScore() {\n\n        long t2 = getRms(mRecordedLength, mOneSec);\n\n        if (t2 == 0) {\n            return 0;\n        }\n\n        double t = mAvgEnergy / t2;\n        mAvgEnergy = (2 * mAvgEnergy + t2) / 3;\n\n        return t;\n    }\n\n    private long getRms(int end, int span) {\n        int begin = end - span;\n        if (begin < 0) {\n            begin = 0;\n        }\n\n        if (0 != (begin % 2)) {\n            begin++;\n        }\n\n        long sum = 0;\n        for (int i = begin; i < end; i += 2) {\n            short curSample = getShort(mRecording[i], mRecording[i + 1]);\n            sum += curSample * curSample;\n        }\n        return sum;\n    }\n\n    private short getShort(byte argB1, byte argB2) {\n        return (short) (argB1 | (argB2 << 8));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/audio/pause/PauseListener.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.audio.pause;\n\n/**\n * Created by benrandall76@gmail.com on 15/02/2016.\n */\npublic interface PauseListener {\n    void onPauseDetected();\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/broadcast/BRBoot.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.broadcast;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Broadcast Receiver for Boot Completed.\n * <p/>\n * Created by benrandall76@gmail.com on 25/03/2016.\n */\npublic class BRBoot extends BroadcastReceiver {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BRBoot.class.getSimpleName();\n\n    @Override\n    public void onReceive(final Context context, final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onReceive\");\n        }\n\n        if (SPH.getSelfAwareEnabled(context.getApplicationContext())\n                && SPH.getStartAtBoot(context.getApplicationContext())) {\n\n            final String action = getAction(intent);\n            if (UtilsString.notNaked(action) && action.equals(Intent.ACTION_BOOT_COMPLETED)\n                    || action.equals(\"android.intent.action.QUICKBOOT_POWERON\")\n                    || action.equals(\"com.htc.intent.action.QUICKBOOT_POWERON\")) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onReceive: starting service\");\n                }\n\n                SelfAwareHelper.startSelfAwareIfRequired(context.getApplicationContext());\n\n                if (SPH.getHotwordBoot(context.getApplicationContext())) {\n\n                    final LocalRequest request = new LocalRequest(context.getApplicationContext());\n                    request.prepareDefault(LocalRequest.ACTION_START_HOTWORD, null);\n                    request.execute();\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onReceive: start at boot hotword disabled by user\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onReceive: action naked or unknown\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onReceive: start at boot disabled by user\");\n            }\n        }\n    }\n\n    /**\n     * Get the Intent action, if there is one.\n     *\n     * @param intent received by the Broadcast\n     * @return the Intent Action or an empty string\n     */\n    private String getAction(final Intent intent) {\n        if (intent != null && intent.getAction() != null) {\n            return intent.getAction();\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"onReceive: unknown intent\");\n            }\n        }\n        return \"\";\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/broadcast/BRRemote.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.broadcast;\n\nimport android.app.Activity;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.support.annotation.NonNull;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.Set;\n\nimport ai.saiy.android.api.helper.BlackListHelper;\nimport ai.saiy.android.api.request.Regex;\nimport ai.saiy.android.api.request.SaiyKeyphrase;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.custom.CCC;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.Resolve;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to handle remote applications registering a keyphrase or hot word.\n * <p>\n * Started from {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)}\n * one of {@link Activity#RESULT_OK} or {@link Activity#RESULT_CANCELED} is returned to the\n * requesting application.\n * <p>\n * Created by benrandall76@gmail.com on 25/03/2016.\n */\npublic class BRRemote extends BroadcastReceiver {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BRRemote.class.getSimpleName();\n\n    private static final String SAIY_INTENT_RECEIVER = \"ai.saiy.android.SAIY_INTENT_RECEIVER\";\n\n    @Override\n    public void onReceive(final Context context, final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onReceive\");\n        }\n\n        if (intent == null) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \" onHandleIntent: Intent null\");\n            }\n            return;\n        }\n\n        final String action = intent.getAction();\n        if (DEBUG) {\n            examineIntent(intent);\n        }\n\n        if (!UtilsString.notNaked(action)) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \" onHandleIntent: action null\");\n            }\n            return;\n        }\n\n        if (!intent.getAction().equals(SaiyKeyphrase.SAIY_REQUEST_RECEIVER)) {\n            Log.e(\"Saiy Remote Request\", \"Incorrect ACTION: rejecting\");\n            return;\n        }\n\n        switch (intent.getIntExtra(SaiyKeyphrase.REQUEST_TYPE, 0)) {\n\n            case SaiyKeyphrase.REQUEST_KEYPHRASE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onHandleIntent: REQUEST_KEYPHRASE\");\n                }\n\n                final String keyphrase = intent.getStringExtra(SaiyKeyphrase.SAIY_KEYPHRASE);\n\n                if (UtilsString.notNaked(keyphrase)) {\n\n                    final String packageName = intent.getStringExtra(SaiyKeyphrase.REQUESTING_PACKAGE);\n\n                    if (UtilsString.notNaked(packageName)) {\n\n                        final BlackListHelper blackListHelper = new BlackListHelper();\n\n                        if (!blackListHelper.isBlacklisted(context, packageName)) {\n\n                            final Pair<Boolean, String> appPair = UtilsApplication.getAppNameFromPackage(\n                                    context.getApplicationContext(), packageName);\n\n                            if (appPair.first && UtilsString.notNaked(appPair.second)) {\n\n                                final Locale vrLocale = SPH.getVRLocale(context.getApplicationContext());\n                                final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(\n                                        vrLocale);\n\n                                final ArrayList<String> voiceData = new ArrayList<>(1);\n                                voiceData.add(keyphrase);\n\n                                final float[] confidence = new float[1];\n                                confidence[0] = 1f;\n\n                                final ArrayList<Pair<CC, Float>> resolvePair = new Resolve(\n                                        context.getApplicationContext(), voiceData, confidence, sl).resolve();\n\n                                if (!UtilsList.notNaked(resolvePair)) {\n\n                                    final CustomCommand customCommand = new CustomCommand(CCC.CUSTOM_INTENT_SERVICE,\n                                            CC.COMMAND_USER_CUSTOM, keyphrase, SaiyRequestParams.SILENCE,\n                                            SaiyRequestParams.SILENCE,\n                                            SPH.getTTSLocale(context.getApplicationContext()).toString(),\n                                            vrLocale.toString(), LocalRequest.ACTION_SPEAK_ONLY);\n\n                                    final Regex regex = (Regex) intent.getSerializableExtra(\n                                            SaiyKeyphrase.KEYPHRASE_REGEX);\n\n                                    switch (regex) {\n\n                                        case MATCHES:\n                                        case STARTS_WITH:\n                                        case ENDS_WITH:\n                                        case CONTAINS:\n                                            customCommand.setRegex(regex);\n                                            break;\n                                        case CUSTOM:\n                                            customCommand.setRegex(regex);\n                                            customCommand.setRegularExpression(\n                                                    intent.getStringExtra(SaiyKeyphrase.REGEX_CONTENT));\n                                            break;\n                                    }\n\n                                    final Intent remoteIntent = new Intent(SAIY_INTENT_RECEIVER);\n                                    remoteIntent.setPackage(packageName);\n\n                                    final Bundle bundle = intent.getExtras();\n\n                                    if (UtilsBundle.notNaked(bundle)) {\n                                        if (!UtilsBundle.isSuspicious(bundle)) {\n                                            if (DEBUG) {\n                                                examineIntent(intent);\n                                            }\n\n                                            remoteIntent.putExtras(bundle);\n                                            customCommand.setIntent(remoteIntent.toUri(0));\n\n                                            final Pair<Boolean, Long> successPair = CustomCommandHelper.setCommand(\n                                                    context.getApplicationContext(), customCommand, -1);\n\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"Custom command created: \" + successPair.first);\n                                            }\n\n                                            final Bundle responseBundle = new Bundle();\n                                            final int responseCode = bundle.getInt(SaiyKeyphrase.SAIY_KEYPHRASE_ID, 0);\n\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"Custom command responseCode: \" + responseCode);\n                                            }\n\n                                            responseBundle.putInt(SaiyKeyphrase.SAIY_KEYPHRASE_ID, responseCode);\n\n                                            final LocalRequest request = new LocalRequest(context.getApplicationContext());\n                                            request.setVRLocale(vrLocale);\n                                            request.setTTSLocale(SPH.getTTSLocale(context.getApplicationContext()));\n                                            request.setSupportedLanguage(sl);\n                                            request.setQueueType(SaiyTextToSpeech.QUEUE_ADD);\n                                            request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n\n                                            if (successPair.first) {\n\n                                                request.setUtterance(PersonalityResponse.getRemoteCommandRegisterSuccess(\n                                                        context.getApplicationContext(), sl, appPair.second, keyphrase));\n                                                request.execute();\n\n                                                setResult(Activity.RESULT_OK, SaiyKeyphrase.class.getSimpleName(),\n                                                        responseBundle);\n                                            } else {\n\n                                                request.setUtterance(PersonalityResponse.getErrorRemoteCommandRegister(\n                                                        context.getApplicationContext(), sl, appPair.second));\n                                                request.execute();\n\n                                                setResult(Activity.RESULT_CANCELED, SaiyKeyphrase.class.getSimpleName(),\n                                                        responseBundle);\n                                            }\n\n                                        } else {\n                                            Log.e(\"Saiy Remote Request\", \"Bundle rejected due to contents\");\n                                        }\n                                    } else {\n                                        Log.e(\"Saiy Remote Request\", \"Request bundle missing contents: rejected\");\n                                    }\n                                } else {\n                                    Log.e(\"Saiy Remote Request\", \"Conflict with inbuilt command: rejected\");\n                                }\n                            } else {\n                                Log.e(\"Saiy Remote Request\", \"Application name undetectable: rejected\");\n                            }\n                        } else {\n                            Log.e(\"Saiy Remote Request\", \"Application blacklisted: rejected\");\n                        }\n                    } else {\n                        Log.e(\"Saiy Remote Request\", \"Package name missing: rejected\");\n                    }\n                } else {\n                    Log.e(\"Saiy Remote Request\", \"Keyphrase missing: rejected\");\n                }\n\n                break;\n            default:\n                Log.e(\"Saiy Remote Request\", \"Internal type error: rejected\");\n                break;\n        }\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param intent containing potential extras\n     */\n    private void examineIntent(@NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineIntent\");\n        }\n\n        final Bundle bundle = intent.getExtras();\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            for (final String key : keys) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"examineIntent: \" + key + \" ~ \" + bundle.get(key));\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cache/speech/IAudioCompression.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cache.speech;\n\n/**\n * Interface for audio compression callbacks\n * <p>\n * Created by benrandall76@gmail.com on 28/04/2016.\n */\npublic interface IAudioCompression {\n\n    void onCompressionCompleted(final byte[] compressedAudio);\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cache/speech/SpeechCachePrepare.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cache.speech;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Process;\nimport android.speech.tts.Voice;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.audio.AudioCompression;\nimport ai.saiy.android.database.DBSpeech;\n\n/**\n * Class to prepare an entry into {@link DBSpeech}. The method {@link #setUncompressedAudio(byte[])}\n * is passed uncompressed audio data, which is subsequently compressed with a callback of completion\n * coming from the implemented {@link IAudioCompression} interface.\n * <p/>\n * Created by benrandall76@gmail.com on 27/04/2016.\n */\npublic class SpeechCachePrepare implements IAudioCompression {\n\n    private final Context mContext;\n    private String engine;\n    private String utterance;\n    private String locale;\n    private Voice voice;\n    private volatile byte[] compressedAudio;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public SpeechCachePrepare(@NonNull final Context mContext) {\n        this.mContext = mContext;\n    }\n\n    /**\n     * Set the uncompressed audio\n     *\n     * @param uncompressedAudio byte[]\n     */\n    public void setUncompressedAudio(@NonNull final byte[] uncompressedAudio) {\n        new Thread() {\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);\n                AudioCompression.compressBytes(SpeechCachePrepare.this, uncompressedAudio);\n            }\n        }.start();\n    }\n\n    public byte[] getCompressedAudio() {\n        return compressedAudio;\n    }\n\n    public String getEngine() {\n        return engine;\n    }\n\n    public void setEngine(@NonNull final String engine) {\n        this.engine = engine;\n    }\n\n    public String getLocale() {\n        return locale;\n    }\n\n    public void setLocale(@NonNull final String locale) {\n        this.locale = locale;\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    public Voice getVoice() {\n        return voice;\n    }\n\n    public void setVoice(@NonNull final Voice voice) {\n        this.voice = voice;\n    }\n\n    @Override\n    public void onCompressionCompleted(final byte[] compressedAudio) {\n        this.compressedAudio = compressedAudio;\n        executeInsert();\n    }\n\n    /**\n     * Execute the insertion of the audio data into {@link DBSpeech}\n     */\n    private void executeInsert() {\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);\n                final DBSpeech dbSpeech = new DBSpeech(mContext);\n                dbSpeech.insertRow(SpeechCachePrepare.this);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cache/speech/SpeechCacheResult.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cache.speech;\n\n/**\n * Helper class to store the results of the speech cache\n * <p>\n * Created by benrandall76@gmail.com on 27/04/2016.\n */\npublic class SpeechCacheResult {\n\n    private final byte[] compressedBytes;\n    private final long rowId;\n    private final boolean success;\n\n    public SpeechCacheResult(final byte[] compressedBytes, final long rowId, final boolean success) {\n        this.compressedBytes = compressedBytes;\n        this.rowId = rowId;\n        this.success = success;\n    }\n\n    public byte[] getCompressedBytes() {\n        return compressedBytes;\n    }\n\n    public long getRowId() {\n        return rowId;\n    }\n\n    public boolean isSuccess() {\n        return success;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/AnalysisResult.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 14/08/2016.\n */\n\npublic class AnalysisResult {\n\n    private long analysisTime;\n    private String recordingId;\n    private String description;\n\n    public AnalysisResult() {\n    }\n\n    public AnalysisResult(@NonNull final String recordingId, final long analysisTime, @NonNull final String description) {\n        this.recordingId = recordingId;\n        this.analysisTime = analysisTime;\n        this.description = description;\n    }\n\n    public String getRecordingId() {\n        return recordingId;\n    }\n\n    public void setRecordingId(final String recordingId) {\n        this.recordingId = recordingId;\n    }\n\n    public long getAnalysisTime() {\n        return analysisTime;\n    }\n\n    public void setAnalysisTime(final long analysisTime) {\n        this.analysisTime = analysisTime;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setDescription(final String description) {\n        this.description = description;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/AnalysisResultHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.List;\nimport java.util.Random;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Analysis;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.AnalysisSummary;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Arousal;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.AudioQuality;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Composite;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Emotions;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Gender;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Group11;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Group21;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Group7;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Mood;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Primary;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Result;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Secondary;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Segment;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Summary;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Temper;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Valence;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 14/08/2016.\n */\n\npublic class AnalysisResultHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = AnalysisResultHelper.class.getSimpleName();\n\n    private final Context mContext;\n    private final SupportedLanguage sl;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage} object\n     */\n    public AnalysisResultHelper(@NonNull final Context mContext, @NonNull final SupportedLanguage sl) {\n        this.mContext = mContext;\n        this.sl = sl;\n    }\n\n    /**\n     * Analyse the emotion response attempting to construct a verbose explanation of the interpretation to announce to the\n     * user.\n     *\n     * @param emotions the {@link Emotions} response object\n     */\n    public void interpretAndStore(@Nullable final Emotions emotions) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"interpretAndStore\");\n        }\n\n        if (emotions != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"interpretAndStore: getRecordingId: \" + emotions.getRecordingId());\n                MyLog.i(CLS_NAME, \"interpretAndStore: getStatus: \" + emotions.getStatus());\n            }\n\n            if (emotions.getStatus().matches(Emotions.SUCCESS)) {\n\n                final Result result = emotions.getResult();\n\n                if (result != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"result: getDuration: \" + result.getDuration());\n                        MyLog.i(CLS_NAME, \"result: getSessionStatus: \" + result.getSessionStatus());\n                        verboseEmotions(result);\n                    }\n\n                    final AnalysisResult resultHolder = new AnalysisResult();\n                    resultHolder.setAnalysisTime(System.currentTimeMillis());\n                    resultHolder.setRecordingId(emotions.getRecordingId());\n                    resultHolder.setDescription(constructResponse(result));\n\n                    if (UtilsString.notNaked(resultHolder.getDescription())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"interpretAndStore: saving emotion analysis\");\n                        }\n                        SPH.setEmotion(mContext, new GsonBuilder().disableHtmlEscaping().create().toJson(resultHolder));\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"interpretAndStore: resultHolder getDescription naked\");\n                        }\n                        SPH.setEmotion(mContext, null);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"interpretAndStore: result null\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"interpretAndStore: status failure\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"interpretAndStore: emotions null\");\n            }\n        }\n\n        NotificationHelper.createEmotionAnalysisNotification(mContext);\n    }\n\n    private String getIntro(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_intro);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getStartDesc(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_start_desc);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getConnector(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_connector);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getGap(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_gap);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getConnectorTwo(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_connector_two);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getValenceIntro(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_valence_intro);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getConnectorThree(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_temper_connector_three);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getMoodConnector(@NonNull final SaiyResources sr) {\n        final String[] stringArray = sr.getStringArray(R.array.array_beyond_verbal_moods_connector);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getModeOne(final String[] stringArray) {\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Helper method to avoid duplication of a modal response\n     *\n     * @param stringArray the array of possible responses\n     * @return a random mode response\n     */\n    private String getModeTwo(@NonNull final String[] stringArray) {\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getModeThree(final String[] stringArray) {\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    private String getValenceLevel(@NonNull final SaiyResources sr, @NonNull final String valenceMode) {\n\n        switch (valenceMode) {\n\n            case Valence.NEGATIVE:\n                return sr.getString(R.string.negative);\n            case Valence.NEUTRAL:\n                return sr.getString(R.string.neutral);\n            case Valence.POSITIVE:\n                return sr.getString(R.string.positive);\n            default:\n                return sr.getString(R.string.neutral);\n        }\n    }\n\n    private String getArousalLevel(@NonNull final SaiyResources sr, @NonNull final String arousalMode) {\n\n        switch (arousalMode) {\n\n            case Analysis.LOW:\n                return sr.getString(R.string.low);\n            case Analysis.MED:\n                return sr.getString(R.string.medium);\n            case Analysis.HIGH:\n                return sr.getString(R.string.high);\n            default:\n                return sr.getString(R.string.medium);\n        }\n    }\n\n    /**\n     * Method to construct a verbose interpretation of the emotion analysis.\n     *\n     * @param result the emotion {@link Result} object\n     * @return a constructed String that can be announced to the user.\n     */\n    public String constructResponse(@Nullable final Result result) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"constructResponse\");\n        }\n\n        String response = null;\n\n        if (result == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: result null\");\n            }\n            return response;\n        }\n\n        final AnalysisSummary analysisSummary = result.getAnalysisSummary();\n\n        if (analysisSummary == null) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"interpretAndStore: analysisSummary null\");\n            }\n            return response;\n        }\n\n        final Analysis analysisResult = analysisSummary.getAnalysisResult();\n\n        if (analysisResult == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: analysisResult null\");\n            }\n            return response;\n        }\n\n        final Temper temper = analysisResult.getTemper();\n\n        if (temper == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: temper null\");\n            }\n            return response;\n        }\n\n        final String temperMode = temper.getMode();\n\n        if (!UtilsString.notNaked(temperMode)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: temperMode null\");\n            }\n        }\n\n        final SaiyResources sr = new SaiyResources(mContext, sl);\n\n        String mode;\n        String modeOne;\n        String modeTwo;\n        String modeThree;\n        String[] stringArray;\n\n        final String intro = getIntro(sr);\n        final String startDesc = getStartDesc(sr);\n        final String connector = getConnector(sr);\n        final String gap = getGap(sr);\n        final String connectorTwo = getConnectorTwo(sr);\n        final String valenceIntro = getValenceIntro(sr);\n        final String connectorThree = getConnectorThree(sr);\n        final String moodConnector = getMoodConnector(sr);\n\n        final String arousalIntro = sr.getString(R.string.beyond_verbal_arousal_intro);\n\n        String valenceLevel = null;\n        String arousalLevel = null;\n\n        String compositePrimaryPhrase = null;\n        String compositeSecondaryPhrase = null;\n        String group7PrimaryPhrase = null;\n        String group7SecondaryPhrase = null;\n        String group11PrimaryPhrase = null;\n        String group11SecondaryPhrase = null;\n        String group21PrimaryPhrase = null;\n        String group21SecondaryPhrase = null;\n\n        switch (temperMode) {\n\n            case Analysis.LOW:\n                mode = sr.getString(R.string.low);\n                stringArray = sr.getStringArray(R.array.array_beyond_verbal_synonyms_low_first);\n                modeOne = getModeOne(stringArray);\n                modeTwo = getModeTwo(stringArray);\n\n                while (modeTwo.matches(modeOne)) {\n                    modeTwo = getModeTwo(stringArray);\n                }\n\n                modeThree = getModeThree(sr.getStringArray(R.array.array_beyond_verbal_synonyms_low_second));\n\n                break;\n            case Analysis.MED:\n                mode = sr.getString(R.string.medium);\n                stringArray = sr.getStringArray(R.array.array_beyond_verbal_synonyms_medium_first);\n                modeOne = getModeOne(stringArray);\n                modeTwo = getModeTwo(stringArray);\n\n                while (modeTwo.matches(modeOne)) {\n                    modeTwo = getModeTwo(stringArray);\n                }\n\n                modeThree = getModeThree(sr.getStringArray(R.array.array_beyond_verbal_synonyms_medium_second));\n\n                break;\n            case Analysis.HIGH:\n                mode = sr.getString(R.string.high);\n                stringArray = sr.getStringArray(R.array.array_beyond_verbal_synonyms_high_first);\n                modeOne = getModeOne(stringArray);\n                modeTwo = getModeTwo(stringArray);\n\n                while (modeTwo.matches(modeOne)) {\n                    modeTwo = getModeTwo(stringArray);\n                }\n\n                modeThree = getModeThree(sr.getStringArray(R.array.array_beyond_verbal_synonyms_high_second));\n\n                break;\n            default:\n                mode = sr.getString(R.string.low);\n                stringArray = sr.getStringArray(R.array.array_beyond_verbal_synonyms_low_first);\n                modeOne = getModeOne(stringArray);\n                modeTwo = getModeTwo(stringArray);\n\n                while (modeTwo.matches(modeOne)) {\n                    modeTwo = getModeTwo(stringArray);\n                }\n\n                modeThree = getModeThree(sr.getStringArray(R.array.array_beyond_verbal_synonyms_low_second));\n\n                break;\n        }\n\n        final Valence valence = analysisResult.getValence();\n\n        if (valence != null) {\n\n            final String valenceMode = valence.getMode();\n\n            if (UtilsString.notNaked(valenceMode)) {\n                valenceLevel = getValenceLevel(sr, valenceMode);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"constructResponse: valenceMode naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: valence null\");\n            }\n        }\n\n        final Arousal arousal = analysisResult.getArousal();\n\n        if (arousal != null) {\n\n            final String arousalMode = arousal.getMode();\n\n            if (UtilsString.notNaked(arousalMode)) {\n                arousalLevel = getArousalLevel(sr, arousalMode);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"constructResponse: arousalMode naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"constructResponse: arousal null\");\n            }\n        }\n\n        final List<Segment> segments = result.getSegments();\n\n        if (UtilsList.notNaked(segments)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: segments count: \" + segments.size());\n            }\n\n            for (final Segment segment : segments) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"constructResponse: segment: getDuration: \" + segment.getDuration());\n                    MyLog.i(CLS_NAME, \"constructResponse: segment: getOffset: \" + segment.getOffset());\n                }\n\n                final Analysis analysis = segment.getAnalysis();\n\n                if (analysis != null) {\n\n                    final Mood mood = analysis.getMood();\n\n                    if (mood != null) {\n\n                        final Composite composite = mood.getComposite();\n                        if (composite != null) {\n                            final Primary compositePrimary = composite.getPrimary();\n                            if (compositePrimary != null) {\n\n                                compositePrimaryPhrase = compositePrimary.getPhrase();\n\n                                if (!UtilsString.notNaked(compositePrimaryPhrase)) {\n                                    compositePrimaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood compositePrimary null\");\n                                }\n                            }\n\n                            final Secondary compositeSecondary = composite.getSecondary();\n                            if (compositeSecondary != null) {\n\n                                compositeSecondaryPhrase = compositeSecondary.getPhrase();\n\n                                if (!UtilsString.notNaked(compositeSecondaryPhrase)) {\n                                    compositeSecondaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood compositeSecondary null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"segment: mood composite null\");\n                            }\n                        }\n\n                        final Group7 group7 = mood.getGroup7();\n\n                        if (group7 != null) {\n\n                            final Primary group7Primary = group7.getPrimary();\n\n                            if (group7Primary != null) {\n\n                                group7PrimaryPhrase = group7Primary.getPhrase();\n\n                                if (!UtilsString.notNaked(group7PrimaryPhrase)) {\n                                    group7PrimaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group7Primary null\");\n                                }\n                            }\n\n                            final Secondary group7Secondary = group7.getSecondary();\n\n                            if (group7Secondary != null) {\n\n                                group7SecondaryPhrase = group7Secondary.getPhrase();\n\n                                if (!UtilsString.notNaked(group7SecondaryPhrase)) {\n                                    group7SecondaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group7Secondary null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"segment: mood group7 null\");\n                            }\n                        }\n\n                        final Group11 group11 = mood.getGroup11();\n\n                        if (group11 != null) {\n\n                            final Primary group11Primary = group11.getPrimary();\n\n                            if (group11Primary != null) {\n\n                                group11PrimaryPhrase = group11Primary.getPhrase();\n\n                                if (!UtilsString.notNaked(group11PrimaryPhrase)) {\n                                    group11PrimaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group11Primary null\");\n                                }\n                            }\n\n                            final Secondary group11Secondary = group11.getSecondary();\n\n                            if (group11Secondary != null) {\n\n                                group11SecondaryPhrase = group11Secondary.getPhrase();\n\n                                if (!UtilsString.notNaked(group11SecondaryPhrase)) {\n                                    group11SecondaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group11Secondary null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"segment: mood group11 null\");\n                            }\n                        }\n\n                        final Group21 group21 = mood.getGroup21();\n\n                        if (group21 != null) {\n\n                            final Primary group21Primary = group21.getPrimary();\n\n                            if (group21Primary != null) {\n\n                                group21PrimaryPhrase = group21Primary.getPhrase();\n\n                                if (!UtilsString.notNaked(group21PrimaryPhrase)) {\n                                    group21PrimaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group21Primary null\");\n                                }\n                            }\n\n                            final Secondary group21Secondary = group21.getSecondary();\n\n                            if (group21Secondary != null) {\n\n                                group21SecondaryPhrase = group21Secondary.getPhrase();\n\n                                if (!UtilsString.notNaked(group21SecondaryPhrase)) {\n                                    group21SecondaryPhrase = \"\";\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood group21Secondary null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"segment: mood group21 null\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"segment: mood null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"constructResponse: analysis null\");\n                    }\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"constructResponse: segments naked\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"constructResponse: intro: \" + intro);\n            MyLog.v(CLS_NAME, \"constructResponse: mode: \" + mode);\n            MyLog.v(CLS_NAME, \"constructResponse: startDesc: \" + startDesc);\n            MyLog.v(CLS_NAME, \"constructResponse: connector: \" + connector);\n            MyLog.v(CLS_NAME, \"constructResponse: modeOne: \" + modeOne);\n            MyLog.v(CLS_NAME, \"constructResponse: gap: \" + gap);\n            MyLog.v(CLS_NAME, \"constructResponse: modeTwo: \" + modeTwo);\n            MyLog.v(CLS_NAME, \"constructResponse: connectorTwo: \" + connectorTwo);\n            MyLog.v(CLS_NAME, \"constructResponse: modeThree: \" + modeThree);\n            MyLog.v(CLS_NAME, \"constructResponse: arousalIntro: \" + arousalIntro);\n            MyLog.v(CLS_NAME, \"constructResponse: arousalLevel: \" + arousalLevel);\n            MyLog.v(CLS_NAME, \"constructResponse: valenceIntro: \" + valenceIntro);\n            MyLog.v(CLS_NAME, \"constructResponse: valenceLevel: \" + valenceLevel);\n            MyLog.v(CLS_NAME, \"constructResponse: connectorThree: \" + connectorThree);\n            MyLog.v(CLS_NAME, \"constructResponse: compositePrimaryPhrase: \" + compositePrimaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: compositeSecondaryPhrase: \" + compositeSecondaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group7PrimaryPhrase: \" + group7PrimaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group7SecondaryPhrase: \" + group7SecondaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group11PrimaryPhrase: \" + group11PrimaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group11SecondaryPhrase: \" + group11SecondaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group21PrimaryPhrase: \" + group21PrimaryPhrase);\n            MyLog.v(CLS_NAME, \"constructResponse: group21SecondaryPhrase: \" + group21SecondaryPhrase);\n        }\n\n        final StringBuilder builder = new StringBuilder();\n        builder.append(intro);\n        builder.append(\" \");\n        builder.append(mode);\n        builder.append(\", \");\n        builder.append(startDesc);\n        builder.append(\" \");\n        builder.append(connector);\n        builder.append(\" \");\n        builder.append(modeOne);\n        builder.append(\", \");\n        builder.append(gap);\n        builder.append(\" \");\n        builder.append(modeTwo);\n        builder.append(\", \");\n        builder.append(connectorTwo);\n        builder.append(\" \");\n        builder.append(modeThree);\n        builder.append(\". \");\n\n        if (UtilsString.notNaked(arousalLevel)) {\n            builder.append(arousalIntro);\n            builder.append(\", \");\n            builder.append(arousalLevel);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(valenceLevel)) {\n            builder.append(valenceIntro);\n            builder.append(\", \");\n            builder.append(valenceLevel);\n            builder.append(\". \");\n        }\n\n        builder.append(connectorThree);\n        builder.append(\", \");\n\n        if (UtilsString.notNaked(compositePrimaryPhrase)) {\n            builder.append(compositePrimaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(compositeSecondaryPhrase)) {\n            builder.append(compositeSecondaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(group7PrimaryPhrase)) {\n            builder.append(group7PrimaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(group7SecondaryPhrase)) {\n            builder.append(group7SecondaryPhrase);\n            builder.append(\". \");\n        }\n\n        builder.append(moodConnector);\n        builder.append(\", \");\n\n        if (UtilsString.notNaked(group11PrimaryPhrase)) {\n            builder.append(group11PrimaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(group11SecondaryPhrase)) {\n            builder.append(group11SecondaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(group21PrimaryPhrase)) {\n            builder.append(group21PrimaryPhrase);\n            builder.append(\". \");\n        }\n\n        if (UtilsString.notNaked(group21SecondaryPhrase)) {\n            builder.append(group21SecondaryPhrase);\n            builder.append(\". \");\n        }\n\n        response = builder.toString().replaceAll(\"\\\\.+\", \".\").trim();\n\n        if (response.endsWith(\".\")) {\n            response = UtilsString.replaceLast(StringUtils.removeEnd(response, \".\"), \".\",\n                    sr.getString(R.string._and));\n        } else {\n            response = UtilsString.replaceLast(response, \".\", sr.getString(R.string._and));\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"constructResponse: response: \" + response);\n        }\n\n        sr.reset();\n\n        return response;\n\n    }\n\n    /**\n     * Verbose emotion analysis information for debugging only\n     *\n     * @param result the {@link Result} object\n     */\n    private void verboseEmotions(@NonNull final Result result) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"verboseEmotions\");\n            MyLog.i(CLS_NAME, \"result: getDuration: \" + result.getDuration());\n            MyLog.i(CLS_NAME, \"result: getSessionStatus: \" + result.getSessionStatus());\n        }\n\n        Temper temper;\n        Valence valence;\n        Gender gender;\n        Arousal arousal;\n        AudioQuality audioQuality;\n\n        final AnalysisSummary analysisSummary = result.getAnalysisSummary();\n\n        if (analysisSummary != null) {\n\n            final Analysis analysisResult = analysisSummary.getAnalysisResult();\n\n            if (analysisResult != null) {\n\n                temper = analysisResult.getTemper();\n\n                if (temper != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"analysisResult: temper: getGroup: \" + temper.getGroup());\n                        MyLog.i(CLS_NAME, \"analysisResult: temper: getValue: \" + temper.getValue());\n                        MyLog.i(CLS_NAME, \"analysisResult: temper: getMode: \" + temper.getMode());\n                        MyLog.i(CLS_NAME, \"analysisResult: temper: getMean: \" + temper.getMean());\n                        MyLog.i(CLS_NAME, \"analysisResult: temper: getScore: \" + temper.getScore());\n                    }\n\n                    final Summary temperSummary = temper.getSummary();\n\n                    if (temperSummary != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"analysisResult: temperSummary: getMode: \" + temperSummary.getMode());\n                            MyLog.i(CLS_NAME, \"analysisResult: temperSummary: getMean: \" + temperSummary.getMean());\n                            MyLog.i(CLS_NAME, \"analysisResult: temperSummary: getModePct: \" + temperSummary.getModePct());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"analysisResult: temperSummary null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"analysisResult: temper null\");\n                    }\n                }\n\n                valence = analysisResult.getValence();\n\n                if (valence != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"analysisResult: valence: getGroup: \" + valence.getGroup());\n                        MyLog.i(CLS_NAME, \"analysisResult: valence: getValue: \" + valence.getValue());\n                        MyLog.i(CLS_NAME, \"analysisResult: valence: getMode: \" + valence.getMode());\n                        MyLog.i(CLS_NAME, \"analysisResult: valence: getMean: \" + valence.getMean());\n                        MyLog.i(CLS_NAME, \"analysisResult: valence: getScore: \" + valence.getScore());\n                    }\n\n                    final Summary valenceSummary = valence.getSummary();\n\n                    if (valenceSummary != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"analysisResult: valenceSummary: getMode: \" + valenceSummary.getMode());\n                            MyLog.i(CLS_NAME, \"analysisResult: valenceSummary: getMean: \" + valenceSummary.getMean());\n                            MyLog.i(CLS_NAME, \"analysisResult: valenceSummary: getModePct: \" + valenceSummary.getModePct());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"analysisResult: valenceSummary null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"analysisResult: valence null\");\n                    }\n                }\n\n                gender = analysisResult.getGender();\n\n                if (gender != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"analysisResult: gender: getGroup: \" + gender.getGroup());\n                        MyLog.i(CLS_NAME, \"analysisResult: gender: getValue: \" + gender.getValue());\n                        MyLog.i(CLS_NAME, \"analysisResult: gender: getMode: \" + gender.getMode());\n                        MyLog.i(CLS_NAME, \"analysisResult: gender: getMean: \" + gender.getMean());\n                    }\n\n                    final Summary genderSummary = gender.getSummary();\n\n                    if (genderSummary != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"analysisResult: genderSummary: getMode: \" + genderSummary.getMode());\n                            MyLog.i(CLS_NAME, \"analysisResult: genderSummary: getMean: \" + genderSummary.getMean());\n                            MyLog.i(CLS_NAME, \"analysisResult: genderSummary: getModePct: \" + genderSummary.getModePct());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"analysisResult: genderSummary null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"analysisResult: gender null\");\n                    }\n                }\n\n                arousal = analysisResult.getArousal();\n\n                if (arousal != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"analysisResult: arousal: getGroup: \" + arousal.getGroup());\n                        MyLog.i(CLS_NAME, \"analysisResult: arousal: getValue: \" + arousal.getValue());\n                        MyLog.i(CLS_NAME, \"analysisResult: arousal: getMode: \" + arousal.getMode());\n                        MyLog.i(CLS_NAME, \"analysisResult: arousal: getMean: \" + arousal.getMean());\n                        MyLog.i(CLS_NAME, \"analysisResult: arousal: getScore: \" + arousal.getScore());\n                    }\n\n                    final Summary arousalSummary = arousal.getSummary();\n\n                    if (arousalSummary != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"analysisResult: arousalSummary: getMode: \" + arousalSummary.getMode());\n                            MyLog.i(CLS_NAME, \"analysisResult: arousalSummary: getMean: \" + arousalSummary.getMean());\n                            MyLog.i(CLS_NAME, \"analysisResult: arousalSummary: getModePct: \" + arousalSummary.getModePct());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"analysisResult: arousalSummary null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"analysisResult: arousal null\");\n                    }\n                }\n\n                audioQuality = analysisResult.getAudioQuality();\n\n                if (audioQuality != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"analysisResult: audioQuality: getGroup: \" + audioQuality.getGroup());\n                        MyLog.i(CLS_NAME, \"analysisResult: audioQuality: getValue: \" + audioQuality.getValue());\n                        MyLog.i(CLS_NAME, \"analysisResult: audioQuality: getMode: \" + audioQuality.getMode());\n                        MyLog.i(CLS_NAME, \"analysisResult: audioQuality: getMean: \" + audioQuality.getMean());\n                    }\n\n                    final Summary audioQualitySummary = audioQuality.getSummary();\n\n                    if (audioQualitySummary != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"analysisResult: audioQualitySummary: getMode: \" + audioQualitySummary.getMode());\n                            MyLog.i(CLS_NAME, \"analysisResult: audioQualitySummary: getMean: \" + audioQualitySummary.getMean());\n                            MyLog.i(CLS_NAME, \"analysisResult: audioQualitySummary: getModePct: \" + audioQualitySummary.getModePct());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"analysisResult: audioQualitySummary null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"analysisResult: audioQuality null\");\n                    }\n                }\n\n                final List<Segment> segments = result.getSegments();\n\n                if (UtilsList.notNaked(segments)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"segments count: \" + segments.size());\n                    }\n\n                    for (final Segment segment : segments) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"segment: getDuration: \" + segment.getDuration());\n                            MyLog.i(CLS_NAME, \"segment: getOffset: \" + segment.getOffset());\n                        }\n\n                        final Analysis analysis = segment.getAnalysis();\n\n                        if (analysis != null) {\n\n                            temper = analysis.getTemper();\n\n                            if (temper != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"analysis: temper: getGroup: \" + temper.getGroup());\n                                    MyLog.i(CLS_NAME, \"analysis: temper: getValue: \" + temper.getValue());\n                                    MyLog.i(CLS_NAME, \"analysis: temper: getMode: \" + temper.getMode());\n                                    MyLog.i(CLS_NAME, \"analysis: temper: getMean: \" + temper.getMean());\n                                    MyLog.i(CLS_NAME, \"analysis: temper: getScore: \" + temper.getScore());\n                                }\n\n                                final Summary temperSummary = temper.getSummary();\n\n                                if (temperSummary != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"analysis: temperSummary: getMode: \" + temperSummary.getMode());\n                                        MyLog.i(CLS_NAME, \"analysis: temperSummary: getMean: \" + temperSummary.getMean());\n                                        MyLog.i(CLS_NAME, \"analysis: temperSummary: getModePct: \" + temperSummary.getModePct());\n                                    }\n\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"analysis: temperSummary null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"analysis: temper null\");\n                                }\n                            }\n\n                            valence = analysis.getValence();\n\n                            if (valence != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"analysis: valence: getGroup: \" + valence.getGroup());\n                                    MyLog.i(CLS_NAME, \"analysis: valence: getValue: \" + valence.getValue());\n                                    MyLog.i(CLS_NAME, \"analysis: valence: getMode: \" + valence.getMode());\n                                    MyLog.i(CLS_NAME, \"analysis: valence: getMean: \" + valence.getMean());\n                                    MyLog.i(CLS_NAME, \"analysis: valence: getScore: \" + valence.getScore());\n                                }\n\n                                final Summary valenceSummary = valence.getSummary();\n\n                                if (valenceSummary != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"analysis: valenceSummary: getMode: \" + valenceSummary.getMode());\n                                        MyLog.i(CLS_NAME, \"analysis: valenceSummary: getMean: \" + valenceSummary.getMean());\n                                        MyLog.i(CLS_NAME, \"analysis: valenceSummary: getModePct: \" + valenceSummary.getModePct());\n                                    }\n\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"analysis: valenceSummary null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"analysis: valence null\");\n                                }\n                            }\n\n                            gender = analysis.getGender();\n\n                            if (gender != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"analysis: gender: getGroup: \" + gender.getGroup());\n                                    MyLog.i(CLS_NAME, \"analysis: gender: getValue: \" + gender.getValue());\n                                    MyLog.i(CLS_NAME, \"analysis: gender: getMode: \" + gender.getMode());\n                                    MyLog.i(CLS_NAME, \"analysis: gender: getMean: \" + gender.getMean());\n                                }\n\n                                final Summary genderSummary = gender.getSummary();\n\n                                if (genderSummary != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"analysis: genderSummary: getMode: \" + genderSummary.getMode());\n                                        MyLog.i(CLS_NAME, \"analysis: genderSummary: getMean: \" + genderSummary.getMean());\n                                        MyLog.i(CLS_NAME, \"analysis:genderSummary: getModePct: \" + genderSummary.getModePct());\n                                    }\n\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"analysis: genderSummary null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"analysis: gender null\");\n                                }\n                            }\n\n                            arousal = analysis.getArousal();\n\n                            if (arousal != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"analysis: arousal: getGroup: \" + arousal.getGroup());\n                                    MyLog.i(CLS_NAME, \"analysis: arousal: getValue: \" + arousal.getValue());\n                                    MyLog.i(CLS_NAME, \"analysis: arousal: getMode: \" + arousal.getMode());\n                                    MyLog.i(CLS_NAME, \"analysis: arousal: getMean: \" + arousal.getMean());\n                                    MyLog.i(CLS_NAME, \"analysis: arousal: getScore: \" + arousal.getScore());\n                                }\n\n                                final Summary arousalSummary = arousal.getSummary();\n\n                                if (arousalSummary != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"analysis: arousalSummary: getMode: \" + arousalSummary.getMode());\n                                        MyLog.i(CLS_NAME, \"analysis: arousalSummary: getMean: \" + arousalSummary.getMean());\n                                        MyLog.i(CLS_NAME, \"analysis: arousalSummary: getModePct: \" + arousalSummary.getModePct());\n                                    }\n\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"analysis: arousalSummary null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"analysis: arousal null\");\n                                }\n                            }\n\n                            audioQuality = analysis.getAudioQuality();\n\n                            if (audioQuality != null) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"analysis: audioQuality: getGroup: \" + audioQuality.getGroup());\n                                    MyLog.i(CLS_NAME, \"analysis: audioQuality: getValue: \" + audioQuality.getValue());\n                                    MyLog.i(CLS_NAME, \"analysis: audioQuality: getMode: \" + audioQuality.getMode());\n                                    MyLog.i(CLS_NAME, \"analysis: audioQuality: getMean: \" + audioQuality.getMean());\n                                }\n\n                                final Summary audioQualitySummary = audioQuality.getSummary();\n\n                                if (audioQualitySummary != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"analysis: audioQualitySummary: getMode: \" + audioQualitySummary.getMode());\n                                        MyLog.i(CLS_NAME, \"analysis: audioQualitySummary: getMean: \" + audioQualitySummary.getMean());\n                                        MyLog.i(CLS_NAME, \"analysis: audioQualitySummary: getModePct: \" + audioQualitySummary.getModePct());\n                                    }\n\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"analysis: audioQualitySummary null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"analysis: audioQuality null\");\n                                }\n                            }\n\n                            final Mood mood = analysis.getMood();\n\n                            if (mood != null) {\n\n                                final Composite composite = mood.getComposite();\n\n                                if (composite != null) {\n\n                                    final Primary compositePrimary = composite.getPrimary();\n\n                                    if (compositePrimary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"compositePrimary: getPhrase: \" + compositePrimary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"compositePrimary: getId: \" + compositePrimary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood compositePrimary null\");\n                                        }\n                                    }\n\n                                    final Secondary compositeSecondary = composite.getSecondary();\n\n                                    if (compositeSecondary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"compositeSecondary: getPhrase: \" + compositeSecondary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"compositeSecondary: getId: \" + compositeSecondary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood compositeSecondary null\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"segment: mood composite null\");\n                                    }\n                                }\n\n                                final Group7 group7 = mood.getGroup7();\n\n                                if (group7 != null) {\n\n                                    final Primary group7Primary = group7.getPrimary();\n\n                                    if (group7Primary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group7Primary: getPhrase: \" + group7Primary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group7Primary: getId: \" + group7Primary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group7Primary null\");\n                                        }\n                                    }\n\n                                    final Secondary group7Secondary = group7.getSecondary();\n\n                                    if (group7Secondary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group7Secondary: getPhrase: \" + group7Secondary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group7Secondary: getId: \" + group7Secondary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group7Secondary null\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"segment: mood group7 null\");\n                                    }\n                                }\n\n                                final Group11 group11 = mood.getGroup11();\n\n                                if (group11 != null) {\n\n                                    final Primary group11Primary = group11.getPrimary();\n\n                                    if (group11Primary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group11Primary: getPhrase: \" + group11Primary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group11Primary: getId: \" + group11Primary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group11Primary null\");\n                                        }\n                                    }\n\n                                    final Secondary group11Secondary = group11.getSecondary();\n\n                                    if (group11Secondary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group11Secondary: getPhrase: \" + group11Secondary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group11Secondary: getId: \" + group11Secondary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group11Secondary null\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"segment: mood group11 null\");\n                                    }\n                                }\n\n                                final Group21 group21 = mood.getGroup21();\n\n                                if (group21 != null) {\n\n                                    final Primary group21Primary = group21.getPrimary();\n\n                                    if (group21Primary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group21Primary: getPhrase: \" + group21Primary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group21Primary: getId: \" + group21Primary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group21Primary null\");\n                                        }\n                                    }\n\n                                    final Secondary group21Secondary = group21.getSecondary();\n\n                                    if (group21Secondary != null) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"group21Secondary: getPhrase: \" + group21Secondary.getPhrase());\n                                            MyLog.i(CLS_NAME, \"group21Secondary: getId: \" + group21Secondary.getId());\n                                        }\n\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"segment: mood group21Secondary null\");\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"segment: mood group21 null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"segment: mood null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"segment: analysis null\");\n                            }\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"interpretAndStore: segments naked\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"interpretAndStore: analysisResult null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"interpretAndStore: analysisSummary null\");\n            }\n        }\n    }\n\n    /**\n     * Check if we have an emotion analysis result saved\n     *\n     * @param ctx the application context\n     * @return true if a {@link AnalysisResult} is stored\n     */\n    public static boolean hasEmotion(@NonNull final Context ctx) {\n        return SPH.getEmotion(ctx) != null;\n    }\n\n    /**\n     * Get the {@link AnalysisResult} description we have stored\n     *\n     * @param ctx the application context\n     * @return the {@link AnalysisResult} description\n     */\n    public static String getEmotionDescription(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final AnalysisResult analysisResult;\n\n        if (hasEmotion(ctx)) {\n\n            try {\n                analysisResult = gson.fromJson(SPH.getEmotion(ctx), AnalysisResult.class);\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"emotion: \" + gson.toJson(analysisResult));\n                }\n                return analysisResult.getDescription();\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"emotion: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"emotion: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"emotion: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return PersonalityResponse.getBeyondVerbalErrorResponse(ctx, sl);\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/BeyondVerbal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.audio.AudioConfig;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.BVCredentials;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.StartRequestBody;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.StartResponse;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.http.BVStartRequest;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.http.BVStreamAudio;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.language.SupportedLanguageBV;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.user.MetaData;\nimport ai.saiy.android.configuration.BeyondVerbalConfiguration;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 13/08/2016.\n */\n\npublic class BeyondVerbal {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BeyondVerbal.class.getSimpleName();\n\n    public static final long FETCH_ANALYSIS_DELAY = 6000;\n    public static final long MINIMUM_AUDIO_TIME = 13000;\n\n    private final SupportedLanguage sl;\n    private final RecognitionMic mic;\n    private final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public BeyondVerbal(@NonNull final Context mContext, @NonNull final RecognitionMic mic,\n                        @NonNull final SupportedLanguage sl) {\n        this.mContext = mContext;\n        this.mic = mic;\n        this.sl = sl;\n    }\n\n    public void stream() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stream\");\n        }\n\n        final String token = getToken();\n\n        if (UtilsString.notNaked(token)) {\n\n            final Pair<Boolean, StartResponse> startRequest = new BVStartRequest(mContext, token)\n                    .getId(new StartRequestBody(AudioConfig.getDefault(), MetaData.getEmpty(),\n                            SupportedLanguageBV.getSupportedLanguage(sl.getLocale())).prepare());\n\n            if (startRequest.first) {\n                if (startRequest.second.isSuccessful()) {\n                    final String recordingId = startRequest.second.getRecordingId();\n\n                    if (mic.isAvailable()) {\n                        new BVStreamAudio(mic, sl, token, recordingId).stream();\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"mic unavailable\");\n                        }\n\n                        Recognition.setState(Recognition.State.IDLE);\n                        onError();\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"startRequest.second.isSuccessful()\");\n                    }\n                    onError();\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"startRequest.first\");\n                }\n                onError();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"token naked\");\n            }\n            onError();\n        }\n    }\n\n    private void onError() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onError\");\n        }\n\n        Recognition.setState(Recognition.State.IDLE);\n\n        final LocalRequest localRequest = new LocalRequest(mContext);\n        localRequest.setSupportedLanguage(sl);\n        localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n        localRequest.setUtterance(PersonalityResponse.getBeyondVerbalServerErrorResponse(mContext, sl));\n        localRequest.setTTSLocale(SPH.getTTSLocale(mContext));\n        localRequest.setVRLocale(SPH.getVRLocale(mContext));\n        localRequest.execute();\n    }\n\n    private String getToken() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getID\");\n        }\n\n        final Pair<Boolean, BVCredentials> tokenPair = BVCredentials.refreshTokenIfRequired(mContext,\n                BeyondVerbalConfiguration.API_KEY);\n\n        if (tokenPair.first) {\n            return tokenPair.second.getAccessToken();\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"failed to get token\");\n            }\n        }\n\n        return null;\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Analysis.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Analysis {\n\n    public static final String LOW = \"low\";\n    public static final String MED = \"med\";\n    public static final String HIGH = \"high\";\n\n    @SerializedName(\"Temper\")\n    private final Temper temper;\n\n    @SerializedName(\"Valence\")\n    private final Valence valence;\n\n    @SerializedName(\"Gender\")\n    private final Gender gender;\n\n    @SerializedName(\"Mood\")\n    private final Mood mood;\n\n    @SerializedName(\"Arousal\")\n    private final Arousal arousal;\n\n    @SerializedName(\"AudioQuality\")\n    private final AudioQuality audioQuality;\n\n    public Analysis(final Gender gender, final Temper temper, final Valence valence, final Mood mood,\n                    final AudioQuality audioQuality, final Arousal arousal) {\n        this.gender = gender;\n        this.temper = temper;\n        this.valence = valence;\n        this.mood = mood;\n        this.audioQuality = audioQuality;\n        this.arousal = arousal;\n    }\n\n    public Arousal getArousal() {\n        return arousal;\n    }\n\n    public AudioQuality getAudioQuality() {\n        return audioQuality;\n    }\n\n    public Gender getGender() {\n        return gender;\n    }\n\n    public Mood getMood() {\n        return mood;\n    }\n\n    public Temper getTemper() {\n        return temper;\n    }\n\n    public Valence getValence() {\n        return valence;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/AnalysisSummary.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 14/08/2016.\n */\n\npublic class AnalysisSummary {\n\n    @SerializedName(\"AnalysisResult\")\n    private final Analysis analysisResult;\n\n    public AnalysisSummary(final Analysis analysisResult) {\n        this.analysisResult = analysisResult;\n    }\n\n    public Analysis getAnalysisResult() {\n        return analysisResult;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Arousal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Arousal {\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    @SerializedName(\"Value\")\n    private final double value;\n\n    @SerializedName(\"Score\")\n    private final double score;\n\n    @SerializedName(\"Group\")\n    private final String group;\n\n    @SerializedName(\"Summary\")\n    private final Summary summary;\n\n    public Arousal(final String group, final double value, final Summary summary, final double mean, final String mode,\n                   final double score) {\n        this.group = group;\n        this.value = value;\n        this.summary = summary;\n        this.mean = mean;\n        this.mode = mode;\n        this.score = score;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public Summary getSummary() {\n        return summary;\n    }\n\n    public double getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/AudioQuality.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class AudioQuality {\n\n    public static final String BAD = \"bad\";\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    @SerializedName(\"Value\")\n    private final double value;\n\n    @SerializedName(\"Group\")\n    private final String group;\n\n    @SerializedName(\"Summary\")\n    private final Summary summary;\n\n    public AudioQuality(final String group, final double value, final double mean, final String mode, final Summary summary) {\n        this.group = group;\n        this.value = value;\n        this.mean = mean;\n        this.mode = mode;\n        this.summary = summary;\n    }\n\n    public Summary getSummary() {\n        return summary;\n    }\n\n    public double getValue() {\n        return value;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Composite.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Composite {\n\n    @SerializedName(\"Primary\")\n    private final Primary primary;\n\n    @SerializedName(\"Secondary\")\n    private final Secondary secondary;\n\n    public Composite(final Primary primary, final Secondary secondary) {\n        this.primary = primary;\n        this.secondary = secondary;\n    }\n\n    public Primary getPrimary() {\n        return primary;\n    }\n\n    public Secondary getSecondary() {\n        return secondary;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Emotions.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 09/06/2016.\n */\npublic class Emotions {\n\n    public static final String SUCCESS = \"success\";\n\n    @SerializedName(\"result\")\n    private final Result result;\n\n    @SerializedName(\"status\")\n    private final String status;\n\n    @SerializedName(\"recordingId\")\n    private String recordingId;\n\n    public Emotions(final String recordingId, final Result result, final String status) {\n        this.recordingId = recordingId;\n        this.result = result;\n        this.status = status;\n    }\n\n    public void setRecordingId(@NonNull final String recordingId) {\n        this.recordingId = recordingId;\n    }\n\n    public String getRecordingId() {\n        return recordingId;\n    }\n\n    public Result getResult() {\n        return result;\n    }\n\n    public String getStatus() {\n        return status;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Gender.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Gender {\n\n    public static final String MALE = \"male\";\n    public static final String FEMALE = \"female\";\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    @SerializedName(\"Value\")\n    private final double value;\n\n    @SerializedName(\"Group\")\n    private final String group;\n\n    @SerializedName(\"Summary\")\n    private final Summary summary;\n\n    public Gender(final String group, final double value, final Summary summary, final double mean, final String mode) {\n        this.group = group;\n        this.value = value;\n        this.summary = summary;\n        this.mean = mean;\n        this.mode = mode;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public Summary getSummary() {\n        return summary;\n    }\n\n    public double getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Group11.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Group11 {\n\n    @SerializedName(\"Primary\")\n    private final Primary primary;\n\n    @SerializedName(\"Secondary\")\n    private final Secondary secondary;\n\n    public Group11(final Primary primary, final Secondary secondary) {\n        this.primary = primary;\n        this.secondary = secondary;\n    }\n\n    public Primary getPrimary() {\n        return primary;\n    }\n\n    public Secondary getSecondary() {\n        return secondary;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Group21.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Group21 {\n\n    @SerializedName(\"Primary\")\n    private final Primary primary;\n\n    @SerializedName(\"Secondary\")\n    private final Secondary secondary;\n\n    public Group21(final Primary primary, final Secondary secondary) {\n        this.primary = primary;\n        this.secondary = secondary;\n    }\n\n    public Primary getPrimary() {\n        return primary;\n    }\n\n    public Secondary getSecondary() {\n        return secondary;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Group7.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Group7 {\n\n    @SerializedName(\"Primary\")\n    private final Primary primary;\n\n    @SerializedName(\"Secondary\")\n    private final Secondary secondary;\n\n    public Group7(final Primary primary, final Secondary secondary) {\n        this.primary = primary;\n        this.secondary = secondary;\n    }\n\n    public Primary getPrimary() {\n        return primary;\n    }\n\n    public Secondary getSecondary() {\n        return secondary;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Mood.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Mood {\n\n    @SerializedName(\"Group11\")\n    private final Group11 group11;\n\n    @SerializedName(\"Group7\")\n    private final Group7 group7;\n\n    @SerializedName(\"Group21\")\n    private final Group21 group21;\n\n    @SerializedName(\"Composite\")\n    private final Composite composite;\n\n    public Mood(final Composite composite, final Group11 group11, final Group7 group7, final Group21 group21) {\n        this.composite = composite;\n        this.group11 = group11;\n        this.group7 = group7;\n        this.group21 = group21;\n    }\n\n    public Composite getComposite() {\n        return composite;\n    }\n\n    public Group11 getGroup11() {\n        return group11;\n    }\n\n    public Group21 getGroup21() {\n        return group21;\n    }\n\n    public Group7 getGroup7() {\n        return group7;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Primary.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Primary {\n\n    @SerializedName(\"Id\")\n    private final int id;\n\n    @SerializedName(\"Phrase\")\n    private final String phrase;\n\n    public Primary(final int id, final String phrase) {\n        this.id = id;\n        this.phrase = phrase;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getPhrase() {\n        return phrase;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Result.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Result {\n\n    @SerializedName(\"analysisSegments\")\n    private final List<Segment> segments;\n\n    @SerializedName(\"analysisSummary\")\n    private final AnalysisSummary analysisSummary;\n\n    @SerializedName(\"duration\")\n    private final double duration;\n\n    @SerializedName(\"sessionStatus\")\n    private final String sessionStatus;\n\n    public Result(final double duration, final List<Segment> segments, final String sessionStatus,\n                  final AnalysisSummary analysisSummary) {\n        this.duration = duration;\n        this.segments = segments;\n        this.sessionStatus = sessionStatus;\n        this.analysisSummary = analysisSummary;\n    }\n\n    public AnalysisSummary getAnalysisSummary() {\n        return analysisSummary;\n    }\n\n    public double getDuration() {\n        return duration;\n    }\n\n    public List<Segment> getSegments() {\n        return segments;\n    }\n\n    public String getSessionStatus() {\n        return sessionStatus;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Secondary.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Secondary {\n\n    @SerializedName(\"Id\")\n    private final int id;\n\n    @SerializedName(\"Phrase\")\n    private final String phrase;\n\n    public Secondary(final int id, final String phrase) {\n        this.id = id;\n        this.phrase = phrase;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getPhrase() {\n        return phrase;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Segment.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Segment {\n\n    @SerializedName(\"duration\")\n    private final double duration;\n\n    @SerializedName(\"offset\")\n    private final double offset;\n\n    @SerializedName(\"analysis\")\n    private final Analysis analysis;\n\n    public Segment(final Analysis analysis, final double duration, final double offset) {\n        this.analysis = analysis;\n        this.duration = duration;\n        this.offset = offset;\n    }\n\n    public Analysis getAnalysis() {\n        return analysis;\n    }\n\n    public double getDuration() {\n        return duration;\n    }\n\n    public double getOffset() {\n        return offset;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Summary.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Summary {\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"ModePct\")\n    private final int modePct;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    public Summary(final double mean, final String mode, final int modePct) {\n        this.mean = mean;\n        this.mode = mode;\n        this.modePct = modePct;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public int getModePct() {\n        return modePct;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Temper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Temper {\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    @SerializedName(\"Value\")\n    private final double value;\n\n    @SerializedName(\"Score\")\n    private final double score;\n\n    @SerializedName(\"Group\")\n    private final String group;\n\n    @SerializedName(\"Summary\")\n    private final Summary summary;\n\n    public Temper(final String group, final double value, final Summary summary, final double mean, final String mode,\n                  final double score) {\n        this.group = group;\n        this.value = value;\n        this.summary = summary;\n        this.mean = mean;\n        this.mode = mode;\n        this.score = score;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public Summary getSummary() {\n        return summary;\n    }\n\n    public double getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/analysis/Valence.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Helper class to serialise the JSON response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class Valence {\n\n    public static final String POSITIVE = \"positive\";\n    public static final String NEUTRAL = \"neutral\";\n    public static final String NEGATIVE = \"negative\";\n\n    @SerializedName(\"Mode\")\n    private final String mode;\n\n    @SerializedName(\"Mean\")\n    private final double mean;\n\n    @SerializedName(\"Value\")\n    private final double value;\n\n    @SerializedName(\"Score\")\n    private final double score;\n\n    @SerializedName(\"Group\")\n    private final String group;\n\n    @SerializedName(\"Summary\")\n    private final Summary summary;\n\n    public Valence(final String group, final double value, final Summary summary, final double mean, final String mode,\n                   final double score) {\n        this.group = group;\n        this.value = value;\n        this.summary = summary;\n        this.mean = mean;\n        this.mode = mode;\n        this.score = score;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public double getMean() {\n        return mean;\n    }\n\n    public String getMode() {\n        return mode;\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public Summary getSummary() {\n        return summary;\n    }\n\n    public double getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/audio/AudioConfig.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.audio;\n\nimport android.support.annotation.NonNull;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * Class to set the audio configuration details, that will be sent with API requests.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class AudioConfig {\n\n    private static final String TYPE = \"type\";\n    private static final String CHANNELS = \"channels\";\n    private static final String SAMPLE_RATE = \"sample_rate\";\n    private static final String BITS_PER_SAMPLE = \"bits_per_sample\";\n    private static final String AUTO_DETECT = \"auto_detect\";\n\n    private AudioType type;\n    private int channels;\n    private int sampleRate;\n    private int bitsPerSample;\n    private boolean autoDetect;\n\n    /**\n     * Default constructor\n     */\n    public AudioConfig() {\n    }\n\n    /**\n     * Constructor\n     *\n     * @param type          the audio type, one of {@link AudioType#PCM} or {@link AudioType#WAV}\n     * @param bitsPerSample the bits per sample\n     * @param channels      number of channels\n     * @param sampleRate    the sampling rate\n     * @param autoDetect    ???\n     */\n    public AudioConfig(@NonNull final AudioType type, final int bitsPerSample,\n                       final int channels, final int sampleRate, final boolean autoDetect) {\n        this.autoDetect = autoDetect;\n        this.bitsPerSample = bitsPerSample;\n        this.channels = channels;\n        this.sampleRate = sampleRate;\n        this.type = type;\n    }\n\n    public static AudioConfig getDefault() {\n        return new AudioConfig(AudioType.PCM, 16, 1, 8000, true);\n    }\n\n    /**\n     * Method to prepare the configuration details in a JSON format that the API will accept.\n     *\n     * @return a JSON formatted representation of the audio configuration\n     */\n    public JSONObject getConfigJson() {\n\n        final JSONObject object = new JSONObject();\n\n        try {\n            object.put(TYPE, getType());\n            object.put(CHANNELS, getChannels());\n            object.put(SAMPLE_RATE, getSampleRate());\n            object.put(BITS_PER_SAMPLE, getBitsPerSample());\n            object.put(AUTO_DETECT, isAutoDetect());\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n\n        return object;\n    }\n\n    public int getBitsPerSample() {\n        return bitsPerSample;\n    }\n\n    public int getSampleRate() {\n        return sampleRate;\n    }\n\n    public boolean isAutoDetect() {\n        return autoDetect;\n    }\n\n    public void setAutoDetect(final boolean autoDetect) {\n        this.autoDetect = autoDetect;\n    }\n\n\n    public void setBitsPerSample(final int bitsPerSample) {\n        this.bitsPerSample = bitsPerSample;\n    }\n\n    public int getChannels() {\n        return channels;\n    }\n\n    public void setChannels(final int channels) {\n        this.channels = channels;\n    }\n\n    public void setSampleRate(final int sampleRate) {\n        this.sampleRate = sampleRate;\n    }\n\n    public AudioType getType() {\n        return type;\n    }\n\n    public void setType(@NonNull final AudioType type) {\n        this.type = type;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/audio/AudioType.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.audio;\n\n/**\n * Class to ensure that a supported audio file type is chosen prior to being sent to the API.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic enum AudioType {\n\n    WAV(\"wav\"),\n    PCM(\"pcm\");\n\n    private final String type;\n\n    /**\n     * Enum Constructor\n     *\n     * @param type the audio file type in a format supported by the API\n     */\n    AudioType(final String type) {\n        this.type = type;\n    }\n\n    public String getType() {\n        return type;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/containers/BVCredentials.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Pair;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\nimport com.google.gson.annotations.SerializedName;\n\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.http.BVAuthRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to serialise the credential information from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 09/06/2016.\n */\npublic class BVCredentials {\n\n    private transient static final boolean DEBUG = MyLog.DEBUG;\n    private transient static final String CLS_NAME = BVCredentials.class.getSimpleName();\n\n    @SerializedName(\"access_token\")\n    private final String accessToken;\n\n    @SerializedName(\"token_type\")\n    private final String tokenType;\n\n    @SerializedName(\"expires_in\")\n    private final long expiresIn;\n\n    @SerializedName(\"expiry_time\")\n    private long expiryTime;\n\n    public BVCredentials(final String accessToken, final String tokenType, final long expiresIn,\n                         final long expiryTime) {\n        this.accessToken = accessToken;\n        this.tokenType = tokenType;\n        this.expiresIn = expiresIn;\n        this.expiryTime = expiryTime;\n    }\n\n    /**\n     * Method to check if the token is valid\n     *\n     * @param bvCredentials the {@link BVCredentials} containing the most recent token credentials.\n     * @return true if the token is valid, false otherwise.\n     */\n    public static boolean isTokenValid(@Nullable final BVCredentials bvCredentials) {\n        return bvCredentials != null && System.currentTimeMillis() < bvCredentials.getExpiryTime();\n    }\n\n    /**\n     * Convenience method to check if the most recent access token remains valid and automatically\n     * request one if required.\n     *\n     * @param ctx    the application context\n     * @param apiKey the API key\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link BVCredentials} object, containing the token credentials. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public static Pair<Boolean, BVCredentials> refreshTokenIfRequired(@NonNull final Context ctx,\n                                                                      @NonNull final String apiKey) {\n        final BVCredentials bvCredentials = getLastToken(ctx);\n\n        if (isTokenValid(bvCredentials)) {\n            return new Pair<>(true, bvCredentials);\n        } else {\n            return new BVAuthRequest(ctx.getApplicationContext(), apiKey).getToken();\n        }\n    }\n\n    /**\n     * Method to get the most recent access token stored in the user {@link SPH )\n     * shared preferences.\n     *\n     * @param ctx the application context\n     * @return the most recent {@link BVCredentials} object\n     */\n    private static BVCredentials getLastToken(@NonNull final Context ctx) {\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final BVCredentials bvCredentials;\n\n        try {\n            bvCredentials = gson.fromJson(SPH.getBeyondVerbalCredentials(ctx), BVCredentials.class);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getLastToken: \" + gson.toJson(bvCredentials));\n            }\n            return bvCredentials;\n        } catch (final JsonSyntaxException e) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getLastToken: JsonSyntaxException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getLastToken: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getLastToken: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    public long getExpiresIn() {\n        return expiresIn;\n    }\n\n    public String getTokenType() {\n        return tokenType;\n    }\n\n    public long getExpiryTime() {\n        return expiryTime;\n    }\n\n    public void setExpiryTime(final long expiryTime) {\n        this.expiryTime = (System.currentTimeMillis() + (expiryTime * 1000));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/containers/StartRequestBody.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.audio.AudioConfig;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.language.SupportedLanguageBV;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.user.MetaData;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Utility class to format the request body parameters into a format the API will accept.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class StartRequestBody {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = StartRequestBody.class.getSimpleName();\n\n    private static final String DATA_FORMAT = \"dataFormat\";\n    private static final String META_DATA = \"metadata\";\n    private static final String DISPLAY_LANGUAGE = \"displayLang\";\n\n    private final AudioConfig config;\n    private final MetaData metaData;\n    private final SupportedLanguageBV sl;\n\n    /**\n     * Constructor\n     *\n     * @param config   the {@link AudioConfig}\n     * @param metaData the {@link MetaData}. Optional, so can be null\n     * @param sl       the {@link SupportedLanguageBV}. Optional, so can be null\n     */\n    public StartRequestBody(@NonNull final AudioConfig config, @Nullable final MetaData metaData,\n                            @Nullable final SupportedLanguageBV sl) {\n        this.config = config;\n        this.metaData = metaData;\n        this.sl = sl;\n    }\n\n    /**\n     * Method to format the body parameters into an accepted JSON format\n     *\n     * @return an {@link JSONObject} containing the body parameters\n     */\n    public JSONObject prepare() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepare\");\n        }\n\n        final JSONObject object = new JSONObject();\n\n        try {\n\n            object.put(DATA_FORMAT, config.getConfigJson());\n\n            if (metaData != null) {\n                object.put(META_DATA, metaData.getMetaJSON());\n            }\n\n            if (sl != null) {\n                object.put(DISPLAY_LANGUAGE, sl.getServerFormat());\n            }\n\n        } catch (final JSONException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"prepare JSONException\");\n                e.printStackTrace();\n            }\n        }\n\n        return object;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/containers/StartResponse.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Class to serialise the response from Beyond Verbal\n * <p>\n * Created by benrandall76@gmail.com on 09/06/2016.\n */\npublic class StartResponse {\n\n    private static final String SUCCESS = \"success\";\n    private static final String FAILURE = \"failure\";\n\n    @SerializedName(\"status\")\n    private final String status;\n\n    @SerializedName(\"recordingId\")\n    private final String recordingId;\n\n    @SerializedName(\"reason\")\n    private final String reason;\n\n    public StartResponse(final String reason, final String status, final String recordingId) {\n        this.reason = reason;\n        this.status = status;\n        this.recordingId = recordingId;\n    }\n\n    public boolean isSuccessful() {\n        return getStatus().matches(SUCCESS);\n    }\n\n    public String getReason() {\n        return reason;\n    }\n\n    public String getRecordingId() {\n        return recordingId;\n    }\n\n    public String getStatus() {\n        return status;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/http/BVAuthRequest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.StringRequest;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.BVCredentials;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to get an initial access token, which will be valid for a short period of time. This\n * request must be made before emotion analysis or any other request.\n * <p>\n * Rather than using this class directly, it is better to\n * call {@link BVCredentials#refreshTokenIfRequired(Context, String)} which will invoke this class if\n * the current access token has expired.\n * <p>\n * The token request is always synchronous, as it is an 'on-demand' requirement.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class BVAuthRequest {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BVAuthRequest.class.getSimpleName();\n\n    private static final String AUTH_URL = \"https://token.beyondverbal.com/token\";\n    private static final String GRANT_TYPE = \"grant_type\";\n    private static final String CLIENT_CREDENTIALS = \"client_credentials\";\n    private static final String API_KEY = \"apiKey\";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final String apiKey;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param apiKey   the api key\n     */\n    public BVAuthRequest(@NonNull final Context mContext, @NonNull final String apiKey) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n    }\n\n    /**\n     * Method to get a temporary access token.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link BVCredentials} object, containing the token credentials. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, BVCredentials> getToken() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getID\");\n        }\n\n        final RequestFuture<String> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final StringRequest request = new StringRequest(Request.Method.POST, AUTH_URL, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            BVAuthRequest.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(CONTENT_TYPE, HEADER_CONTENT_TYPE);\n                return params;\n            }\n\n            @Override\n            protected Map<String, String> getParams() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(API_KEY, apiKey);\n                params.put(GRANT_TYPE, CLIENT_CREDENTIALS);\n                return params;\n            }\n        };\n\n        request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(request);\n\n        String response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response);\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final BVCredentials authResponse = gson.fromJson(response, BVCredentials.class);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onResponse: getAccessToken: \" + authResponse.getAccessToken());\n                MyLog.i(CLS_NAME, \"onResponse: getTokenType: \" + authResponse.getTokenType());\n                MyLog.i(CLS_NAME, \"onResponse: getExpiresIn: \" + authResponse.getExpiresIn());\n            }\n\n            authResponse.setExpiryTime(authResponse.getExpiresIn());\n            SPH.setBeyondVerbalCredentials(mContext, gson.toJson(authResponse));\n\n            return new Pair<>(true, authResponse);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/http/BVEmotionAnalysis.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.Cache;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.Network;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.BasicNetwork;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.HurlStack;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.StringRequest;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.nuance.dragon.toolkit.oem.api.json.JSONObject;\n\nimport org.json.JSONException;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.AnalysisResultHelper;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.analysis.Emotions;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.BVCredentials;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsVolley;\n\n/**\n * Class to get an initial access token, which will be valid for a short period of time. This\n * request must be made before emotion analysis or any other request.\n * <p/>\n * Rather than using this class directly, it is better to\n * call {@link BVCredentials#refreshTokenIfRequired(Context, String)} which will invoke this class if\n * the current access token has expired.\n * <p/>\n * The token request is always synchronous, as it is an 'on-demand' requirement.\n * <p/>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class BVEmotionAnalysis {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BVEmotionAnalysis.class.getSimpleName();\n\n    private static final String ANALYSIS_URL = \"https://apiv4.beyondverbal.com/v4/recording/\";\n    private static final String FROM_MS = \"/analysis?fromMs=\";\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final SupportedLanguage sl;\n    private final String token;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage} object\n     * @param token    the access token\n     */\n    public BVEmotionAnalysis(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                             @NonNull final String token) {\n        this.token = token;\n        this.mContext = mContext;\n        this.sl = sl;\n    }\n\n    /**\n     * Method to get a temporary access token.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link BVCredentials} object, containing the token credentials. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, Emotions> getAnalysis(@NonNull final String recordingId, final int offset) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAnalysis\");\n        }\n\n        final RequestFuture<String> future = RequestFuture.newFuture();\n        final Cache cache = UtilsVolley.getCache(mContext);\n        final Network network = new BasicNetwork(new HurlStack());\n        final RequestQueue queue = new RequestQueue(cache, network);\n        queue.start();\n\n        final String url = ANALYSIS_URL + recordingId + FROM_MS + String.valueOf(offset);\n\n        final StringRequest request = new StringRequest(Request.Method.GET, url, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            BVEmotionAnalysis.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(AUTHORIZATION, BEARER_ + token);\n                return params;\n            }\n        };\n\n        request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(request);\n\n        String response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onResponse: \" + response);\n                try {\n                    final JSONObject object = new JSONObject(response);\n                    MyLog.i(CLS_NAME, \"object: \" + object.toString(4));\n                } catch (final JSONException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final Emotions emotions = gson.fromJson(response, Emotions.class);\n            emotions.setRecordingId(recordingId);\n\n            new AnalysisResultHelper(mContext, sl).interpretAndStore(emotions);\n\n            return new Pair<>(true, emotions);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"onResponse: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/http/BVSendFile.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;\n\nimport android.net.ParseException;\nimport android.support.annotation.NonNull;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URL;\nimport java.net.UnknownHostException;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to send a file of audio data to the BV API\n * <p>\n * Created by benrandall76@gmail.com on 09/06/2016.\n */\npublic class BVSendFile {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BVSendFile.class.getSimpleName();\n\n    private static final String RECORDING_URL = \"https://apiv4.beyondverbal.com/v4/recording/\";\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n\n    private HttpsURLConnection urlConnection;\n    private OutputStream outputStream;\n    private FileInputStream fileInputStream;\n\n    private final String token;\n    private final String recordingId;\n    private final File file;\n\n    public BVSendFile(@NonNull final String token, @NonNull final String recordingId,\n                      @NonNull final File file) {\n        this.token = token;\n        this.recordingId = recordingId;\n        this.file = file;\n    }\n\n    public void stream() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stream\");\n        }\n\n        try {\n\n            urlConnection = (HttpsURLConnection) new URL(RECORDING_URL + recordingId).openConnection();\n            urlConnection.setRequestMethod(Constants.HTTP_POST);\n            urlConnection.setRequestProperty(CONTENT_TYPE, HEADER_CONTENT_TYPE);\n            urlConnection.setRequestProperty(AUTHORIZATION, BEARER_ + token);\n            urlConnection.setUseCaches(false);\n            urlConnection.setDoOutput(true);\n            urlConnection.connect();\n\n            outputStream = urlConnection.getOutputStream();\n            fileInputStream = new FileInputStream(file);\n\n            final byte[] buffer = new byte[1024];\n\n            int len;\n            while ((len = fileInputStream.read(buffer)) != -1) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"writing bytes: \" + len);\n                }\n                outputStream.write(buffer, 0, len);\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Requesting Response code\");\n            }\n\n            final int responseCode = urlConnection.getResponseCode();\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"responseCode: \" + responseCode);\n            }\n\n            if (responseCode != HttpsURLConnection.HTTP_OK) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"ErrorStream: \"\n                            + UtilsString.streamToString(urlConnection.getErrorStream()));\n                }\n            }\n\n        } catch (final ParseException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"ParseException\");\n                e.printStackTrace();\n            }\n        } catch (final UnknownHostException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"UnknownHostException\");\n                e.printStackTrace();\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"IOException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            closeConnection();\n        }\n    }\n\n    private void closeConnection() {\n\n        if (fileInputStream != null) {\n\n            try {\n                fileInputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/http/BVStartRequest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.json.JSONObject;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.StartResponse;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to get a recording identifier that will be used for emotion analysis request. This identifier\n * should be stored for future use, as it can be used to retrieve meta-data regarding the recording\n * in the future.\n * <p/>\n * This is a synchronous request, as the identifier will be required immediately.\n * <p/>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class BVStartRequest {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BVStartRequest.class.getSimpleName();\n\n    private static final String START_URL = \"https://apiv4.beyondverbal.com/v4/recording/start\";\n\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final String token;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param token    the access token\n     */\n    public BVStartRequest(@NonNull final Context mContext, @NonNull final String token) {\n        this.token = token;\n        this.mContext = mContext.getApplicationContext();\n    }\n\n    /**\n     * Method to get a recording identifier.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link StartResponse} object, containing the recording id.\n     * <p>\n     * If the request was unsuccessful, the second parameter may be null.\n     */\n    public Pair<Boolean, StartResponse> getId(@NonNull final JSONObject body) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getId\");\n        }\n\n        final RequestFuture<JSONObject> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST, START_URL,\n                body, future, new Response.ErrorListener() {\n            @Override\n            public void onErrorResponse(final VolleyError error) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                    BVStartRequest.this.verboseError(error);\n                }\n                queue.stop();\n            }\n        }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(AUTHORIZATION, BEARER_ + token);\n                return params;\n            }\n        };\n\n        jsonObjReq.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(jsonObjReq);\n\n        JSONObject response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response);\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final StartResponse startResponse = gson.fromJson(response.toString(), StartResponse.class);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onResponse: getStatus: \" + startResponse.getStatus());\n            }\n\n            if (startResponse.isSuccessful()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onResponse: getRecordingId: \" + startResponse.getRecordingId());\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onResponse: getReason: \" + startResponse.getReason());\n                }\n            }\n\n            return new Pair<>(true, startResponse);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/http/BVStreamAudio.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;\n\nimport android.net.ParseException;\nimport android.os.Process;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.UnknownHostException;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.BeyondVerbal;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to stream raw audio to the BV API\n * <p>\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class BVStreamAudio implements IMic {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BVStreamAudio.class.getSimpleName();\n\n    private static final String RECORDING_URL = \"https://apiv4.beyondverbal.com/v4/recording/\";\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n\n    private static final String TRANSFER_ENCODING = \"Transfer-Encoding\";\n    private static final String CHUNKED = \"chunked\";\n    private static final String CONTENT_TYPE_AUDIO_PARAMS = \"audio/l16; rate=8000\";\n\n    private volatile HttpsURLConnection urlConnection;\n    private volatile OutputStream outputStream;\n\n    private volatile int retryCount;\n\n    private final String token;\n    private final String recordingId;\n    private final RecognitionMic mic;\n    private final SupportedLanguage sl;\n\n    /**\n     * Constructor\n     *\n     * @param mic         the initialised {@link RecognitionMic} object\n     * @param sl          the {@link SupportedLanguage} object\n     * @param token       the Beyond Verbal access token\n     * @param recordingId the Beyond Verbal recording id.\n     */\n    public BVStreamAudio(@NonNull final RecognitionMic mic, @NonNull final SupportedLanguage sl,\n                         @NonNull final String token,\n                         @NonNull final String recordingId) {\n        this.mic = mic;\n        this.token = token;\n        this.recordingId = recordingId;\n        this.sl = sl;\n\n        this.mic.setMicListener(this);\n    }\n\n    /**\n     * Start streaming the audio to the BV servers\n     */\n    public void stream() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stream\");\n        }\n\n        mic.startRecording();\n\n        final Thread httpThread = new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                try {\n\n                    urlConnection = (HttpsURLConnection) new URL(RECORDING_URL + recordingId).openConnection();\n                    urlConnection.setAllowUserInteraction(false);\n                    urlConnection.setInstanceFollowRedirects(true);\n                    urlConnection.setRequestMethod(Constants.HTTP_POST);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, HEADER_CONTENT_TYPE);\n                    urlConnection.setRequestProperty(AUTHORIZATION, BEARER_ + token);\n                    urlConnection.setUseCaches(false);\n                    urlConnection.setDoOutput(true);\n                    urlConnection.setRequestProperty(TRANSFER_ENCODING, CHUNKED);\n                    urlConnection.setChunkedStreamingMode(0);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, CONTENT_TYPE_AUDIO_PARAMS);\n                    urlConnection.connect();\n\n                    outputStream = urlConnection.getOutputStream();\n\n                    synchronized (mic.getLock()) {\n                        while (mic.isRecording()) {\n                            try {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"mic lock waiting\");\n                                }\n                                mic.getLock().wait();\n                            } catch (final InterruptedException e) {\n                                if (DEBUG) {\n                                    MyLog.e(CLS_NAME, \"InterruptedException\");\n                                    e.printStackTrace();\n                                }\n                                break;\n                            }\n                        }\n                    }\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"requesting response\");\n                    }\n\n                    final int responseCode = urlConnection.getResponseCode();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n                    }\n\n                    if (responseCode != HttpsURLConnection.HTTP_OK) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"audioThread ErrorStream: \"\n                                    + UtilsString.streamToString(urlConnection.getErrorStream()));\n                        }\n\n                        onError(SpeechRecognizer.ERROR_NETWORK);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"response: HTTP_OK. Setting timer\");\n                        }\n\n                        if (mic.isInterrupted()) {\n                            onError(Speaker.ERROR_USER_CANCELLED);\n                        } else {\n                            mic.getRecognitionListener().onComplete();\n                            proceedAndNotify();\n                        }\n                    }\n\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final ParseException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"ParseException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final UnknownHostException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnknownHostException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IOException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                } finally {\n                    closeConnection();\n                    mic.stopRecording();\n                }\n            }\n        };\n\n        httpThread.start();\n    }\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n\n        try {\n            for (int i = 0; i < bufferReadResult; i++) {\n                if (outputStream != null) {\n                    outputStream.write(buffer[i]);\n                }\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"onBufferReceived: IOException\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private void proceedAndNotify() {\n\n        if (retryCount < 2) {\n\n            if (retryCount == 0) {\n                final LocalRequest localRequest = new LocalRequest(mic.getContext());\n                localRequest.setSupportedLanguage(sl);\n                localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n                localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n                localRequest.setUtterance(PersonalityResponse.getBVAnalysisCompleteResponse(mic.getContext(), sl));\n                localRequest.execute();\n            }\n\n            retryCount++;\n\n            final TimerTask timerTask = new TimerTask() {\n                @Override\n                public void run() {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"timerTask: fetching analysis\");\n                    }\n\n                    if (!new BVEmotionAnalysis(mic.getContext(), sl, token).getAnalysis(recordingId, 0)\n                            .first) {\n                        proceedAndNotify();\n                    }\n                }\n            };\n\n            new Timer().schedule(timerTask, BeyondVerbal.FETCH_ANALYSIS_DELAY);\n        }\n    }\n\n    @Override\n    public void onError(int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        mic.getRecognitionListener().onComplete();\n        Recognition.setState(Recognition.State.IDLE);\n\n        if (mic.isInterrupted()) {\n            error = Speaker.ERROR_USER_CANCELLED;\n        }\n\n        final LocalRequest localRequest = new LocalRequest(mic.getContext());\n        localRequest.setSupportedLanguage(sl);\n        localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n        localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n        localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n\n        switch (error) {\n\n            case Speaker.ERROR_USER_CANCELLED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_USER_CANCELLED\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.cancelled));\n                break;\n            case Speaker.ERROR_NETWORK:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_NETWORK\");\n                }\n                localRequest.setUtterance(PersonalityResponse.getNoNetwork(mic.getContext(), sl));\n                break;\n            case Speaker.ERROR_AUDIO:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_AUDIO\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            case Speaker.ERROR_FILE:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_FILE\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError default\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n        }\n\n        localRequest.execute();\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n    }\n\n    private void closeConnection() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"closeConnection\");\n        }\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/language/SupportedLanguageBV.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.language;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.Locale;\n\n/**\n * Utility class to query and access supported languages of the Beyond Verbal API\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic enum SupportedLanguageBV {\n\n    ENGLISH_US(\"en-us\", \"eng_USA\", \"English\", \"United States\", Locale.US);\n\n    private final String languageCountry;\n    private final String languageCountryISO;\n    private final String language;\n    private final String country;\n    private final Locale locale;\n\n    SupportedLanguageBV(final String country, final String languageCountry, final String languageCountryISO,\n                        final String language, final Locale locale) {\n        this.country = country;\n        this.languageCountry = languageCountry;\n        this.languageCountryISO = languageCountryISO;\n        this.language = language;\n        this.locale = locale;\n    }\n\n    public static boolean isSupported(@NonNull final Locale loc) {\n\n        for (final SupportedLanguageBV sl : getSupportedLanguages()) {\n            if (sl.getLocale().equals(loc)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    public static SupportedLanguageBV getSupportedLanguage(@NonNull final Locale loc) {\n\n        for (final SupportedLanguageBV sl : getSupportedLanguages()) {\n            if (sl.getLocale().equals(loc)) {\n                return sl;\n            }\n        }\n\n        return ENGLISH_US;\n    }\n\n    public static SupportedLanguageBV[] getSupportedLanguages() {\n        return SupportedLanguageBV.values();\n    }\n\n    public String getCountry() {\n        return country;\n    }\n\n    public String getLanguage() {\n        return language;\n    }\n\n    public String getLanguageCountry() {\n        return languageCountry;\n    }\n\n    public String getServerFormat() {\n        return languageCountry;\n    }\n\n    public String getLanguageCountryISO() {\n        return languageCountryISO;\n    }\n\n    public Locale getLocale() {\n        return locale;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/emotion/provider/beyondverbal/user/MetaData.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.emotion.provider.beyondverbal.user;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * Class to set additional optional information regarding the user. Such parameters will have future\n * uses, such as direct and social media sharing.\n * <p>\n * In the case of encrypted emotion analysis, the unique device id will need to be set.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class MetaData {\n\n    private static final String EMAIL = \"email\";\n    private static final String PHONE = \"phone\";\n    private static final String DEVICE_ID = \"deviceId\";\n    private static final String FACEBOOK_ID = \"facebookId\";\n    private static final String TWITTER_ID = \"twitterId\";\n\n    private static final String DEFAULT_DEVICE_ID = \"default_device_id\";\n    private static final String DEFAULT_DEVICE_PHONE = \"default_phone\";\n    private static final String DEFAULT_DEVICE_EMAIL = \"default_email\";\n    private static final String DEFAULT_DEVICE_FACEBOOK_ID = \"default_facebook_id\";\n    private static final String DEFAULT_DEVICE_TWITTER_HANDLE = \"default_twitter_id\";\n\n    private String email;\n    private String phone;\n    private String deviceId;\n    private String facebookId;\n    private String twitterId;\n\n    /**\n     * Default Constructor\n     */\n    public MetaData() {\n    }\n\n    /**\n     * Constructor\n     *\n     * @param deviceId   the device unique id\n     * @param email      the user's email address\n     * @param facebookId the user's facebook url\n     * @param phone      the user's phone number\n     * @param twitterId  the user's Twitter handle\n     */\n    public MetaData(@Nullable final String deviceId, @Nullable final String email,\n                    @Nullable final String facebookId, @Nullable final String phone,\n                    @Nullable final String twitterId) {\n        this.deviceId = deviceId;\n        this.email = email;\n        this.facebookId = facebookId;\n        this.phone = phone;\n        this.twitterId = twitterId;\n    }\n\n    public static MetaData getEmpty() {\n        return new MetaData(DEFAULT_DEVICE_ID, DEFAULT_DEVICE_EMAIL,\n                DEFAULT_DEVICE_FACEBOOK_ID, DEFAULT_DEVICE_PHONE, DEFAULT_DEVICE_TWITTER_HANDLE);\n    }\n\n    /**\n     * Method to prepare the meta data in a JSON format that the API will accept.\n     *\n     * @return a JSON formatted representation of the meta data\n     */\n    public JSONObject getMetaJSON() {\n\n        final JSONObject object = new JSONObject();\n\n        try {\n            object.put(EMAIL, getEmail());\n            object.put(PHONE, getPhone());\n            object.put(DEVICE_ID, getDeviceId());\n            object.put(FACEBOOK_ID, getFacebookId());\n            object.put(TWITTER_ID, getTwitterId());\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n\n        return object;\n    }\n\n    public String getDeviceId() {\n        return deviceId;\n    }\n\n    public void setDeviceId(@NonNull final String deviceId) {\n        this.deviceId = deviceId;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(@NonNull final String email) {\n        this.email = email;\n    }\n\n    public String getFacebookId() {\n        return facebookId;\n    }\n\n    public void setFacebookId(@NonNull final String facebookId) {\n        this.facebookId = facebookId;\n    }\n\n    public String getPhone() {\n        return phone;\n    }\n\n    public void setPhone(@NonNull final String phone) {\n        this.phone = phone;\n    }\n\n    public String getTwitterId() {\n        return twitterId;\n    }\n\n    public void setTwitterId(@NonNull final String twitterId) {\n        this.twitterId = twitterId;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/Speaker.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft;\n\nimport android.support.annotation.Nullable;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 16/09/2016.\n */\n\npublic class Speaker {\n\n    public static final String EXTRA_IDENTIFY_OUTCOME = \"extra_identify_outcome\";\n    public static final String EXTRA_IDENTITY_OUTCOME = \"extra_identity_outcome\";\n\n    public static final int ERROR_USER_CANCELLED = 1;\n    public static final int ERROR_AUDIO = 2;\n    public static final int ERROR_NETWORK = 3;\n    public static final int ERROR_FILE = 4;\n\n    public enum Confidence {\n\n        HIGH,\n        NORMAL,\n        LOW,\n        UNDEFINED,\n        ERROR;\n\n        /**\n         * Get the {@link Confidence} from the string equivalent\n         *\n         * @param level the string level\n         * @return the equivalent {@link Confidence}\n         */\n        public static Confidence getConfidence(@Nullable final String level) {\n\n            if (UtilsString.notNaked(level)) {\n\n                if (StringUtils.containsIgnoreCase(HIGH.name(), level)) {\n                    return HIGH;\n                } else if (StringUtils.containsIgnoreCase(NORMAL.name(), level)) {\n                    return NORMAL;\n                } else if (StringUtils.containsIgnoreCase(LOW.name(), level)) {\n                    return LOW;\n                }\n            }\n\n            return UNDEFINED;\n        }\n    }\n\n    public enum Status {\n\n        NOTSTARTED,\n        RUNNING,\n        FAILED,\n        SUCCEEDED,\n        UNDEFINED;\n\n        /**\n         * Get the {@link Status} from the string equivalent\n         *\n         * @param status the string status\n         * @return the equivalent {@link Status}\n         */\n        public static Status getStatus(@Nullable final String status) {\n\n            if (UtilsString.notNaked(status)) {\n\n                if (StringUtils.containsIgnoreCase(NOTSTARTED.name(), status)) {\n                    return NOTSTARTED;\n                } else if (StringUtils.containsIgnoreCase(RUNNING.name(), status)) {\n                    return RUNNING;\n                } else if (StringUtils.containsIgnoreCase(FAILED.name(), status)) {\n                    return FAILED;\n                } else if (StringUtils.containsIgnoreCase(SUCCEEDED.name(), status)) {\n                    return SUCCEEDED;\n                }\n            }\n\n            return UNDEFINED;\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/SpeakerEnrollment.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft;\n\nimport android.os.Process;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.http.CreateIDEnrollment;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class SpeakerEnrollment implements IMic {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SpeakerEnrollment.class.getSimpleName();\n\n    public static final long MINIMUM_AUDIO_TIME = 15000;\n\n    private final SupportedLanguage sl;\n    private final RecognitionMic mic;\n    private final String apiKey;\n    private final String profileId;\n    private final boolean shortAudio;\n\n    /**\n     * Constructor\n     *\n     * @param mic        the initialised {@link RecognitionMic} object\n     * @param sl         the {@link SupportedLanguage}\n     * @param apiKey     the OCP APIM key\n     * @param profileId  of the user\n     * @param shortAudio true if the capture length will be below the recommended threshold, false otherwise\n     */\n    public SpeakerEnrollment(@NonNull final RecognitionMic mic,\n                             @NonNull final SupportedLanguage sl, @NonNull final String apiKey,\n                             @NonNull final String profileId, final boolean shortAudio) {\n        this.mic = mic;\n        this.sl = sl;\n        this.apiKey = apiKey;\n        this.profileId = profileId;\n        this.shortAudio = shortAudio;\n\n        this.mic.setMicListener(this);\n    }\n\n    /**\n     * Start recording the enrollment audio\n     */\n    public void record() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"record\");\n        }\n\n        if (mic.isAvailable()) {\n            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n            mic.startRecording();\n\n            synchronized (mic.getLock()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"mic locked\");\n                }\n\n                while (mic.isRecording()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"mic isRecording\");\n                    }\n\n                    try {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"mic lock waiting\");\n                        }\n                        mic.getLock().wait();\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"InterruptedException\");\n                            e.printStackTrace();\n                        }\n                        break;\n                    }\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"record lock released\");\n            }\n\n            if (mic.isInterrupted()) {\n                onError(Speaker.ERROR_USER_CANCELLED);\n            } else {\n                mic.getRecognitionListener().onComplete();\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"mic unavailable\");\n            }\n\n            onError(Speaker.ERROR_AUDIO);\n        }\n    }\n\n    @Override\n    public void onError(int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        mic.getRecognitionListener().onComplete();\n        Recognition.setState(Recognition.State.IDLE);\n\n        if (mic.isInterrupted()) {\n            error = Speaker.ERROR_USER_CANCELLED;\n        }\n\n        final LocalRequest localRequest = new LocalRequest(mic.getContext());\n        localRequest.setSupportedLanguage(sl);\n        localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n        localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n        localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n\n        switch (error) {\n\n            case Speaker.ERROR_USER_CANCELLED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_USER_CANCELLED\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.cancelled));\n                break;\n            case Speaker.ERROR_NETWORK:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_NETWORK\");\n                }\n                localRequest.setUtterance(PersonalityResponse.getNoNetwork(mic.getContext(), sl));\n                break;\n            case Speaker.ERROR_AUDIO:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_AUDIO\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            case Speaker.ERROR_FILE:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_FILE\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError default\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n        }\n\n        localRequest.execute();\n    }\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n    }\n\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFileWriteComplete: \" + success);\n        }\n\n        if (!mic.isInterrupted()) {\n\n            if (success) {\n\n                final LocalRequest localRequest = new LocalRequest(mic.getContext());\n                localRequest.setSupportedLanguage(sl);\n                localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n                localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.vocal_notify_enroll));\n                localRequest.execute();\n\n                new CreateIDEnrollment(mic, apiKey, profileId, shortAudio, mic.getFile()).stream();\n            } else {\n                onError(Speaker.ERROR_FILE);\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onFileWriteComplete: mic interrupted\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/SpeakerIdentification.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft;\n\nimport android.os.Process;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.http.ValidateID;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class SpeakerIdentification implements IMic {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SpeakerIdentification.class.getSimpleName();\n\n    public static final long MINIMUM_AUDIO_TIME = 3000;\n\n    private final SupportedLanguage sl;\n    private final RecognitionMic mic;\n    private final String apiKey;\n    private final String profileId;\n    private final boolean shortAudio;\n\n    /**\n     * Constructor\n     *\n     * @param mic        the initialised {@link RecognitionMic} object\n     * @param sl         the {@link SupportedLanguage}\n     * @param apiKey     the OCP APIM key\n     * @param profileId  of the user\n     * @param shortAudio true if the capture length will be below the recommended threshold, false otherwise\n     */\n    public SpeakerIdentification(@NonNull final RecognitionMic mic,\n                                 @NonNull final SupportedLanguage sl, @NonNull final String apiKey,\n                                 @NonNull final String profileId, final boolean shortAudio) {\n        this.mic = mic;\n        this.sl = sl;\n        this.apiKey = apiKey;\n        this.profileId = profileId;\n        this.shortAudio = shortAudio;\n\n        this.mic.setMicListener(this);\n    }\n\n    /**\n     * Start recording the enrollment file\n     */\n    public void record() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"record\");\n        }\n\n        if (mic.isAvailable()) {\n            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n            mic.startRecording();\n\n            synchronized (mic.getLock()) {\n                while (mic.isRecording()) {\n                    try {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"mic lock waiting\");\n                        }\n                        mic.getLock().wait();\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"InterruptedException\");\n                            e.printStackTrace();\n                        }\n                        break;\n                    }\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"record lock released\");\n            }\n\n            mic.getRecognitionListener().onComplete();\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"mic unavailable\");\n            }\n\n            onError(Speaker.ERROR_AUDIO);\n        }\n    }\n\n    @Override\n    public void onError(int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        mic.getRecognitionListener().onComplete();\n        Recognition.setState(Recognition.State.IDLE);\n\n        if (mic.isInterrupted()) {\n            error = Speaker.ERROR_USER_CANCELLED;\n        }\n\n        final LocalRequest localRequest = new LocalRequest(mic.getContext());\n        localRequest.setSupportedLanguage(sl);\n        localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n        localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n        localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n\n        switch (error) {\n\n            case Speaker.ERROR_USER_CANCELLED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_USER_CANCELLED\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.cancelled));\n                break;\n            case Speaker.ERROR_NETWORK:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_NETWORK\");\n                }\n                localRequest.setUtterance(PersonalityResponse.getNoNetwork(mic.getContext(), sl));\n                break;\n            case Speaker.ERROR_AUDIO:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_AUDIO\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            case Speaker.ERROR_FILE:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError ERROR_FILE\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError default\");\n                }\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.error_audio));\n                break;\n        }\n\n        localRequest.execute();\n    }\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFileWriteComplete: \" + success);\n        }\n\n        if (!mic.isInterrupted()) {\n\n            if (success) {\n\n                final LocalRequest localRequest = new LocalRequest(mic.getContext());\n                localRequest.setSupportedLanguage(sl);\n                localRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                localRequest.setTTSLocale(SPH.getTTSLocale(mic.getContext()));\n                localRequest.setVRLocale(SPH.getVRLocale(mic.getContext()));\n                localRequest.setUtterance(SaiyResourcesHelper.getStringResource(mic.getContext(), sl,\n                        R.string.vocal_notify_verify));\n                localRequest.execute();\n\n                new ValidateID(mic, sl, apiKey, profileId, shortAudio, mic.getFile()).stream();\n            } else {\n                onError(Speaker.ERROR_FILE);\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onFileWriteComplete: mic interrupted\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/containers/EnrollmentID.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.containers;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class EnrollmentID {\n\n    @SerializedName(\"identificationProfileId\")\n    private final String id;\n\n    public EnrollmentID(final String id) {\n        this.id = id;\n    }\n\n    public String getId() {\n        return id;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/containers/OperationStatus.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.containers;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 15/09/2016.\n */\n\npublic class OperationStatus {\n\n    @SerializedName(\"status\")\n    private String status;\n\n    @SerializedName(\"createdDateTime\")\n    private String created;\n\n    @SerializedName(\"lastActionDateTime\")\n    private String lastAction;\n\n    @SerializedName(\"message\")\n    private String message;\n\n    @SerializedName(\"processingResult\")\n    private ProcessingResult processingResult;\n\n    public OperationStatus(final String created, final String lastAction, final String message,\n                           final ProcessingResult processingResult, final String status) {\n        this.created = created;\n        this.lastAction = lastAction;\n        this.message = message;\n        this.processingResult = processingResult;\n        this.status = status;\n    }\n\n    public String getCreated() {\n        return created;\n    }\n\n    public void setCreated(final String created) {\n        this.created = created;\n    }\n\n    public String getLastAction() {\n        return lastAction;\n    }\n\n    public void setLastAction(final String lastAction) {\n        this.lastAction = lastAction;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(final String message) {\n        this.message = message;\n    }\n\n    public ProcessingResult getProcessingResult() {\n        return processingResult;\n    }\n\n    public void setProcessingResult(final ProcessingResult processingResult) {\n        this.processingResult = processingResult;\n    }\n\n    public String getStatus() {\n        return status;\n    }\n\n    public void setStatus(final String status) {\n        this.status = status;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/containers/ProcessingResult.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.containers;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 15/09/2016.\n */\n\npublic class ProcessingResult {\n\n    @SerializedName(\"enrollmentStatus\")\n    private String enrollmentStatus;\n\n    @SerializedName(\"speechTime\")\n    private double speechTime;\n\n    @SerializedName(\"enrollmentSpeechTime\")\n    private double enrollmentSpeechTime;\n\n    @SerializedName(\"remainingEnrollmentSpeechTime\")\n    private double remainingSpeechTime;\n\n    @SerializedName(\"identifiedProfileId\")\n    private String profileId;\n\n    @SerializedName(\"confidence\")\n    private String confidence;\n\n    public ProcessingResult(final String confidence, final double enrollmentSpeechTime,\n                            final String enrollmentStatus, final String profileId,\n                            final double remainingSpeechTime, final double speechTime) {\n        this.confidence = confidence;\n        this.enrollmentSpeechTime = enrollmentSpeechTime;\n        this.enrollmentStatus = enrollmentStatus;\n        this.profileId = profileId;\n        this.remainingSpeechTime = remainingSpeechTime;\n        this.speechTime = speechTime;\n    }\n\n    public String getConfidence() {\n        return confidence;\n    }\n\n    public void setConfidence(final String confidence) {\n        this.confidence = confidence;\n    }\n\n    public double getEnrollmentSpeechTime() {\n        return enrollmentSpeechTime;\n    }\n\n    public void setEnrollmentSpeechTime(final double enrollmentSpeechTime) {\n        this.enrollmentSpeechTime = enrollmentSpeechTime;\n    }\n\n    public String getEnrollmentStatus() {\n        return enrollmentStatus;\n    }\n\n    public void setEnrollmentStatus(final String enrollmentStatus) {\n        this.enrollmentStatus = enrollmentStatus;\n    }\n\n    public String getProfileId() {\n        return profileId;\n    }\n\n    public void setProfileId(final String profileId) {\n        this.profileId = profileId;\n    }\n\n    public double getRemainingSpeechTime() {\n        return remainingSpeechTime;\n    }\n\n    public void setRemainingSpeechTime(final double remainingSpeechTime) {\n        this.remainingSpeechTime = remainingSpeechTime;\n    }\n\n    public double getSpeechTime() {\n        return speechTime;\n    }\n\n    public void setSpeechTime(final double speechTime) {\n        this.speechTime = speechTime;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/containers/ProfileItem.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.containers;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class ProfileItem {\n\n    @SerializedName(\"identificationProfileId\")\n    private final String id;\n\n    @SerializedName(\"locale\")\n    private String locale;\n\n    @SerializedName(\"createdDateTime\")\n    private String created;\n\n    @SerializedName(\"lastActionDateTime\")\n    private String lastAction;\n\n    @SerializedName(\"enrollmentStatus\")\n    private String status;\n\n    @SerializedName(\"enrollmentSpeechTime\")\n    private double speechTime;\n\n    @SerializedName(\"remainingEnrollmentSpeechTime\")\n    private double remainingSpeechTime;\n\n    public ProfileItem(final String created, final String id, final String locale, final String lastAction,\n                       final String status, final double speechTime, final double remainingSpeechTime) {\n        this.created = created;\n        this.id = id;\n        this.locale = locale;\n        this.lastAction = lastAction;\n        this.status = status;\n        this.speechTime = speechTime;\n        this.remainingSpeechTime = remainingSpeechTime;\n    }\n\n    public ProfileItem(@NonNull final String id) {\n        this.id = id;\n    }\n\n    public String getCreated() {\n        return created;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getLastAction() {\n        return lastAction;\n    }\n\n    public String getLocale() {\n        return locale;\n    }\n\n    public double getRemainingSpeechTime() {\n        return remainingSpeechTime;\n    }\n\n    public double getSpeechTime() {\n        return speechTime;\n    }\n\n    public String getStatus() {\n        return status;\n    }\n\n    public void setCreated(final String created) {\n        this.created = created;\n    }\n\n    public void setLastAction(final String lastAction) {\n        this.lastAction = lastAction;\n    }\n\n    public void setLocale(final String locale) {\n        this.locale = locale;\n    }\n\n    public void setRemainingSpeechTime(final double remainingSpeechTime) {\n        this.remainingSpeechTime = remainingSpeechTime;\n    }\n\n    public void setSpeechTime(final double speechTime) {\n        this.speechTime = speechTime;\n    }\n\n    public void setStatus(final String status) {\n        this.status = status;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/containers/ProfileList.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.containers;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class ProfileList {\n\n    private final List<ProfileItem> items;\n\n    public ProfileList(final List<ProfileItem> items) {\n        this.items = items;\n    }\n\n    public List<ProfileItem> getItems() {\n        return items;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/CreateIDEnrollment.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.net.ParseException;\nimport android.os.Process;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.net.UnknownHostException;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.OperationStatus;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.user.SaiyAccountHelper;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 15/09/2016.\n */\n\npublic class CreateIDEnrollment {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CreateIDEnrollment.class.getSimpleName();\n\n    private static final String ENROLLMENT_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identificationProfiles/\";\n    private static final String ENROLLMENT_URL_EXTRA = \"/enroll?shortAudio=\";\n\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String CONTENT_TYPE_AUDIO_PARAMS = \"audio/l16; rate=16000\";\n    private static final int HTTP_ACCEPTED = 202;\n    private static final String OPERATION_LOCATION = \"Operation-Location\";\n\n    private static final long FETCH_DELAY = 6000;\n    private static final long FETCH_DELAY_EXTENDED = 12000;\n\n    private volatile HttpsURLConnection urlConnection;\n    private volatile OutputStream outputStream;\n    private FileInputStream fileInputStream;\n\n    private volatile boolean retry = true;\n\n    private final String apiKey;\n    private final String profileId;\n    private final RecognitionMic mic;\n    private final boolean shortAudio;\n    private final File file;\n\n    /**\n     * Constructor\n     *\n     * @param mic       the initialised {@link RecognitionMic} object\n     * @param apiKey    the api key\n     * @param profileId of the user.\n     */\n    public CreateIDEnrollment(@NonNull final RecognitionMic mic, @NonNull final String apiKey,\n                              @NonNull final String profileId, final boolean shortAudio,\n                              @NonNull final File file) {\n        this.mic = mic;\n        this.apiKey = apiKey;\n        this.profileId = profileId;\n        this.shortAudio = shortAudio;\n        this.file = file;\n    }\n\n    /**\n     * Start streaming the Microsoft enrollment servers\n     */\n    public void stream() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stream\");\n        }\n\n        final Thread httpThread = new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                try {\n\n                    final String url = ENROLLMENT_URL + URLEncoder.encode(profileId, Constants.ENCODING_UTF8)\n                            + ENROLLMENT_URL_EXTRA + String.valueOf(shortAudio);\n\n                    urlConnection = (HttpsURLConnection) new URL(url).openConnection();\n                    urlConnection.setRequestMethod(Constants.HTTP_POST);\n                    urlConnection.setRequestProperty(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, CONTENT_TYPE_AUDIO_PARAMS);\n                    urlConnection.setUseCaches(false);\n                    urlConnection.setDoOutput(true);\n                    urlConnection.connect();\n\n                    outputStream = urlConnection.getOutputStream();\n                    fileInputStream = new FileInputStream(file);\n\n                    final byte[] buffer = new byte[1024];\n\n                    int len;\n                    while ((len = fileInputStream.read(buffer)) != -1) {\n                        outputStream.write(buffer, 0, len);\n                    }\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"requesting response\");\n                    }\n\n                    final int responseCode = urlConnection.getResponseCode();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n                    }\n\n                    if (responseCode != HTTP_ACCEPTED) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"ErrorStream: \"\n                                    + UtilsString.streamToString(urlConnection.getErrorStream()));\n                        }\n\n                        mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"response: HTTP_ACCEPTED\");\n                        }\n\n                        final String statusUrl = urlConnection.getHeaderField(OPERATION_LOCATION);\n\n                        if (UtilsString.notNaked(statusUrl)) {\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"response statusUrl: \" + statusUrl);\n                            }\n\n                            final TimerTask timerTask = new TimerTask() {\n                                @Override\n                                public void run() {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"timerTask: fetching operation status\");\n                                    }\n                                    checkResult(new FetchIDOperation(mic.getContext(), apiKey, statusUrl)\n                                            .getStatus(), statusUrl);\n                                }\n                            };\n\n                            new Timer().schedule(timerTask, FETCH_DELAY);\n\n                        } else {\n                            mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                        }\n                    }\n\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final UnsupportedEncodingException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnsupportedEncodingException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final ParseException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"ParseException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final UnknownHostException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnknownHostException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IOException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } finally {\n                    closeConnection();\n                }\n            }\n        };\n\n        httpThread.start();\n    }\n\n    /**\n     * Check the result of the enrollment request\n     *\n     * @param statusPair the {@link Pair} with the first parameter denoting success and the\n     *                   second the {@link OperationStatus} object\n     * @param statusUrl  url to recheck the status if required\n     */\n    private void checkResult(@NonNull final Pair<Boolean, OperationStatus> statusPair,\n                             @NonNull final String statusUrl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkResult\");\n        }\n\n        if (statusPair.first && statusPair.second != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"statusPair: have data\");\n            }\n\n            switch (Speaker.Status.getStatus(statusPair.second.getStatus())) {\n\n                case SUCCEEDED:\n\n                    final boolean success = SaiyAccountHelper.updateEnrollmentStatus(\n                            mic.getContext(), statusPair.second, profileId);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"account updated: \" + success);\n                    }\n\n                    NotificationHelper.createIdentificationNotification(mic.getContext(),\n                            Condition.CONDITION_IDENTITY, success, null);\n                    break;\n                case FAILED:\n                case UNDEFINED:\n                    NotificationHelper.createIdentificationNotification(mic.getContext(),\n                            Condition.CONDITION_IDENTITY, false, null);\n                    break;\n                case NOTSTARTED:\n                case RUNNING:\n\n                    if (retry) {\n                        retry = false;\n\n                        final TimerTask timerTask = new TimerTask() {\n                            @Override\n                            public void run() {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"timerTask: fetching operation status\");\n                                }\n                                checkResult(new FetchIDOperation(mic.getContext(), apiKey, statusUrl)\n                                        .getStatus(), statusUrl);\n                            }\n                        };\n\n                        new Timer().schedule(timerTask, FETCH_DELAY_EXTENDED);\n\n                    } else {\n                        NotificationHelper.createIdentificationNotification(mic.getContext(),\n                                Condition.CONDITION_IDENTITY, false, null);\n                    }\n\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"statusPair false\");\n            }\n            NotificationHelper.createIdentificationNotification(mic.getContext(),\n                    Condition.CONDITION_IDENTITY, false, null);\n        }\n    }\n\n    private void closeConnection() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"closeConnection\");\n        }\n\n        if (fileInputStream != null) {\n\n            try {\n                fileInputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/CreateIDProfile.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.EnrollmentID;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to get an initial enrollment id, which will be used to enroll and validate the user.\n * <p>\n * This request is always synchronous, as it is an 'on-demand' requirement.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class CreateIDProfile {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CreateIDProfile.class.getSimpleName();\n\n    private static final String CREATE_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identificationProfiles\";\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n    private static final String JSON_HEADER_ACCEPT = \"accept\";\n    private static final String JSON_HEADER_VALUE_ACCEPT = \"application/json\";\n    private static final String LOCALE_PARAM = \"locale\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final String apiKey;\n    private final Locale locale;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param apiKey   the api key\n     * @param locale   the locale of the user. Currently unused as only en-us is supported\n     */\n    public CreateIDProfile(@NonNull final Context mContext, @NonNull final String apiKey,\n                           @NonNull final Locale locale) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n        this.locale = locale;\n    }\n\n    /**\n     * Method to get an enrollment id.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link EnrollmentID} object, containing the id. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, EnrollmentID> getID() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getID\");\n        }\n\n        final long then = System.nanoTime();\n\n        final JSONObject object = new JSONObject();\n\n        try {\n            object.put(LOCALE_PARAM, \"en-us\");\n        } catch (final JSONException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: JSONException\");\n                e.printStackTrace();\n            }\n        }\n\n        final RequestFuture<JSONObject> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST, CREATE_URL, object, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            CreateIDProfile.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(JSON_HEADER_ACCEPT, JSON_HEADER_VALUE_ACCEPT);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                return params;\n            }\n        };\n\n        jsonObjReq.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(jsonObjReq);\n\n        JSONObject response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getID: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response.toString());\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final EnrollmentID enrollmentID = gson.fromJson(response.toString(), EnrollmentID.class);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onResponse: getId: \" + enrollmentID.getId());\n            }\n\n            return new Pair<>(true, enrollmentID);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/DeleteIDProfile.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.StringRequest;\nimport com.android.volley.toolbox.Volley;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 07/09/2016.\n */\n\npublic class DeleteIDProfile {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = DeleteIDProfile.class.getSimpleName();\n\n    private static final String DELETE_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identificationProfiles/\";\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String CHARSET = \"Accept-Charset\";\n\n    private final Context mContext;\n    private final String apiKey;\n    private final String profileId;\n\n    /**\n     * Constructor\n     *\n     * @param mContext  the application context\n     * @param apiKey    the api key\n     * @param profileId to delete\n     */\n    public DeleteIDProfile(@NonNull final Context mContext, @NonNull final String apiKey,\n                           @NonNull final String profileId) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n        this.profileId = profileId;\n    }\n\n    public void delete() {\n\n        final long then = System.nanoTime();\n\n        String url = null;\n\n        try {\n            url = DELETE_URL + URLEncoder.encode(profileId, Constants.ENCODING_UTF8);\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"delete: UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        }\n\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final StringRequest stringRequest = new StringRequest(Request.Method.DELETE, url,\n                new Response.Listener<String>() {\n                    @Override\n                    public void onResponse(final String response) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onResponse: success\");\n                        }\n                        queue.stop();\n                    }\n                },\n\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            DeleteIDProfile.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, Constants.ENCODING_UTF8);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                return params;\n            }\n        };\n\n        stringRequest.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(stringRequest);\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/FetchIDOperation.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.json.JSONObject;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.OperationStatus;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProcessingResult;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to check the enrollment status of the user, post receipt of audio.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class FetchIDOperation {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FetchIDOperation.class.getSimpleName();\n\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n    private static final String JSON_HEADER_ACCEPT = \"accept\";\n    private static final String JSON_HEADER_VALUE_ACCEPT = \"application/json\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final String apiKey;\n    private final String url;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param apiKey   the api key\n     * @param url      of the operation status.\n     */\n    public FetchIDOperation(@NonNull final Context mContext, @NonNull final String apiKey,\n                            @NonNull final String url) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n        this.url = url;\n    }\n\n    /**\n     * Method to get an enrollment id.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link OperationStatus} object. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, OperationStatus> getStatus() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getStatus\");\n        }\n\n        final long then = System.nanoTime();\n\n        final RequestFuture<JSONObject> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET, url, null, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            FetchIDOperation.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(JSON_HEADER_ACCEPT, JSON_HEADER_VALUE_ACCEPT);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                return params;\n            }\n        };\n\n        jsonObjReq.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(jsonObjReq);\n\n        JSONObject response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getStatus: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getStatus: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getStatus: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response.toString());\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final OperationStatus operationStatus = gson.fromJson(response.toString(), OperationStatus.class);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: getStatus: \" + operationStatus.getStatus());\n                MyLog.i(CLS_NAME, \"response: getCreated: \" + operationStatus.getCreated());\n                MyLog.i(CLS_NAME, \"response: getLastAction: \" + operationStatus.getLastAction());\n                MyLog.i(CLS_NAME, \"response: getMessage: \" + operationStatus.getMessage());\n\n                final ProcessingResult processingResult = operationStatus.getProcessingResult();\n\n                if (processingResult != null) {\n                    MyLog.i(CLS_NAME, \"response: getEnrollmentStatus: \" + processingResult.getEnrollmentStatus());\n                    MyLog.i(CLS_NAME, \"response: getConfidence: \" + processingResult.getConfidence());\n                    MyLog.i(CLS_NAME, \"response: getProfileId: \" + processingResult.getProfileId());\n                    MyLog.i(CLS_NAME, \"response: getEnrollmentSpeechTime: \" + processingResult.getEnrollmentSpeechTime());\n                    MyLog.i(CLS_NAME, \"response: getRemainingSpeechTime: \" + processingResult.getRemainingSpeechTime());\n                    MyLog.i(CLS_NAME, \"response: getSpeechTime: \" + processingResult.getSpeechTime());\n                } else {\n                    MyLog.i(CLS_NAME, \"response: processingResult: null\");\n                }\n            }\n\n            return new Pair<>(true, operationStatus);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/FetchIDProfile.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.JsonObjectRequest;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport org.json.JSONObject;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileItem;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to get an initial enrollment id, which will be used to enroll and validate the user.\n * <p>\n * This request is always synchronous, as it is an 'on-demand' requirement.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class FetchIDProfile {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CreateIDProfile.class.getSimpleName();\n\n    private static final String FETCH_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identificationProfiles/\";\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n    private static final String JSON_HEADER_ACCEPT = \"accept\";\n    private static final String JSON_HEADER_VALUE_ACCEPT = \"application/json\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n\n    private final Context mContext;\n    private final String apiKey;\n    private final String profileId;\n\n    /**\n     * Constructor\n     *\n     * @param mContext  the application context\n     * @param apiKey    the api key\n     * @param profileId the profile id of the user.\n     */\n    public FetchIDProfile(@NonNull final Context mContext, @NonNull final String apiKey,\n                          @NonNull final String profileId) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n        this.profileId = profileId;\n    }\n\n    /**\n     * Method to get an enrollment id.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link ProfileItem} object. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, ProfileItem> getProfile() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getProfile\");\n        }\n\n        final long then = System.nanoTime();\n\n        String url = null;\n\n        try {\n            url = FETCH_URL + URLEncoder.encode(profileId, ENCODING);\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfile: UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        }\n\n        final RequestFuture<JSONObject> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET, url, null, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            FetchIDProfile.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(JSON_HEADER_ACCEPT, JSON_HEADER_VALUE_ACCEPT);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                return params;\n            }\n        };\n\n        jsonObjReq.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(jsonObjReq);\n\n        JSONObject response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfile: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfile: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfile: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response.toString());\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final ProfileItem profileItem = gson.fromJson(response.toString(), ProfileItem.class);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: getId: \" + profileItem.getId());\n            }\n\n            return new Pair<>(true, profileItem);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/ListIDProfiles.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.StringRequest;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.io.UnsupportedEncodingException;\nimport java.lang.reflect.Type;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileList;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileItem;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to get a list of enrollment ids.\n * <p>\n * This request is always synchronous, as it is an 'on-demand' requirement.\n * <p>\n * Created by benrandall76@gmail.com on 08/06/2016.\n */\npublic class ListIDProfiles {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ListIDProfiles.class.getSimpleName();\n\n    private static final String LIST_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identificationProfiles\";\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String CHARSET = \"Accept-Charset\";\n\n    /**\n     * The initial volley request can be inexplicably slow\n     */\n    private static final int OTT_MULTIPLIER = 20;\n    private static final long THREAD_TIMEOUT = 20L;\n\n    private final Context mContext;\n    private final String apiKey;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param apiKey   the api key\n     */\n    public ListIDProfiles(@NonNull final Context mContext, @NonNull final String apiKey) {\n        this.apiKey = apiKey;\n        this.mContext = mContext.getApplicationContext();\n    }\n\n    /**\n     * Method to get all enrollment profile information.\n     *\n     * @return an {@link Pair} of which the first parameter will denote success and the second an\n     * {@link ProfileList} object, containing the list of profiles. If the request was unsuccessful,\n     * the second parameter may be null.\n     */\n    public Pair<Boolean, ProfileList> getProfiles() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getProfiles\");\n        }\n\n        final long then = System.nanoTime();\n\n        final RequestFuture<String> future = RequestFuture.newFuture();\n        final RequestQueue queue = Volley.newRequestQueue(mContext);\n        queue.start();\n\n        final StringRequest request = new StringRequest(Request.Method.GET, LIST_URL, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            ListIDProfiles.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(CHARSET, ENCODING);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                return params;\n            }\n        };\n\n        request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * OTT_MULTIPLIER,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(request);\n\n        String response = null;\n\n        try {\n            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfiles: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfiles: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProfiles: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (response != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"response: \" + response);\n\n            }\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final Type type = new TypeToken<List<ProfileItem>>() {\n            }.getType();\n\n            final ProfileList profileList = new ProfileList(gson.<List<ProfileItem>>fromJson(response,type));\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onResponse: profileList size: \" + profileList.getItems().size());\n            }\n\n            return new Pair<>(true, profileList);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/identity/provider/microsoft/http/ValidateID.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.identity.provider.microsoft.http;\n\nimport android.net.ParseException;\nimport android.os.Process;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.net.UnknownHostException;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.OperationStatus;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 15/09/2016.\n */\n\npublic class ValidateID implements IMic {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ValidateID.class.getSimpleName();\n\n    private static final String IDENTIFICATION_URL = \"https://westus.api.cognitive.microsoft.com/spid/v1.0/identify?identificationProfileIds=\";\n    private static final String IDENTIFICATION_URL_EXTRA = \"&shortAudio=\";\n\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String CONTENT_TYPE_AUDIO_PARAMS = \"audio/l16; rate=16000\";\n    private static final int HTTP_ACCEPTED = 202;\n    private static final String OPERATION_LOCATION = \"Operation-Location\";\n\n    private static final long FETCH_DELAY = 6000;\n    private static final long FETCH_DELAY_EXTENDED = 12000;\n\n    private volatile HttpsURLConnection urlConnection;\n    private volatile OutputStream outputStream;\n    private FileInputStream fileInputStream;\n\n    private volatile boolean retry = true;\n\n    private final String apiKey;\n    private final String profileId;\n    private final RecognitionMic mic;\n    private final SupportedLanguage sl;\n    private final boolean shortAudio;\n    private final File file;\n\n    /**\n     * Constructor\n     *\n     * @param mic       the initialised {@link RecognitionMic} object\n     * @param sl        the {@link SupportedLanguage} object\n     * @param apiKey    the api key\n     * @param profileId of the user.\n     */\n    public ValidateID(@NonNull final RecognitionMic mic, @NonNull final SupportedLanguage sl,\n                      @NonNull final String apiKey,\n                      @NonNull final String profileId, final boolean shortAudio,\n                      @NonNull final File file) {\n        this.mic = mic;\n        this.apiKey = apiKey;\n        this.profileId = profileId;\n        this.sl = sl;\n        this.shortAudio = shortAudio;\n        this.file = file;\n    }\n\n    /**\n     * Start streaming the Microsoft enrollment servers\n     */\n    public void stream() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stream\");\n        }\n\n        final Thread httpThread = new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                try {\n\n                    final String url = IDENTIFICATION_URL + URLEncoder.encode(profileId, Constants.ENCODING_UTF8)\n                            + IDENTIFICATION_URL_EXTRA + String.valueOf(shortAudio);\n\n                    urlConnection = (HttpsURLConnection) new URL(url).openConnection();\n                    urlConnection.setRequestMethod(Constants.HTTP_POST);\n                    urlConnection.setRequestProperty(OCP_SUBSCRIPTION_KEY_HEADER, apiKey);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, CONTENT_TYPE_AUDIO_PARAMS);\n                    urlConnection.setUseCaches(false);\n                    urlConnection.setDoOutput(true);\n                    urlConnection.connect();\n\n                    outputStream = urlConnection.getOutputStream();\n                    fileInputStream = new FileInputStream(file);\n\n                    final byte[] buffer = new byte[1024];\n\n                    int len;\n                    while ((len = fileInputStream.read(buffer)) != -1) {\n                        outputStream.write(buffer, 0, len);\n                    }\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"requesting response\");\n                    }\n\n                    final int responseCode = urlConnection.getResponseCode();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n                    }\n\n                    if (responseCode != HTTP_ACCEPTED) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"ErrorStream: \"\n                                    + UtilsString.streamToString(urlConnection.getErrorStream()));\n                        }\n\n                        mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"response: HTTP_ACCEPTED\");\n                        }\n\n                        final String statusUrl = urlConnection.getHeaderField(OPERATION_LOCATION);\n\n                        if (UtilsString.notNaked(statusUrl)) {\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"response statusUrl: \" + statusUrl);\n                            }\n\n                            final TimerTask timerTask = new TimerTask() {\n                                @Override\n                                public void run() {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"timerTask: fetching operation status\");\n                                    }\n                                    checkResult(new FetchIDOperation(mic.getContext(), apiKey, statusUrl)\n                                            .getStatus(), statusUrl);\n                                }\n                            };\n\n                            new Timer().schedule(timerTask, FETCH_DELAY);\n\n                        } else {\n                            mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                        }\n                    }\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final UnsupportedEncodingException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnsupportedEncodingException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final ParseException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"ParseException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final UnknownHostException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnknownHostException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IOException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                    mic.getMicListener().onError(Speaker.ERROR_NETWORK);\n                } finally {\n                    closeConnection();\n                }\n            }\n        };\n\n        httpThread.start();\n    }\n\n    /**\n     * Check the result of the validation request\n     *\n     * @param statusPair the {@link Pair} with the first parameter denoting success and the\n     *                   second the {@link OperationStatus} object\n     * @param statusUrl  url to recheck the status if required\n     */\n    private void checkResult(@NonNull final Pair<Boolean, OperationStatus> statusPair,\n                             @NonNull final String statusUrl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkResult\");\n        }\n\n        if (statusPair.first && statusPair.second != null && statusPair.second.getProcessingResult() != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"checkResult: have data\");\n            }\n\n            switch (Speaker.Status.getStatus(statusPair.second.getStatus())) {\n\n                case SUCCEEDED:\n                    NotificationHelper.createIdentificationNotification(mic.getContext(),\n                            Condition.CONDITION_IDENTIFY, true, Speaker.Confidence.getConfidence(\n                                    statusPair.second.getProcessingResult().getConfidence()));\n                    break;\n                case FAILED:\n                case UNDEFINED:\n                    NotificationHelper.createIdentificationNotification(mic.getContext(),\n                            Condition.CONDITION_IDENTIFY, true, Speaker.Confidence.ERROR);\n                    break;\n                case NOTSTARTED:\n                case RUNNING:\n\n                    if (retry) {\n                        retry = false;\n\n                        final TimerTask timerTask = new TimerTask() {\n                            @Override\n                            public void run() {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"timerTask: fetching operation status\");\n                                }\n                                checkResult(new FetchIDOperation(mic.getContext(), apiKey, statusUrl)\n                                        .getStatus(), statusUrl);\n                            }\n                        };\n\n                        new Timer().schedule(timerTask, FETCH_DELAY_EXTENDED);\n\n                    } else {\n                        NotificationHelper.createIdentificationNotification(mic.getContext(),\n                                Condition.CONDITION_IDENTIFY, true, Speaker.Confidence.ERROR);\n                    }\n\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"statusPair false\");\n            }\n            NotificationHelper.createIdentificationNotification(mic.getContext(),\n                    Condition.CONDITION_IDENTIFY, true, Speaker.Confidence.ERROR);\n        }\n    }\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n    }\n\n    @Override\n    public void onError(final int error) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onError\");\n        }\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFileWriteComplete: \" + success);\n        }\n    }\n\n    private void closeConnection() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"closeConnection\");\n        }\n\n        if (fileInputStream != null) {\n\n            try {\n                fileInputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/WolframAlphaCognitive.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha;\n\nimport android.net.ParseException;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.net.UnknownHostException;\n\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve.ResolveWolframAlpha;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve.WolframAlphaRequest;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve.WolframAlphaResponse;\nimport ai.saiy.android.configuration.WolframConfiguration;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\npublic class WolframAlphaCognitive {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = WolframAlphaCognitive.class.getSimpleName();\n\n    private static final String REINTERPRET = \"&reinterpret=true\";\n    private static final String FORMAT = \"&format=plaintext\";\n    private static final String PARSE_TIMEOUT = \"&parsetimeout=10\";\n    private static final String SCAN_TIMEOUT = \"&scantimeout=2\";\n    private static final String FORMAT_TIMEOUT = \"&formattimeout=5\";\n    private static final String APP_ID = \"&appid=\";\n    private static final String IGNORE_CASE = \"&ignorecase=true\";\n    private static final String ENCODING = \"utf-8\";\n\n    private static final String POD_INDEX = \"&podindex=1,2,3\";\n    private static final String LAT_LONG = \"&latlong=\";\n\n    // TODO - need to contact W|A\n    private static final String SIGNATURE = \"sig\";\n\n    private String response;\n    private HttpURLConnection urlConnection;\n\n    /**\n     * Perform a synchronous validation request to Wolfram Alpha\n     *\n     * @param text the text to be queried\n     * @return true if the request passed validation, false otherwise\n     */\n    public boolean validate(@NonNull final String text) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validate\");\n        }\n\n        final long then = System.nanoTime();\n\n        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());\n\n        try {\n\n            final String urlString = WolframConfiguration.VALIDATE_URL + URLEncoder.encode(text, ENCODING)\n                    + APP_ID + WolframConfiguration.WOLFRAM_APP_ID + FORMAT + IGNORE_CASE;\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"url:\" + urlString);\n            }\n\n            urlConnection = (HttpURLConnection) new URL(urlString).openConnection();\n\n            urlConnection.setRequestMethod(\"GET\");\n            urlConnection.setDoInput(true);\n            urlConnection.connect();\n\n            final int responseCode = urlConnection.getResponseCode();\n\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n            }\n\n            if (responseCode != HttpURLConnection.HTTP_OK) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"error stream: \"\n                            + UtilsString.streamToString(urlConnection.getErrorStream()));\n                }\n            } else {\n                response = UtilsString.streamToString(urlConnection.getInputStream());\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"response: \" + response);\n                }\n            }\n        } catch (final MalformedURLException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"MalformedURLException\");\n                e.printStackTrace();\n            }\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        } catch (final ParseException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"ParseException\");\n                e.printStackTrace();\n            }\n        } catch (final UnknownHostException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"UnknownHostException\");\n                e.printStackTrace();\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"IOException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            closeConnection();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (UtilsString.notNaked(response)) {\n            return new ResolveWolframAlpha().validate(response);\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return false;\n        }\n    }\n\n\n    /**\n     * Perform a synchronous Wolfram Alpha request\n     *\n     * @param request the {@link WolframAlphaRequest} object\n     * @return a {@link Pair} with the first parameter donating success and the second the result\n     */\n    public Pair<Boolean, WolframAlphaResponse> execute(@NonNull final WolframAlphaRequest request) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n\n        final long then = System.nanoTime();\n\n        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());\n\n        try {\n\n            final String urlString = WolframConfiguration.QUERY_URL\n                    + URLEncoder.encode(request.getQuery(), ENCODING) + APP_ID\n                    + WolframConfiguration.WOLFRAM_APP_ID + FORMAT + PARSE_TIMEOUT + SCAN_TIMEOUT\n                    + FORMAT_TIMEOUT + REINTERPRET + IGNORE_CASE;\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"url:\" + urlString);\n            }\n\n            urlConnection = (HttpURLConnection) new URL(urlString).openConnection();\n\n            urlConnection.setRequestMethod(\"GET\");\n            urlConnection.setDoInput(true);\n            urlConnection.connect();\n\n            final int responseCode = urlConnection.getResponseCode();\n\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n            }\n\n            if (responseCode != HttpURLConnection.HTTP_OK) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"error stream: \"\n                            + UtilsString.streamToString(urlConnection.getErrorStream()));\n                }\n            } else {\n                response = UtilsString.streamToString(urlConnection.getInputStream());\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"response: \" + response);\n                }\n            }\n        } catch (final MalformedURLException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"MalformedURLException\");\n                e.printStackTrace();\n            }\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        } catch (final ParseException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"ParseException\");\n                e.printStackTrace();\n            }\n        } catch (final UnknownHostException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"UnknownHostException\");\n                e.printStackTrace();\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"IOException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            closeConnection();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (UtilsString.notNaked(response)) {\n            return new ResolveWolframAlpha().resolve(request, response);\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"response: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    private void closeConnection() {\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Alternative.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\nimport org.simpleframework.xml.Text;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"alternative\")\npublic class Alternative {\n\n    @Attribute(name = \"score\", required = false)\n    private double score;\n\n    @Attribute(name = \"level\", required = false)\n    private String level;\n\n    private String text;\n\n    public Alternative() {\n    }\n\n    public Alternative(@Attribute(name = \"level\", required = false) final String level,\n                       @Attribute(name = \"score\", required = false) final double score,\n                       @Text final String text) {\n        this.level = level;\n        this.score = score;\n        this.text = text;\n    }\n\n    public String getLevel() {\n        return level;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    @Text\n    public String getText() {\n        return text;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Assumption.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"assumption\")\npublic class Assumption {\n\n    @Attribute(name = \"type\")\n    private String type;\n\n    @Attribute(name = \"word\", required = false)\n    private String word;\n\n    @Attribute(name = \"template\", required = false)\n    private String template;\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @Attribute(name = \"desc\", required = false)\n    private String desc;\n\n    @Attribute(name = \"current\", required = false)\n    private long current;\n\n    @ElementList(inline = true, name = \"value\")\n    private List<Value> values;\n\n    public Assumption() {\n    }\n\n    public Assumption(@Attribute(name = \"count\") final long count,\n                      @Attribute(name = \"template\", required = false) final String template,\n                      @Attribute(name = \"type\") final String type,\n                      @ElementList(inline = true, name = \"value\") final List<Value> values,\n                      @Attribute(name = \"word\", required = false) final String word,\n                      @Attribute(name = \"current\", required = false) final long current,\n                      @Attribute(name = \"desc\", required = false) final String desc) {\n        this.count = count;\n        this.template = template;\n        this.type = type;\n        this.values = values;\n        this.word = word;\n        this.current = current;\n        this.desc = desc;\n    }\n\n    public long getCurrent() {\n        return current;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public String getTemplate() {\n        return template;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public List<Value> getValues() {\n        return values;\n    }\n\n    public String getWord() {\n        return word;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Assumptions.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"assumptions\")\npublic class Assumptions {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @ElementList(inline = true, name = \"assumption\")\n    private List<Assumption> assumptions;\n\n    public Assumptions() {\n    }\n\n    public Assumptions(@ElementList(inline = true, name = \"assumption\") final List<Assumption> assumptions,\n                       @Attribute(name = \"count\") final long count) {\n        this.assumptions = assumptions;\n        this.count = count;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<Assumption> getAssumptions() {\n        return assumptions;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Definition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"definition\")\npublic class Definition {\n\n    @Attribute(name = \"word\", required = false)\n    private String word;\n\n    @Attribute(name = \"desc\", required = false)\n    private String desc;\n\n    public Definition() {\n    }\n\n    public Definition(@Attribute(name = \"desc\", required = false) final String desc,\n                      @Attribute(name = \"word\", required = false) final String word) {\n        this.desc = desc;\n        this.word = word;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public String getWord() {\n        return word;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Definitions.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"definitions\")\npublic class Definitions {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @ElementList(inline = true, name = \"definition\")\n    private List<Definition> definitions;\n\n    public Definitions() {\n    }\n\n    public Definitions(@Attribute(name = \"count\") final long count,\n                       @ElementList(inline = true, name = \"definition\") final List<Definition> definitions) {\n        this.count = count;\n        this.definitions = definitions;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<Definition> getDefinitions() {\n        return definitions;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Info.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"info\")\npublic class Info {\n\n    @Element(name = \"link\", required = false)\n    private Link link;\n\n    @Element(name = \"units\", required = false)\n    private Units units;\n\n    public Info() {\n    }\n\n    public Info(@Element(name = \"link\", required = false) final Link link,\n                @Element(name = \"units\", required = false) final Units units) {\n        this.link = link;\n        this.units = units;\n    }\n\n    public boolean hasUnits() {\n        return units != null;\n    }\n\n    public Units getUnits() {\n        return units;\n    }\n\n    public boolean hasLink() {\n        return link != null;\n    }\n\n    public Link getLink() {\n        return link;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Infos.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"infos\")\npublic class Infos {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @ElementList(inline = true, name = \"info\")\n    private List<Info> info;\n\n    public Infos() {\n    }\n\n    public Infos(@Attribute(name = \"count\") final long count,\n                 @ElementList(inline = true, name = \"info\") final List<Info> info) {\n        this.count = count;\n        this.info = info;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<Info> getInfo() {\n        return info;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Link.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"link\")\npublic class Link {\n\n    @Attribute(name = \"url\")\n    private String url;\n\n    @Attribute(name = \"text\")\n    private String text;\n\n    public Link() {\n    }\n\n    public Link(@Attribute(name = \"text\") final String text,\n                @Attribute(name = \"url\") final String url) {\n        this.text = text;\n        this.url = url;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Pod.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"pod\")\npublic class Pod {\n\n    public static final String ID_INPUT = \"Input\";\n    public static final String ID_RESULT = \"Result\";\n\n    @Attribute(name = \"title\")\n    private String title;\n\n    @Attribute(name = \"scanner\")\n    private String scanner;\n\n    @Attribute(name = \"id\")\n    private String id;\n\n    @Attribute(name = \"position\")\n    private long position;\n\n    @Attribute(name = \"primary\", required = false)\n    private boolean primary;\n\n    @Attribute(name = \"error\")\n    private boolean error;\n\n    @Attribute(name = \"numsubpods\")\n    private long numsubpods;\n\n    @ElementList(inline = true, name = \"subpods\")\n    private List<SubPod> subpods;\n\n    @Element(name = \"states\", required = false)\n    private States states;\n\n    @Element(name = \"infos\", required = false)\n    private Infos infos;\n\n    @Element(name = \"definitions\", required = false)\n    private Definitions definitions;\n\n    public Pod() {\n    }\n\n    public Pod(@Attribute(name = \"error\") final boolean error,\n               @Attribute(name = \"primary\") final boolean primary,\n               @Attribute(name = \"title\") final String title,\n               @Attribute(name = \"scanner\") final String scanner,\n               @Attribute(name = \"id\") final String id,\n               @Attribute(name = \"position\") final long position,\n               @Attribute(name = \"numsubpods\") final long numsubpods,\n               @ElementList(inline = true, name = \"subpods\") final List<SubPod> subpods,\n               @Element(name = \"states\") final States states,\n               @Element(name = \"infos\") final Infos infos,\n               @Element(name = \"definitions\", required = false) final Definitions definitions) {\n        this.error = error;\n        this.title = title;\n        this.scanner = scanner;\n        this.id = id;\n        this.position = position;\n        this.numsubpods = numsubpods;\n        this.subpods = subpods;\n        this.primary = primary;\n        this.states = states;\n        this.infos = infos;\n        this.definitions = definitions;\n    }\n\n    public boolean hasDefinitions() {\n        return definitions != null;\n    }\n\n    public Definitions getDefinitions() {\n        return definitions;\n    }\n\n    public boolean hasInfos() {\n        return infos != null;\n    }\n\n    public Infos getInfos() {\n        return infos;\n    }\n\n    public boolean hasStates() {\n        return states != null;\n    }\n\n    public States getStates() {\n        return states;\n    }\n\n    public boolean isPrimary() {\n        return primary;\n    }\n\n    public boolean isError() {\n        return error;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public long getNumsubpods() {\n        return numsubpods;\n    }\n\n    public long getPosition() {\n        return position;\n    }\n\n    public String getScanner() {\n        return scanner;\n    }\n\n    public boolean hasSubPods() {\n        return UtilsList.notNaked(subpods);\n    }\n\n    public List<SubPod> getSubPods() {\n        return subpods;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/QueryResult.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"queryresult\") //, strict = false\npublic class QueryResult {\n\n    @Attribute(name = \"success\")\n    private boolean success;\n\n    @Attribute(name = \"error\")\n    private boolean error;\n\n    @Attribute(name = \"nodata\", required = false)\n    private boolean noData;\n\n    @Attribute(name = \"numpods\")\n    private int numpods;\n\n    @Attribute(name = \"datatypes\")\n    private String datatypes;\n\n    @Attribute(name = \"timedout\")\n    private String timedout;\n\n    @Attribute(name = \"timedoutpods\")\n    private String timedoutpods;\n\n    @Attribute(name = \"timing\")\n    private double timing;\n\n    @Attribute(name = \"parsetimedout\")\n    private boolean parsetimedout;\n\n    @Attribute(name = \"parsetiming\")\n    private double parsetiming;\n\n    @Attribute(name = \"recalculate\")\n    private String recalculate;\n\n    @Attribute(name = \"id\")\n    private String id;\n\n    @Attribute(name = \"host\")\n    private String host;\n\n    @Attribute(name = \"server\")\n    private int server;\n\n    @Attribute(name = \"related\")\n    private String related;\n\n    @Attribute(name = \"version\")\n    private double version;\n\n    @ElementList(inline = true, name = \"pod\")\n    private List<Pod> pods;\n\n    @Element(name = \"assumptions\", required = false)\n    private Assumptions assumptions;\n\n    @Element(name = \"sources\", required = false)\n    private Sources sources;\n\n    @Element(name = \"warnings\", required = false)\n    private Warnings warnings;\n\n    public QueryResult() {\n    }\n\n    public QueryResult(@Attribute(name = \"datatypes\") final String datatypes,\n                       @Attribute(name = \"success\") final boolean success,\n                       @Attribute(name = \"nodata\") final boolean noData,\n                       @Attribute(name = \"error\") final boolean error,\n                       @Attribute(name = \"numpods\") final int numpods,\n                       @Attribute(name = \"timedout\") final String timedout,\n                       @Attribute(name = \"timedoutpods\") final String timedoutpods,\n                       @Attribute(name = \"timing\") final double timing,\n                       @Attribute(name = \"parsetimedout\") final boolean parsetimedout,\n                       @Attribute(name = \"parsetiming\") final double parsetiming,\n                       @Attribute(name = \"recalculate\") final String recalculate,\n                       @Attribute(name = \"id\") final String id,\n                       @Attribute(name = \"host\") final String host,\n                       @Attribute(name = \"server\") final int server,\n                       @Attribute(name = \"related\") final String related,\n                       @Attribute(name = \"version\") final double version,\n                       @ElementList(inline = true, name = \"pod\") final List<Pod> pods,\n                       @Element(name = \"assumptions\") final Assumptions assumptions,\n                       @Element(name = \"sources\", required = false) final Sources sources,\n                       @Element(name = \"warnings\", required = false) final Warnings warnings) {\n        this.datatypes = datatypes;\n        this.success = success;\n        this.noData = noData;\n        this.error = error;\n        this.numpods = numpods;\n        this.timedout = timedout;\n        this.timedoutpods = timedoutpods;\n        this.timing = timing;\n        this.parsetimedout = parsetimedout;\n        this.parsetiming = parsetiming;\n        this.recalculate = recalculate;\n        this.id = id;\n        this.host = host;\n        this.server = server;\n        this.related = related;\n        this.version = version;\n        this.pods = pods;\n        this.assumptions = assumptions;\n        this.sources = sources;\n        this.warnings = warnings;\n    }\n\n    public boolean noData() {\n        return noData;\n    }\n\n    public Warnings getWarnings() {\n        return warnings;\n    }\n\n    public Sources getSources() {\n        return sources;\n    }\n\n    public boolean hasSources() {\n        return sources != null;\n    }\n\n    public Assumptions getAssumptions() {\n        return assumptions;\n    }\n\n    public boolean hasAssumptions() {\n        return assumptions != null;\n    }\n\n    public String getDatatypes() {\n        return datatypes;\n    }\n\n    public boolean isError() {\n        return error;\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public int getNumpods() {\n        return numpods;\n    }\n\n    public boolean isParsetimedout() {\n        return parsetimedout;\n    }\n\n    public double getParsetiming() {\n        return parsetiming;\n    }\n\n    public boolean hasPods() {\n        return UtilsList.notNaked(pods);\n    }\n\n    public List<Pod> getPods() {\n        return pods;\n    }\n\n    public String getRecalculate() {\n        return recalculate;\n    }\n\n    public String getRelated() {\n        return related;\n    }\n\n    public int getServer() {\n        return server;\n    }\n\n    public boolean isSuccess() {\n        return success;\n    }\n\n    public String getTimedout() {\n        return timedout;\n    }\n\n    public String getTimedoutpods() {\n        return timedoutpods;\n    }\n\n    public double getTiming() {\n        return timing;\n    }\n\n    public double getVersion() {\n        return version;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Reinterpret.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"reinterpret\")\npublic class Reinterpret {\n\n    @Attribute(name = \"text\", required = false)\n    private String text;\n\n    @Attribute(name = \"new\", required = false)\n    private String replaced;\n\n    @Attribute(name = \"score\", required = false)\n    private double score;\n\n    @Attribute(name = \"level\", required = false)\n    private String level;\n\n    @ElementList(inline = true, name = \"alternative\", required = false)\n    private List<Alternative> alternatives;\n\n    public Reinterpret() {\n    }\n\n    public Reinterpret(@Attribute(name = \"level\", required = false) final String level,\n                       @Attribute(name = \"new\", required = false) final String replaced,\n                       @Attribute(name = \"score\", required = false) final double score,\n                       @Attribute(name = \"text\", required = false) final String text,\n                       @ElementList(inline = true, name = \"alternative\", required = false)\n                       final List<Alternative> alternatives) {\n        this.level = level;\n        this.replaced = replaced;\n        this.score = score;\n        this.text = text;\n        this.alternatives = alternatives;\n    }\n\n    public boolean haveAlternatives() {\n        return alternatives != null;\n    }\n\n    public List<Alternative> getAlternatives() {\n        return alternatives;\n    }\n\n    public String getLevel() {\n        return level;\n    }\n\n    public String getReplaced() {\n        return replaced;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public String getText() {\n        return text;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Source.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"source\")\npublic class Source {\n\n    @Attribute(name = \"url\")\n    private String url;\n\n    @Attribute(name = \"text\")\n    private String text;\n\n    public Source() {\n    }\n\n    public Source(@Attribute(name = \"text\") final String text,\n                  @Attribute(name = \"url\") final String url) {\n        this.text = text;\n        this.url = url;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Sources.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"sources\")\npublic class Sources {\n\n    @ElementList(inline = true, name = \"source\")\n    private List<Source> sources;\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    public Sources() {\n    }\n\n    public Sources(@Attribute(name = \"count\") final long count,\n                   @ElementList(inline = true, name = \"source\") final List<Source> sources) {\n        this.count = count;\n        this.sources = sources;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<Source> getSources() {\n        return sources;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/SpellCheck.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"spellcheck\")\npublic class SpellCheck {\n\n    @Attribute(name = \"word\")\n    private String word;\n\n    @Attribute(name = \"suggestion\")\n    private String suggestion;\n\n    @Attribute(name = \"text\")\n    private String text;\n\n    public SpellCheck() {\n    }\n\n    public SpellCheck(@Attribute(name = \"suggestion\") final String suggestion,\n                      @Attribute(name = \"text\") final String text,\n                      @Attribute(name = \"word\") final String word) {\n        this.suggestion = suggestion;\n        this.text = text;\n        this.word = word;\n    }\n\n    public String getSuggestion() {\n        return suggestion;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public String getWord() {\n        return word;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/State.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"state\")\npublic class State {\n\n    @Attribute(name = \"name\")\n    private String name;\n\n    @Attribute(name = \"input\")\n    private String input;\n\n    public State() {\n    }\n\n    public State(@Attribute(name = \"name\") final String input,\n                 @Attribute(name = \"input\") final String name) {\n        this.input = input;\n        this.name = name;\n    }\n\n    public String getInput() {\n        return input;\n    }\n\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/StateList.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"statelist\")\npublic class StateList {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @Attribute(name = \"value\", required = false)\n    private String value;\n\n    @Attribute(name = \"delimiters\", required = false)\n    private String delimiters;\n\n    @ElementList(inline = true, name = \"state\")\n    private List<State> state;\n\n    public StateList() {\n    }\n\n    public StateList(@Attribute(name = \"count\") final long count,\n                     @Attribute(name = \"delimiters\", required = false) final String delimiters,\n                     @ElementList(inline = true, name = \"state\") final List<State> state,\n                     @Attribute(name = \"value\", required = false) final String value) {\n        this.count = count;\n        this.delimiters = delimiters;\n        this.state = state;\n        this.value = value;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public String getDelimiters() {\n        return delimiters;\n    }\n\n    public List<State> getState() {\n        return state;\n    }\n\n    public String getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/States.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"states\")\npublic class States {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @Element(name = \"statelist\", required = false)\n    private StateList stateList;\n\n    @ElementList(inline = true, name = \"state\")\n    private List<State> state;\n\n    public States() {\n    }\n\n    public States(@Attribute(name = \"count\") final long count,\n                  @ElementList(inline = true, name = \"state\") final List<State> state,\n                  @Element(name = \"statelist\", required = false) final StateList stateList) {\n        this.count = count;\n        this.state = state;\n        this.stateList = stateList;\n    }\n\n    public boolean hasStateList() {\n        return stateList != null;\n    }\n\n    public StateList getStateList() {\n        return stateList;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<State> getState() {\n        return state;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/SubPod.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"subpod\")\npublic class SubPod {\n\n    public static final String DATA_UNAVAILABLE = \"(data not available)\";\n\n    @Attribute(name = \"title\")\n    private String title;\n\n    @Element(name = \"plaintext\", required = false)\n    private String plaintext;\n\n    @Element(name = \"imagesource\", required = false)\n    private String imagesource;\n\n    public SubPod() {\n    }\n\n    public SubPod(@Element(name = \"plaintext\") final String plaintext,\n                  @Attribute(name = \"title\") final String title,\n                  @Element(name = \"imagesource\", required = false) final String imagesource) {\n        this.plaintext = plaintext;\n        this.title = title;\n        this.imagesource = imagesource;\n    }\n\n    public String getImagesource() {\n        return imagesource;\n    }\n\n    public String getPlaintext() {\n        return plaintext;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Unit.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"unit\")\npublic class Unit {\n\n    @Attribute(name = \"short\")\n    private String shortUnit;\n\n    @Attribute(name = \"long\")\n    private String longUnit;\n\n    public Unit() {\n    }\n\n    public Unit(@Attribute(name = \"long\") final String longUnit,\n                @Attribute(name = \"short\") final String shortUnit) {\n        this.longUnit = longUnit;\n        this.shortUnit = shortUnit;\n    }\n\n    public String getLongUnit() {\n        return longUnit;\n    }\n\n    public String getShortUnit() {\n        return shortUnit;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Units.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"units\")\npublic class Units {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @ElementList(inline = true, name = \"unit\")\n    private List<Unit> unit;\n\n    public Units() {\n    }\n\n    public Units(@Attribute(name = \"count\") final long count,\n                 @ElementList(inline = true, name = \"unit\") final List<Unit> unit) {\n        this.count = count;\n        this.unit = unit;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public List<Unit> getUnit() {\n        return unit;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/ValidateQueryResult.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 09/08/2016.\n */\n\n@Root(name = \"validatequeryresult\", strict = false)\npublic class ValidateQueryResult {\n\n    @Attribute(name = \"success\")\n    private final boolean success;\n\n    @Attribute(name = \"error\")\n    private boolean error;\n\n    public ValidateQueryResult(@Attribute(name = \"error\") final boolean error,\n                               @Attribute(name = \"success\") final boolean success) {\n        this.error = error;\n        this.success = success;\n    }\n\n    public boolean isError() {\n        return error;\n    }\n\n    public boolean isSuccess() {\n        return success;\n    }\n\n    public boolean passedValidation() {\n        return success && !error;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Value.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\n@Root(name = \"value\")\npublic class Value {\n\n    @Attribute(name = \"name\")\n    private String name;\n\n    @Attribute(name = \"desc\")\n    private String desc;\n\n    @Attribute(name = \"input\")\n    private String input;\n\n    @Attribute(name = \"word\", required = false)\n    private String word;\n\n    @Attribute(name = \"valid\", required = false)\n    private boolean valid;\n\n    public Value() {\n    }\n\n    public Value(@Attribute(name = \"input\") final String input,\n                 @Attribute(name = \"name\") final String name,\n                 @Attribute(name = \"desc\") final String desc,\n                 @Attribute(name = \"word\") final String word,\n                 @Attribute(name = \"valid\", required = false) final boolean valid) {\n        this.input = input;\n        this.name = name;\n        this.desc = desc;\n        this.word = word;\n        this.valid = valid;\n    }\n\n    public boolean isValid() {\n        return valid;\n    }\n\n    public String getWord() {\n        return word;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public String getInput() {\n        return input;\n    }\n\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/parse/Warnings.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Element;\nimport org.simpleframework.xml.Root;\n\n/**\n * Created by benrandall76@gmail.com on 07/08/2016.\n */\n\n@Root(name = \"warnings\")\npublic class Warnings {\n\n    @Attribute(name = \"count\")\n    private long count;\n\n    @Element(name = \"reinterpret\", required = false)\n    private Reinterpret reinterpret;\n\n    @Element(name = \"spellcheck\", required = false)\n    private SpellCheck spellcheck;\n\n    public Warnings() {\n    }\n\n    public Warnings(@Attribute(name = \"count\") final long count,\n                    @Element(name = \"reinterpret\", required = false) final Reinterpret reinterpret,\n                    @Element(name = \"spellcheck\", required = false) final SpellCheck spellcheck) {\n        this.count = count;\n        this.reinterpret = reinterpret;\n        this.spellcheck = spellcheck;\n    }\n\n    public boolean hasSpellCheck() {\n        return spellcheck != null;\n    }\n\n    public SpellCheck getSpellcheck() {\n        return spellcheck;\n    }\n\n    public long getCount() {\n        return count;\n    }\n\n    public boolean hasReinterpret() {\n        return reinterpret != null;\n    }\n\n    public Reinterpret getReinterpret() {\n        return reinterpret;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/resolve/ResolveWolframAlpha.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Pair;\n\nimport org.simpleframework.xml.Serializer;\nimport org.simpleframework.xml.core.Persister;\n\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Assumption;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Assumptions;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Pod;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.QueryResult;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Source;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Sources;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.State;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.States;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.SubPod;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.ValidateQueryResult;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.Value;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 08/08/2016.\n */\n\npublic class ResolveWolframAlpha {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveWolframAlpha.class.getSimpleName();\n\n    private static final String DELIMITER = \"\\\\s\\\\|\";\n\n    private List<Pod> podList;\n    private WolframAlphaResponse wolframAlphaResponse = null;\n\n    public boolean validate(@NonNull final String question) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validate\");\n        }\n\n        final Serializer serializer = new Persister();\n\n        try {\n\n            final ValidateQueryResult result = serializer.read(ValidateQueryResult.class, question, false);\n            return result.passedValidation();\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    public Pair<Boolean, WolframAlphaResponse> resolve(@NonNull final WolframAlphaRequest request,\n                                                       @NonNull final String xmlResponse) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolve\");\n        }\n\n        final Serializer serializer = new Persister();\n\n        try {\n\n            final QueryResult queryResult = serializer.read(QueryResult.class, xmlResponse, false);\n\n            if (isSuccess(queryResult)) {\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getId());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getDatatypes());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getHost());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getRecalculate());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getTimedout());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getNumpods());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getRelated());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getTimedoutpods());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.isParsetimedout());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getVersion());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getTiming());\n                    MyLog.i(CLS_NAME, \"result: \" + queryResult.getServer());\n                }\n\n                if (canResolveResponse(queryResult)) {\n\n                    for (final Pod pod : podList) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"pod: \" + pod.getId());\n                            MyLog.i(CLS_NAME, \"pod: \" + pod.getScanner());\n                            MyLog.i(CLS_NAME, \"pod: \" + pod.getTitle());\n                            MyLog.i(CLS_NAME, \"pod: \" + pod.getNumsubpods());\n                            MyLog.i(CLS_NAME, \"pod: \" + pod.getPosition());\n                        }\n\n                        final List<SubPod> subPodList = pod.getSubPods();\n\n                        for (final SubPod subPod : subPodList) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"subPod: \" + subPod.getTitle());\n                                MyLog.i(CLS_NAME, \"subPod: \" + subPod.getPlaintext());\n                            }\n                        }\n\n                        if (pod.hasStates()) {\n\n                            final States states = pod.getStates();\n                            final List<State> stateList = states.getState();\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"states: \" + states.getCount());\n                            }\n\n                            for (final State state : stateList) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"state: \" + state.getInput());\n                                    MyLog.i(CLS_NAME, \"state: \" + state.getName());\n                                }\n                            }\n\n                        } else {\n                            MyLog.i(CLS_NAME, \"pod: no states\");\n                        }\n                    }\n\n                    if (queryResult.hasAssumptions()) {\n\n                        final Assumptions assumptions = queryResult.getAssumptions();\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"assumptions: \" + assumptions.getCount());\n                        }\n\n                        final List<Assumption> assumptionList = assumptions.getAssumptions();\n\n                        for (final Assumption assumption : assumptionList) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"assumption: \" + assumption.getCount());\n                                MyLog.i(CLS_NAME, \"assumption: \" + assumption.getTemplate());\n                                MyLog.i(CLS_NAME, \"assumption: \" + assumption.getType());\n                                MyLog.i(CLS_NAME, \"assumption: \" + assumption.getWord());\n                            }\n\n                            final List<Value> valueList = assumption.getValues();\n\n                            for (final Value value : valueList) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"value: \" + value.getDesc());\n                                    MyLog.i(CLS_NAME, \"value: \" + value.getInput());\n                                    MyLog.i(CLS_NAME, \"value: \" + value.getName());\n                                }\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"result: no assumptions\");\n                        }\n                    }\n\n                    if (queryResult.hasSources()) {\n\n                        final Sources sources = queryResult.getSources();\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"sources: \" + sources.getCount());\n                        }\n\n                        final List<Source> sourceList = sources.getSources();\n\n                        for (final Source source : sourceList) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"source: \" + source.getText());\n                                MyLog.i(CLS_NAME, \"source: \" + source.getUrl());\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"result: no sources\");\n                        }\n                    }\n\n                    resolveResponse(podList);\n                    wolframAlphaResponse.setQueryResult(queryResult);\n                    wolframAlphaResponse.setQuestion(request.getQuery());\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"result: can't resolve response\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"result: isSuccess: false\");\n                }\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return new Pair<>(wolframAlphaResponse != null, wolframAlphaResponse);\n    }\n\n    private boolean isSuccess(@Nullable final QueryResult result) {\n        return result != null && result.isSuccess() && !result.isError() && !result.noData();\n    }\n\n    private boolean canResolveResponse(@NonNull final QueryResult result) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"canResolveResponse\");\n        }\n\n        if (result.hasPods()) {\n\n            podList = result.getPods();\n            if (haveInputPod(podList)) {\n                if (haveResultPod(podList)) {\n                    return true;\n                } else {\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"canResolveResponse: no result pod\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"canResolveResponse: no input pod\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"canResolveResponse: no pods\");\n            }\n        }\n\n        return result.hasPods() && haveInputPod(result.getPods()) && haveResultPod(result.getPods());\n    }\n\n    private boolean haveResultPod(@NonNull final List<Pod> podList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"haveResultPod\");\n        }\n\n        String id;\n        for (final Pod pod : podList) {\n            id = pod.getId();\n            if (UtilsString.notNaked(id) && id.matches(Pod.ID_RESULT)\n                    && haveSubPodPlainText(pod)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private boolean haveInputPod(@NonNull final List<Pod> podList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"haveInputPod\");\n        }\n\n        String id;\n        for (final Pod pod : podList) {\n            id = pod.getId();\n            if (UtilsString.notNaked(id) && Pod.ID_INPUT.matches(Pattern.quote(id))\n                    && haveSubPodPlainText(pod)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private String getInputText(@NonNull final List<Pod> podList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getInputText\");\n        }\n\n        String id;\n        for (final Pod pod : podList) {\n            id = pod.getId();\n            if (Pod.ID_INPUT.matches(Pattern.quote(id))) {\n                return pod.getSubPods().get(0).getPlaintext();\n            }\n        }\n\n        return null;\n    }\n\n    private String getResultText(@NonNull final List<Pod> podList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getResultText\");\n        }\n\n        String id;\n        for (final Pod pod : podList) {\n            id = pod.getId();\n            if (Pod.ID_RESULT.matches(Pattern.quote(id))) {\n                return pod.getSubPods().get(0).getPlaintext();\n            }\n        }\n\n        return null;\n    }\n\n    private boolean haveSubPodPlainText(@NonNull final Pod pod) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"haveSubPodPlainText\");\n        }\n\n        final List<SubPod> subPodList = pod.getSubPods();\n        return UtilsList.notNaked(subPodList) && UtilsString.notNaked(subPodList.get(0).getPlaintext())\n                && !SubPod.DATA_UNAVAILABLE.matches(Pattern.quote(subPodList.get(0).getPlaintext()));\n    }\n\n    private void resolveResponse(@NonNull final List<Pod> podList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolveResponse\");\n        }\n\n        wolframAlphaResponse = new WolframAlphaResponse();\n\n        final String input = getInputText(podList);\n        final String result = getResultText(podList);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"input: \" + input);\n            MyLog.i(CLS_NAME, \"result: \" + result);\n        }\n\n        wolframAlphaResponse.setInterpretation(formatString(input));\n        wolframAlphaResponse.setResult(formatString(result));\n\n    }\n\n    private String formatString(@NonNull final String toFormat) {\n        return toFormat.replaceAll(DELIMITER, \",\").replaceAll(\"\\\\s+\", \" \").trim() + \". \";\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/resolve/WolframAlphaRequest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class to package up a Wolfram Alpha request\n * <p>\n * Created by benrandall76@gmail.com on 09/08/2016.\n */\n\npublic class WolframAlphaRequest {\n\n    public enum Type {\n        GENERAL,\n        MATHEMATICA,\n        AUDIO,\n        IMAGERY\n    }\n\n    private String query;\n    private Type type;\n    private boolean autoShow;\n\n    public WolframAlphaRequest() {\n    }\n\n    /**\n     * Constructor\n     *\n     * @param type     one of {@link Type}\n     * @param query    the string of the query to request\n     * @param autoShow whether or not to show the results once they are complete\n     */\n    public WolframAlphaRequest(@NonNull final Type type, @NonNull final String query,\n                               final boolean autoShow) {\n        this.autoShow = autoShow;\n        this.query = query;\n        this.type = type;\n    }\n\n    public boolean isAutoShow() {\n        return autoShow;\n    }\n\n    public void setAutoShow(final boolean autoShow) {\n        this.autoShow = autoShow;\n    }\n\n    public String getQuery() {\n        return query;\n    }\n\n    public void setQuery(@NonNull final String query) {\n        this.query = query;\n    }\n\n    public Type getType() {\n        return type;\n    }\n\n    public void setType(@NonNull final Type type) {\n        this.type = type;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/knowledge/provider/wolframalpha/resolve/WolframAlphaResponse.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.parse.QueryResult;\n\n/**\n * Class to package a response from Wolfram Alpha\n * <p>\n * Created by benrandall76@gmail.com on 09/08/2016.\n */\n\npublic class WolframAlphaResponse {\n\n    private QueryResult queryResult;\n    private String question;\n    private String interpretation;\n    private String result;\n\n    public WolframAlphaResponse() {\n    }\n\n    /**\n     * Constructor\n     *\n     * @param queryResult    a {@link QueryResult} object containing the full response\n     * @param question       the question that was asked\n     * @param interpretation the interpretation of the question by Wolfram Alpha\n     * @param result         the results supplied by Wolfram Alpha\n     */\n    public WolframAlphaResponse(@NonNull final QueryResult queryResult, @NonNull final String question,\n                                @NonNull final String interpretation, @NonNull final String result) {\n        this.interpretation = interpretation;\n        this.queryResult = queryResult;\n        this.question = question;\n        this.result = result;\n    }\n\n    public void setInterpretation(final String interpretation) {\n        this.interpretation = interpretation;\n    }\n\n    public void setQueryResult(final QueryResult queryResult) {\n        this.queryResult = queryResult;\n    }\n\n    public void setQuestion(final String question) {\n        this.question = question;\n    }\n\n    public void setResult(final String result) {\n        this.result = result;\n    }\n\n    public String getInterpretation() {\n        return interpretation;\n    }\n\n    public QueryResult getQueryResult() {\n        return queryResult;\n    }\n\n    public String getQuestion() {\n        return question;\n    }\n\n    public String getResult() {\n        return result;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/motion/provider/google/Motion.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.motion.provider.google;\n\n/**\n * Created by benrandall76@gmail.com on 05/07/2016.\n */\n\npublic class Motion {\n\n    protected transient static final int CONFIDENCE_THRESHOLD = 75;\n\n    private final int type;\n    private final int confidence;\n    private final long time;\n\n    public Motion(final int type, final int confidence, final long time) {\n        this.confidence = confidence;\n        this.type = type;\n        this.time = time;\n    }\n\n    public int getConfidence() {\n        return confidence;\n    }\n\n    public int getType() {\n        return type;\n    }\n\n    public long getTime() {\n        return time;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/motion/provider/google/MotionHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.motion.provider.google;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.android.gms.location.DetectedActivity;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Helper class to store the most recent ActivityRecognition event in the user's shared preferences\n * <p>\n * Created by benrandall76@gmail.com on 15/08/2016.\n */\npublic class MotionHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = MotionHelper.class.getSimpleName();\n\n    /**\n     * Check if we have a recent {@link Motion} object stored\n     *\n     * @param ctx the application context\n     * @return true if a {@link Motion} is stored\n     */\n    public static boolean haveMotion(@NonNull final Context ctx) {\n        return SPH.getMotion(ctx) != null;\n    }\n\n\n    /**\n     * Store the {@link Motion} object in the shared preferences\n     *\n     * @param ctx    the application context\n     * @param motion {@link Motion} object\n     */\n    public static void setMotion(@NonNull final Context ctx, @NonNull final Motion motion) {\n\n        final String gsonString = new GsonBuilder().disableHtmlEscaping().create().toJson(motion);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setMotion: gsonString: \" + gsonString);\n        }\n\n        SPH.setMotion(ctx, gsonString);\n        reactMotion(ctx, motion);\n    }\n\n    /**\n     * Check if we need to react to the detected ActivityRecognition type.\n     *\n     * @param ctx    the application context\n     * @param motion the detection {@link Motion} object\n     */\n    private static void reactMotion(@NonNull final Context ctx, @NonNull final Motion motion) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"reactMotion\");\n        }\n\n        switch (motion.getType()) {\n\n            case DetectedActivity.WALKING:\n                break;\n            case DetectedActivity.IN_VEHICLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"reactMotion: IN_VEHICLE\");\n                }\n\n                if (SPH.getHotwordDriving(ctx)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"reactMotion: IN_VEHICLE: enabled\");\n                    }\n\n                    final LocalRequest request = new LocalRequest(ctx);\n                    request.prepareDefault(LocalRequest.ACTION_START_HOTWORD, null);\n                    request.execute();\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"reactMotion: IN_VEHICLE: disabled\");\n                    }\n                }\n\n                break;\n            case DetectedActivity.ON_BICYCLE:\n                break;\n            case DetectedActivity.ON_FOOT:\n                break;\n            case DetectedActivity.RUNNING:\n                break;\n            case DetectedActivity.STILL:\n                break;\n            case DetectedActivity.TILTING:\n                break;\n            case DetectedActivity.UNKNOWN:\n                break;\n            default:\n                break;\n        }\n    }\n\n    /**\n     * Get the {@link Motion} we have stored\n     *\n     * @param ctx the application context\n     * @return the {@link Motion} object or a created one if none was present\n     */\n    public static Motion getMotion(@NonNull final Context ctx) {\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final Motion motion;\n\n        if (haveMotion(ctx)) {\n\n            try {\n                motion = gson.fromJson(SPH.getMemory(ctx), Motion.class);\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"motion: \" + gson.toJson(motion));\n                }\n                return motion;\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"motion: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"motion: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"motion: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return getUnknown();\n    }\n\n    /**\n     * No {@link Motion} has been stored, so return an empty {@link Motion} object that can be identified as unknown.\n     *\n     * @return a constructed {@link Motion} object\n     */\n    private static Motion getUnknown() {\n        return new Motion(DetectedActivity.UNKNOWN, 0, 0L);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/motion/provider/google/MotionIntentService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.motion.provider.google;\n\nimport android.app.IntentService;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport com.google.android.gms.location.ActivityRecognitionResult;\nimport com.google.android.gms.location.DetectedActivity;\n\nimport java.util.Set;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to handle callbacks from the ActivityRecognition API.\n * <p>\n * If the confidence scores are high, the activity is stored, so we can understand the context of what the\n * user is doing elsewhere in the application.\n * <p>\n * Created by benrandall76@gmail.com on 05/07/2016.\n */\n\npublic class MotionIntentService extends IntentService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = MotionIntentService.class.getSimpleName();\n\n    protected static final long UPDATE_INTERVAL = 600000L;\n    protected static final int REQUEST_CODE = 55;\n\n    private long then;\n\n    /**\n     * Constructor\n     */\n    public MotionIntentService() {\n        super(MotionIntentService.class.getSimpleName());\n    }\n\n    @Override\n    public void onCreate() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        then = System.nanoTime();\n        super.onCreate();\n    }\n\n    @Override\n    protected void onHandleIntent(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onHandleIntent\");\n        }\n\n        if (SPH.getMotionEnabled(getApplicationContext())) {\n\n            if (intent != null) {\n                if (DEBUG) {\n                    examineIntent(intent);\n                }\n\n                if (ActivityRecognitionResult.hasResult(intent)) {\n                    final Motion motion = extractMotion(intent);\n                    if (motion != null) {\n                        MotionHelper.setMotion(getApplicationContext(), motion);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: motion null: ignoring\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: no ActivityRecognition results\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onHandleIntent: intent: null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onHandleIntent: user has switched off. Don't store.\");\n            }\n        }\n    }\n\n    /**\n     * Store the most recent user activity for use elsewhere in the application.\n     *\n     * @param intent which should contain an {@link ActivityRecognitionResult}\n     * @return a {@link Motion} object\n     */\n    private Motion extractMotion(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"extractMotion\");\n        }\n\n        final ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);\n\n        if (result != null) {\n\n            final DetectedActivity detectedActivity = result.getMostProbableActivity();\n\n            if (detectedActivity != null) {\n                if (DEBUG) {\n                    logActivity(detectedActivity);\n                }\n\n                final int confidence = detectedActivity.getConfidence();\n\n                if (confidence > Motion.CONFIDENCE_THRESHOLD) {\n\n                    switch (detectedActivity.getType()) {\n\n                        case DetectedActivity.WALKING:\n                        case DetectedActivity.IN_VEHICLE:\n                        case DetectedActivity.ON_BICYCLE:\n                        case DetectedActivity.ON_FOOT:\n                        case DetectedActivity.RUNNING:\n                        case DetectedActivity.STILL:\n                            return new Motion(detectedActivity.getType(), confidence, result.getTime());\n                        case DetectedActivity.TILTING:\n                        case DetectedActivity.UNKNOWN:\n                        default:\n                            break;\n                    }\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"detectedActivity: ignoring low confidence\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"detectedActivity: null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"detectedActivity: ActivityRecognitionResult: null\");\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Logging only\n     *\n     * @param detectedActivity the {@link DetectedActivity}\n     */\n    private void logActivity(final DetectedActivity detectedActivity) {\n        MyLog.v(CLS_NAME, \"detectedActivity: confidence: \" + detectedActivity.getConfidence());\n\n        switch (detectedActivity.getType()) {\n\n            case DetectedActivity.WALKING:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.WALKING\");\n                break;\n            case DetectedActivity.IN_VEHICLE:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.IN_VEHICLE\");\n                break;\n            case DetectedActivity.ON_BICYCLE:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.ON_BICYCLE\");\n                break;\n            case DetectedActivity.ON_FOOT:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.ON_FOOT\");\n                break;\n            case DetectedActivity.RUNNING:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.RUNNING\");\n                break;\n            case DetectedActivity.STILL:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.STILL\");\n                break;\n            case DetectedActivity.TILTING:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.TILTING\");\n                break;\n            case DetectedActivity.UNKNOWN:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.UNKNOWN\");\n                break;\n            default:\n                MyLog.i(CLS_NAME, \"detectedActivity: DetectedActivity.default\");\n                break;\n        }\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param intent containing potential extras\n     */\n    private void examineIntent(@NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineIntent\");\n        }\n\n        final Bundle bundle = intent.getExtras();\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            for (final String key : keys) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"examineIntent: \" + key + \" ~ \" + bundle.get(key));\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/cognitive/motion/provider/google/MotionRecognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.cognitive.motion.provider.google;\n\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.google.android.gms.common.ConnectionResult;\nimport com.google.android.gms.common.GoogleApiAvailability;\nimport com.google.android.gms.common.api.GoogleApiClient;\nimport com.google.android.gms.common.api.ResultCallback;\nimport com.google.android.gms.common.api.Status;\nimport com.google.android.gms.location.ActivityRecognition;\nimport com.google.android.gms.security.ProviderInstaller;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to setup the ActivityRecognition API and register the {@link MotionIntentService} for callbacks.\n * <p>\n * This must only survive as long as {@link ai.saiy.android.service.SelfAware} is running, so must\n * be destroyed as per its lifecycle.\n * <p>\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class MotionRecognition implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,\n        ResultCallback<Status> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = MotionRecognition.class.getSimpleName();\n\n    private PendingIntent pendingIntent;\n    private GoogleApiClient activityClient;\n\n    /**\n     * Prepare the Activity Recognition API for use.\n     *\n     * @param ctx the application context\n     */\n    public void prepare(@NonNull final Context ctx) {\n\n        final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();\n        final int connectionResult = apiAvailability.isGooglePlayServicesAvailable(ctx);\n\n        switch (connectionResult) {\n\n            case ConnectionResult.SUCCESS:\n\n                activityClient = new GoogleApiClient.Builder(ctx).addConnectionCallbacks(this)\n                        .addOnConnectionFailedListener(this).addApi(ActivityRecognition.API).build();\n\n                pendingIntent = PendingIntent.getService(ctx, MotionIntentService.REQUEST_CODE,\n                        new Intent(ctx, MotionIntentService.class),\n                        PendingIntent.FLAG_UPDATE_CURRENT);\n\n                try {\n\n                    ProviderInstaller.installIfNeededAsync(ctx, new ProviderInstaller.ProviderInstallListener() {\n                        @Override\n                        public void onProviderInstalled() {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepare: play services onProviderInstalled\");\n                            }\n                        }\n\n                        @Override\n                        public void onProviderInstallFailed(final int errorCode, final Intent intent) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"prepare: play services onProviderInstallFailed\");\n                            }\n\n                            if (apiAvailability.isUserResolvableError(errorCode)) {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"prepare: play services onProviderInstallFailed\");\n                                }\n\n                                apiAvailability.showErrorNotification(ctx, errorCode);\n\n                            } else {\n                                // TODO - unrecoverable\n                            }\n                        }\n                    });\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"prepare: play services unavailable\");\n                        e.printStackTrace();\n                    }\n                }\n\n                break;\n\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"prepare: play services unavailable\");\n                }\n                apiAvailability.showErrorNotification(ctx, connectionResult);\n                break;\n        }\n    }\n\n    /**\n     * Connect to receive activity recognition callbacks\n     */\n    public void connect() {\n\n        if (activityClient != null) {\n            activityClient.connect();\n        }\n    }\n\n    @Override\n    public void onConnected(@Nullable final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onConnected\");\n        }\n\n        if (activityClient != null && pendingIntent != null) {\n            ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(activityClient,\n                    MotionIntentService.UPDATE_INTERVAL, pendingIntent).setResultCallback(this);\n        }\n    }\n\n    @Override\n    public void onConnectionSuspended(final int i) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onConnectionSuspended\");\n        }\n    }\n\n    @Override\n    public void onConnectionFailed(@NonNull final ConnectionResult connectionResult) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onConnectionFailed\");\n        }\n    }\n\n    @Override\n    public void onResult(@NonNull final Status status) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResult\");\n        }\n    }\n\n    /**\n     * Cancel any future callbacks and disconnect the client.\n     */\n    public void destroy() {\n\n        if (activityClient != null && pendingIntent != null) {\n\n            try {\n                ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(activityClient, pendingIntent);\n                activityClient.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"destroy: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/Battery.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class Battery implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object battery;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Battery(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                   @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                battery = new Battery_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                battery = new Battery_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                battery = new Battery_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public Battery(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n    /**\n     * Method to identify the required battery information\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> of voice data\n     * @return a {@link CommandBatteryValues} object containing the required parameters\n     */\n    public CommandBatteryValues fetch(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return Battery_en.sortBattery(ctx, voiceData, sl);\n            case ENGLISH_US:\n                return Battery_en.sortBattery(ctx, voiceData, sl);\n            default:\n                return Battery_en.sortBattery(ctx, voiceData, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Battery_en) battery).detectCallable();\n            case ENGLISH_US:\n                return ((Battery_en) battery).detectCallable();\n            default:\n                return ((Battery_en) battery).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/BatteryInformation.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.BatteryManager;\nimport android.support.annotation.NonNull;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to resolve information regarding the device's battery and assign the parameters for the\n * vocal response.\n * <p>\n * Created by benrandall76@gmail.com on 13/06/2016.\n */\npublic class BatteryInformation {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BatteryInformation.class.getSimpleName();\n\n    public static final int CELSIUS = 0;\n    public static final int FAHRENHEIT = 1;\n\n    private final Intent batteryIntent;\n\n    private final Context mContext;\n    private final SupportedLanguage sl;\n    private final Outcome outcome;\n    private final String typeString;\n\n    /**\n     * Constructor\n     *\n     * @param mContext   the application context\n     * @param sl         the {@link SupportedLanguage}\n     * @param outcome    the {@link Outcome}\n     * @param typeString the String representation of the battery information request type.\n     */\n    public BatteryInformation(@NonNull final Context mContext, @NonNull SupportedLanguage sl,\n                              @NonNull final Outcome outcome, @NonNull final String typeString) {\n        this.mContext = mContext;\n        this.sl = sl;\n        this.outcome = outcome;\n        this.typeString = typeString;\n\n        batteryIntent = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));\n    }\n\n    /**\n     * Method to assign the response parameters to the {@link Outcome}\n     *\n     * @param type the {@link CommandBatteryValues.Type}\n     * @return the {@link Outcome}\n     */\n    public Outcome getInfo(@NonNull final CommandBatteryValues.Type type) {\n\n        switch (type) {\n\n            case TEMPERATURE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.TEMPERATURE.name());\n                }\n                getTemperature();\n                break;\n            case PERCENTAGE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.PERCENTAGE.name());\n                }\n                getPercentage();\n                break;\n            case VOLTAGE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.VOLTAGE.name());\n                }\n                getVoltage();\n                break;\n            case STATUS:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.STATUS.name());\n                }\n                getStatus();\n                break;\n            case HEALTH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.HEALTH.name());\n                }\n                getHealth();\n                break;\n            case UNKNOWN:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getInfo: \" + CommandBatteryValues.Type.UNKNOWN.name());\n                }\n                outcome.setOutcome(Outcome.FAILURE);\n                outcome.setUtterance(PersonalityResponse.getBatteryErrorUnknownResponse(mContext, sl));\n                break;\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Method to resolve the battery health\n     */\n    private void getHealth() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getHealth\");\n        }\n\n        if (batteryIntent != null) {\n\n            final SaiyResources sr = new SaiyResources(mContext, sl);\n\n            final int health = batteryIntent.getIntExtra(BatteryManager.EXTRA_HEALTH,\n                    BatteryManager.BATTERY_HEALTH_UNKNOWN);\n\n            switch (health) {\n\n                case BatteryManager.BATTERY_HEALTH_COLD:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.cold));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_DEAD:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.dead));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_GOOD:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.good));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_OVERHEAT:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.over_heating));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.over_voltage));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_UNKNOWN:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.currently_indeterminable));\n                    break;\n                case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.currently_indeterminable));\n                    break;\n                default:\n                    setHealthResponse(sr.getString(ai.saiy.android.R.string.currently_indeterminable));\n                    break;\n            }\n\n            sr.reset();\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"batteryIntent: null\");\n            }\n            setAccessFailure();\n        }\n    }\n\n    /**\n     * Method to set the {@link Outcome} parameters\n     *\n     * @param health the resolved battery {@link CommandBatteryValues.Type#HEALTH}\n     */\n    private void setHealthResponse(@NonNull final String health) {\n        outcome.setOutcome(Outcome.SUCCESS);\n        outcome.setUtterance(PersonalityResponse.getBatteryResponse(mContext, sl, typeString, health));\n    }\n\n    /**\n     * Method to resolve the battery status\n     */\n    private void getStatus() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getStatus\");\n        }\n\n        if (batteryIntent != null) {\n\n            final SaiyResources sr = new SaiyResources(mContext, sl);\n\n            final int status = batteryIntent.getIntExtra(BatteryManager.EXTRA_STATUS,\n                    BatteryManager.BATTERY_STATUS_UNKNOWN);\n\n            switch (status) {\n\n                case BatteryManager.BATTERY_STATUS_CHARGING:\n\n                    int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED,\n                            BatteryManager.BATTERY_STATUS_UNKNOWN);\n\n                    switch (plugged) {\n                        case BatteryManager.BATTERY_PLUGGED_AC:\n                            setStatusResponse(sr.getString(ai.saiy.android.R.string.ac_charging));\n                            break;\n                        case BatteryManager.BATTERY_PLUGGED_USB:\n                            setStatusResponse(sr.getString(ai.saiy.android.R.string.usb_charging));\n                            break;\n                        default:\n                            setStatusResponse(sr.getString(ai.saiy.android.R.string.charging));\n                            break;\n                    }\n                    break;\n\n                case BatteryManager.BATTERY_STATUS_DISCHARGING:\n                    setStatusResponse(sr.getString(ai.saiy.android.R.string.discharging));\n                    break;\n                case BatteryManager.BATTERY_STATUS_NOT_CHARGING:\n                    setStatusResponse(sr.getString(ai.saiy.android.R.string.discharging));\n                    break;\n                case BatteryManager.BATTERY_STATUS_FULL:\n                    setStatusResponse(sr.getString(ai.saiy.android.R.string.fully_charged));\n                    break;\n                case BatteryManager.BATTERY_STATUS_UNKNOWN:\n                    setStatusResponse(sr.getString(ai.saiy.android.R.string.currently_indeterminable));\n                    break;\n                default:\n                    setStatusResponse(sr.getString(ai.saiy.android.R.string.currently_indeterminable));\n                    break;\n            }\n\n            sr.reset();\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"batteryIntent: null\");\n            }\n            setAccessFailure();\n        }\n    }\n\n    /**\n     * Method to set the {@link Outcome} parameters\n     *\n     * @param status the resolved battery {@link CommandBatteryValues.Type#STATUS}\n     */\n    private void setStatusResponse(@NonNull final String status) {\n        outcome.setOutcome(Outcome.SUCCESS);\n        outcome.setUtterance(PersonalityResponse.getBatteryResponse(mContext, sl, typeString, status));\n    }\n\n    /**\n     * Method to resolve the battery voltage\n     */\n    private void getVoltage() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getVoltage\");\n        }\n\n        if (batteryIntent != null) {\n\n            int voltage = batteryIntent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);\n\n            if (voltage > 0) {\n                voltage = voltage / 1000;\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getVoltage: \" + voltage);\n                }\n\n                final SaiyResources sr = new SaiyResources(mContext, sl);\n                setVoltageResponse(String.valueOf(voltage) + \" \" + sr.getString(ai.saiy.android.R.string.volts));\n                sr.reset();\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getVoltage reporting incorrectly\");\n                }\n                setAccessFailure();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"batteryIntent: null\");\n            }\n            setAccessFailure();\n        }\n    }\n\n    /**\n     * Method to set the {@link Outcome} parameters\n     *\n     * @param voltage the resolved battery {@link CommandBatteryValues.Type#VOLTAGE}\n     */\n    private void setVoltageResponse(@NonNull final String voltage) {\n        outcome.setOutcome(Outcome.SUCCESS);\n        outcome.setUtterance(PersonalityResponse.getBatteryResponse(mContext, sl, typeString, voltage));\n    }\n\n    /**\n     * Method to resolve the battery percentage\n     */\n    private void getPercentage() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getPercentage\");\n        }\n\n        if (batteryIntent != null) {\n\n            final int percentage = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);\n\n            if (percentage > -1) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getPercentage: \" + percentage);\n                }\n\n                setPercentageResponse(String.valueOf(percentage) + \"%\");\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getPercentage reporting incorrectly\");\n                }\n                setAccessFailure();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"batteryIntent: null\");\n            }\n            setAccessFailure();\n        }\n    }\n\n    /**\n     * Method to set the {@link Outcome} parameters\n     *\n     * @param percentage the resolved battery {@link CommandBatteryValues.Type#PERCENTAGE}\n     */\n    private void setPercentageResponse(@NonNull final String percentage) {\n        outcome.setOutcome(Outcome.SUCCESS);\n        outcome.setUtterance(PersonalityResponse.getBatteryResponse(mContext, sl, typeString, percentage));\n    }\n\n    /**\n     * Method to resolve the battery temperature\n     */\n    private void getTemperature() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getTemperature\");\n        }\n\n        if (batteryIntent != null) {\n\n            double celsius = batteryIntent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);\n\n            if (celsius > 0) {\n                celsius = celsius / 10;\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getTemperature: celsius: \" + celsius);\n                }\n\n                final SaiyResources sr = new SaiyResources(mContext, sl);\n\n                final String degrees = sr.getString(ai.saiy.android.R.string.degrees);\n\n                String units = \"\";\n                double temperature = 1;\n                switch (SPH.getDefaultTemperatureUnits(mContext)) {\n\n                    case CELSIUS:\n                        units = sr.getString(ai.saiy.android.R.string.celsius);\n                        temperature = celsius;\n                        break;\n                    case FAHRENHEIT:\n                        final double conversion = (((celsius * 1.8) + 32));\n                        final BigDecimal bd = new BigDecimal(conversion).setScale(0, RoundingMode.HALF_UP);\n                        temperature = bd.doubleValue();\n                        units = sr.getString(ai.saiy.android.R.string.fahrenheit);\n                        break;\n                }\n\n                sr.reset();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getTemperature: \" + temperature);\n                }\n\n                setTemperatureResponse(String.valueOf(temperature) + \" \" + degrees + \" \" + units);\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getTemperature reporting incorrectly\");\n                }\n                setAccessFailure();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"batteryIntent: null\");\n            }\n            setAccessFailure();\n        }\n    }\n\n    /**\n     * Method to set the {@link Outcome} parameters\n     *\n     * @param temperature the resolved battery {@link CommandBatteryValues.Type#TEMPERATURE}\n     */\n    private void setTemperatureResponse(@NonNull final String temperature) {\n        outcome.setOutcome(Outcome.SUCCESS);\n        outcome.setUtterance(PersonalityResponse.getBatteryResponse(mContext, sl, typeString, temperature));\n    }\n\n    /**\n     * Method set the {@link Outcome#FAILURE} parameters\n     */\n    private void setAccessFailure() {\n        outcome.setOutcome(Outcome.FAILURE);\n        outcome.setUtterance(PersonalityResponse.getBatteryErrorAccessResponse(mContext, sl));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/Battery_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve battery commands\n * <p>\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic class Battery_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Battery_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String battery;\n    private static String temperature;\n    private static String level;\n    private static String percentage;\n    private static String percent;\n    private static String voltage;\n    private static String volts;\n    private static String status;\n    private static String health;\n\n\n    /**\n     * Constructor\n     * <p>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Battery_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                      @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (battery == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        battery = sr.getString(ai.saiy.android.R.string.battery);\n        temperature = sr.getString(ai.saiy.android.R.string.temperature);\n        level = sr.getString(ai.saiy.android.R.string.level);\n        percentage = sr.getString(ai.saiy.android.R.string.percentage);\n        percent = sr.getString(ai.saiy.android.R.string.percent);\n        voltage = sr.getString(ai.saiy.android.R.string.voltage);\n        volts = sr.getString(ai.saiy.android.R.string.volts);\n        status = sr.getString(ai.saiy.android.R.string.status);\n        health = sr.getString(ai.saiy.android.R.string.health);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(battery)\n                        && ((vdLower.contains(temperature)\n                        || vdLower.contains(level)\n                        || vdLower.contains(percentage)\n                        || vdLower.contains(percent)\n                        || vdLower.contains(voltage)\n                        || vdLower.contains(volts)\n                        || vdLower.contains(status)\n                        || vdLower.contains(health)))) {\n\n                    toReturn.add(new Pair<>(CC.COMMAND_BATTERY, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"battery: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Static method.\n     * <p>\n     * Iterate through the voice data array and return an element of the highest confidence match.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @return a {@link CommandBatteryValues} object containing the required parameters\n     */\n    public static CommandBatteryValues sortBattery(@NonNull final Context ctx, final ArrayList<String> voiceData,\n                                                   @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n\n        final CommandBatteryValues values = new CommandBatteryValues();\n        values.setTypeString(\"\");\n        values.setType(CommandBatteryValues.Type.UNKNOWN);\n\n        if (battery == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        String vdLower;\n        for (final String vd : voiceData) {\n            vdLower = vd.toLowerCase(loc).trim();\n\n            if (vdLower.contains(battery)) {\n\n                if (vdLower.contains(temperature)) {\n                    values.setTypeString(temperature);\n                    values.setType(CommandBatteryValues.Type.TEMPERATURE);\n                    break;\n                } else if (vdLower.contains(level)\n                        || vdLower.contains(percent)\n                        || vdLower.contains(percentage)) {\n                    values.setTypeString(level);\n                    values.setType(CommandBatteryValues.Type.PERCENTAGE);\n                    break;\n                } else if (vdLower.contains(voltage)\n                        || vdLower.contains(volts)) {\n                    values.setTypeString(voltage);\n                    values.setType(CommandBatteryValues.Type.VOLTAGE);\n                    break;\n                } else if (vdLower.contains(status)) {\n                    values.setTypeString(status);\n                    values.setType(CommandBatteryValues.Type.STATUS);\n                    break;\n                } else if (vdLower.contains(health)) {\n                    values.setTypeString(health);\n                    values.setType(CommandBatteryValues.Type.HEALTH);\n                    break;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return values;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/CommandBattery.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to process a Battery request. Handles both remote NLP intents and falling back to\n * resolving locally.\n * <p>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandBattery {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandBattery.class.getSimpleName();\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @param cr        the {@link CommandRequest}\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final long then = System.nanoTime();\n\n        Outcome outcome = new Outcome();\n\n        if (cr.isResolved()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: true\");\n            }\n\n            final CommandBatteryValues cbv = (CommandBatteryValues) cr.getVariableData();\n            final CommandBatteryValues.Type type = cbv.getType();\n            outcome = new BatteryInformation(ctx, sl, outcome, cbv.getTypeString()).getInfo(type);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: false\");\n            }\n            outcome = new CommandBatteryLocal().getResponse(ctx, voiceData, sl);\n        }\n\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return outcome;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/CommandBatteryLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Outcome;\n\n/**\n * Helper class to resolve a battery command locally.\n * <p>\n * Created by benrandall76@gmail.com on 13/06/2016.\n */\npublic class CommandBatteryLocal {\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                  This is not necessarily the Locale of the device, as the user may be\n     *                  multi-lingual and/or have set a custom recognition language in a launcher short-cut.\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl) {\n\n        final CommandBatteryValues values = new Battery(sl).fetch(ctx, voiceData);\n        return new BatteryInformation(ctx, sl, new Outcome(), values.getTypeString()).getInfo(values.getType());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/battery/CommandBatteryValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.battery;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to hold information and constants to resolve a battery command.\n * <p>\n * Created by benrandall76@gmail.com on 01/06/2016.\n */\npublic class CommandBatteryValues {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandBatteryValues.class.getSimpleName();\n\n    /**\n     * Constant battery request types\n     */\n    public enum Type {\n        UNKNOWN,\n        TEMPERATURE,\n        PERCENTAGE,\n        VOLTAGE,\n        STATUS,\n        HEALTH\n    }\n\n    private Type type;\n    private String typeString;\n    private long startIndex;\n    private long endIndex;\n    private int[][] ranges;\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n\n    public void setRanges(@NonNull final int[][] ranges) {\n        this.ranges = ranges;\n    }\n\n    public String getTypeString() {\n        return typeString;\n    }\n\n    public void setTypeString(@NonNull final String typeString) {\n        this.typeString = typeString;\n    }\n\n    public Type getType() {\n        return type;\n    }\n\n    public void setType(@NonNull final Type type) {\n        this.type = type;\n    }\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public void setEndIndex(final long endIndex) {\n        this.endIndex = endIndex;\n    }\n\n    public void setStartIndex(final long startIndex) {\n        this.startIndex = startIndex;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n\n    /**\n     * Method to convert the spoken battery command information type, to an {@link Type} for use as\n     * a constant in further methods.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return one of {@link Type} or {@link Type#UNKNOWN} if the voice data cannot be resolved.\n     */\n    public Type stringToType(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                             @NonNull final String typeString) {\n\n        if (UtilsString.notNaked(typeString)) {\n\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            final String type = typeString.toLowerCase(sl.getLocale()).trim();\n\n            final String temperature = sr.getString(ai.saiy.android.R.string.temperature);\n            final String percent = sr.getString(ai.saiy.android.R.string.percent);\n            final String percentage = sr.getString(ai.saiy.android.R.string.percentage);\n            final String level = sr.getString(ai.saiy.android.R.string.level);\n            final String voltage = sr.getString(ai.saiy.android.R.string.voltage);\n            final String status = sr.getString(ai.saiy.android.R.string.status);\n            final String health = sr.getString(ai.saiy.android.R.string.health);\n            sr.reset();\n\n            if (type.matches(temperature)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stringToType: \" + Type.TEMPERATURE.name());\n                }\n                return Type.TEMPERATURE;\n            } else if (type.matches(percent)\n                    || type.matches(percentage)\n                    || type.matches(level)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stringToType: \" + Type.PERCENTAGE.name());\n                }\n                return Type.PERCENTAGE;\n            } else if (type.matches(voltage)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stringToType: \" + Type.VOLTAGE.name());\n                }\n                return Type.VOLTAGE;\n            } else if (type.matches(status)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stringToType: \" + Type.STATUS.name());\n                }\n                return Type.STATUS;\n            } else if (type.matches(health)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stringToType: \" + Type.HEALTH.name());\n                }\n                return Type.HEALTH;\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stringToType: \" + Type.UNKNOWN.name());\n                }\n                return Type.UNKNOWN;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stringToType: literal naked\");\n            }\n            return Type.UNKNOWN;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/cancel/Cancel.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.cancel;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the command Cancel, which is used in many places across the application,\n * not just when trying to resolve commands.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Cancel implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object cancel;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Cancel(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                  @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                cancel = new Cancel_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                cancel = new Cancel_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                cancel = new Cancel_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor\n     * <p>\n     * Used when a {@link SaiyResources} object is being handled elsewhere\n     *\n     * @param sl    the {@link SupportedLanguage}\n     * @param sr    the {@link SaiyResources}\n     * @param reset set to true if the {@link SaiyResources} should be reset\n     */\n    public Cancel(@NonNull final SupportedLanguage sl, @NonNull final SaiyResources sr, final boolean reset) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                cancel = new Cancel_en(sr, reset);\n                break;\n            case ENGLISH_US:\n                cancel = new Cancel_en(sr, reset);\n                break;\n            default:\n                cancel = new Cancel_en(sr, reset);\n                break;\n        }\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Cancel_en) cancel).detectCallable();\n            case ENGLISH_US:\n                return ((Cancel_en) cancel).detectCallable();\n            default:\n                return ((Cancel_en) cancel).detectCallable();\n        }\n    }\n\n    /**\n     * Will loop through an array to detect the command. The initialisation of any localised resources\n     * will only take place once in the constructor, which is better for performance.\n     * <p>\n     * The language to be used is decided by the {@link SupportedLanguage} object\n     *\n     * @param results received from the recognition provider which will contain voice data\n     * @return true if a cancel command is detected.\n     */\n    public boolean detectPartial(@NonNull final Bundle results) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Cancel_en) cancel).detectPartial(sl.getLocale(), results);\n            case ENGLISH_US:\n                return ((Cancel_en) cancel).detectPartial(sl.getLocale(), results);\n            default:\n                return ((Cancel_en) cancel).detectPartial(SupportedLanguage.ENGLISH.getLocale(),\n                        results);\n        }\n    }\n\n    /**\n     * Will loop through an array to detect the command. The initialisation of any localised resources\n     * will only take place once in the constructor, which is better for performance.\n     * <p>\n     * The language to be used is decided by the {@link SupportedLanguage} object\n     *\n     * @param voiceData received from the recognition provider\n     * @return true if a cancel command is detected.\n     */\n    public boolean detectCancel(@NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Cancel_en) cancel).detectCancel(sl.getLocale(), voiceData);\n            case ENGLISH_US:\n                return ((Cancel_en) cancel).detectCancel(sl.getLocale(), voiceData);\n            default:\n                return ((Cancel_en) cancel).detectCancel(SupportedLanguage.ENGLISH.getLocale(),\n                        voiceData);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/cancel/CancelPartial.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.cancel;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.partial.Partial;\nimport ai.saiy.android.partial.PartialHelper;\n\n/**\n * Created by benrandall76@gmail.com on 26/04/2016.\n */\npublic class CancelPartial implements Callable<Pair<Boolean, Integer>> {\n\n    private final SupportedLanguage sl;\n    private final Object cancel;\n    private Bundle results;\n\n    /**\n     * Constructor (used in the {@link PartialHelper}\n     * <p>\n     * Used when a {@link SaiyResources} object is being handled elsewhere\n     *\n     * @param sl the {@link SupportedLanguage}\n     * @param sr the {@link SaiyResources}\n     */\n    public CancelPartial(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                cancel = new Cancel_en(sr, false);\n                break;\n            case ENGLISH_US:\n                cancel = new Cancel_en(sr, false);\n                break;\n            default:\n                cancel = new Cancel_en(sr, false);\n                break;\n        }\n    }\n\n    /**\n     * Set the partial results to analyse during a recognition loop.\n     *\n     * @param results the {@link Bundle} of recognition results\n     */\n    public void setPartialData(@NonNull final Bundle results) {\n        this.results = results;\n    }\n\n    /**\n     * Will loop through an array to detect the command. The initialisation of any localised resources\n     * will only take place once in the constructor, which is better for performance.\n     * <p>\n     * The language to be used is decided by the {@link SupportedLanguage} object\n     *\n     * @return a {@link Pair} with the first parameter denoting detection and the second the\n     * {@link Partial} constant.\n     */\n    public Pair<Boolean, Integer> detectPartial() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return new Pair<>(((Cancel_en) cancel).detectPartial(sl.getLocale(), results),\n                        Partial.CANCEL);\n            case ENGLISH_US:\n                return new Pair<>(((Cancel_en) cancel).detectPartial(sl.getLocale(), results),\n                        Partial.CANCEL);\n            default:\n                return new Pair<>(((Cancel_en) cancel).detectPartial(SupportedLanguage.ENGLISH.getLocale(),\n                        results), Partial.CANCEL);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Pair<Boolean, Integer> call() throws Exception {\n        return detectPartial();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/cancel/Cancel_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.cancel;\n\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class to check if the user wishes to cancel the voice interaction.\n * <p/>\n * Created by benrandall76@gmail.com on 17/02/2016.\n */\npublic class Cancel_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Cancel_en.class.getSimpleName();\n\n    private static String cancel_;\n    private static String cancel_trim;\n    private static String council_;\n    private static String council_trim;\n    private static String cancel_that;\n    private static String never_mind;\n    private static String shush;\n    private static String shut_up;\n\n    private SupportedLanguage sl;\n    private ArrayList<String> voiceData;\n    private float[] confidence;\n\n    /**\n     * Constructor\n     *\n     * @param sr    the {@link SaiyResources}\n     * @param reset true if the {@link SaiyResources} should be reset immediately\n     */\n    public Cancel_en(@NonNull final SaiyResources sr, final boolean reset) {\n\n        if (cancel_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        if (reset) {\n            sr.reset();\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        cancel_ = sr.getString(ai.saiy.android.R.string.cancel_);\n        cancel_trim = cancel_.trim();\n        council_ = sr.getString(ai.saiy.android.R.string.council_);\n        council_trim = council_.trim();\n        cancel_that = sr.getString(ai.saiy.android.R.string.cancel_that);\n        never_mind = sr.getString(ai.saiy.android.R.string.never_mind);\n        shush = sr.getString(ai.saiy.android.R.string.shush);\n        shut_up = sr.getString(ai.saiy.android.R.string.shut_up);\n    }\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Cancel_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                     @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (cancel_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n    }\n\n    /**\n     * Iterate through the voice data array to see if the user has requested to cancel the current\n     * speech recognition session.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.startsWith(cancel_)\n                        || vdLower.matches(cancel_trim)\n                        || vdLower.contains(cancel_ + cancel_trim)\n                        || vdLower.endsWith(cancel_that)\n                        || vdLower.matches(never_mind)\n                        || vdLower.matches(shush)\n                        || vdLower.matches(shut_up)\n                        || vdLower.startsWith(council_)\n                        || vdLower.matches(council_trim)\n                        || vdLower.contains(council_ + council_trim)\n                        || vdLower.contains(council_ + cancel_trim)\n                        || vdLower.contains(cancel_ + council_trim)) {\n\n                    toReturn.add(new Pair<>(CC.COMMAND_CANCEL, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isCancel: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Iterate through the voice data array to see if the user has requested to cancel the current\n     * speech recognition session.\n     * <p/>\n     * Any unstable results that are found, may only contain one word or be a segment of the middle\n     * of the utterance and therefore we have to apply different matching in order to avoid a false positive.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, perhaps implementing a\n     * matcher, would probably be overkill.\n     *\n     * @param loc     the {@link SupportedLanguage} {@link Locale}\n     * @param results {@link Bundle} containing the voice data\n     * @return true if a cancel variant is detected\n     */\n    public boolean detectPartial(@NonNull final Locale loc, @NonNull final Bundle results) {\n\n        final long then = System.nanoTime();\n        boolean cancelled = false;\n\n        if (UtilsBundle.notNaked(results)) {\n            if (!UtilsBundle.isSuspicious(results)) {\n\n                final ArrayList<String> partialData = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n\n                /* handles empty string bug */\n                if (UtilsList.notNaked(partialData)) {\n                    partialData.removeAll(Collections.singleton(\"\"));\n\n                    if (!partialData.isEmpty()) {\n\n                        String vdLower;\n                        int size = partialData.size();\n                        for (int i = 0; i < size; i++) {\n                            vdLower = partialData.get(i).toLowerCase(loc).trim();\n\n                            if (vdLower.startsWith(cancel_)\n                                    || vdLower.matches(cancel_trim)\n                                    || vdLower.contains(cancel_ + cancel_trim)\n                                    || vdLower.endsWith(cancel_that)\n                                    || vdLower.matches(never_mind)\n                                    || vdLower.matches(shush)\n                                    || vdLower.matches(shut_up)\n                                    || vdLower.startsWith(council_)\n                                    || vdLower.matches(council_trim)\n                                    || vdLower.contains(council_ + council_trim)\n                                    || vdLower.contains(council_ + cancel_trim)\n                                    || vdLower.contains(cancel_ + council_trim)) {\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"partial vd: \" + vdLower);\n                                }\n                                cancelled = true;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                if (!cancelled) {\n                    final ArrayList<String> unstableData = results.getStringArrayList(RecognitionNative.UNSTABLE_RESULTS);\n\n                    /* handles empty string bug */\n                    if (UtilsList.notNaked(unstableData)) {\n                        unstableData.removeAll(Collections.singleton(\"\"));\n\n                        if (!unstableData.isEmpty()) {\n\n                            String vdLower;\n                            int size = unstableData.size();\n                            for (int i = 0; i < size; i++) {\n                                vdLower = unstableData.get(i).toLowerCase(loc).trim();\n\n                                if (vdLower.contains(cancel_ + cancel_trim)\n                                        || vdLower.endsWith(cancel_that)\n                                        || vdLower.matches(never_mind)\n                                        || vdLower.matches(shush)\n                                        || vdLower.matches(shut_up)\n                                        || vdLower.contains(council_ + council_trim)\n                                        || vdLower.contains(council_ + cancel_trim)\n                                        || vdLower.contains(cancel_ + council_trim)) {\n\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"unstable vd: \" + vdLower);\n                                    }\n                                    cancelled = true;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"isCancel: bundle has been tampered with\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isCancel: returning ~ \" + cancelled);\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return cancelled;\n    }\n\n    public boolean detectCancel(@NonNull final Locale loc, @NonNull final ArrayList<String> voiceData) {\n\n        String vdLower;\n        for (final String utterance : voiceData) {\n            vdLower = utterance.toLowerCase(loc).trim();\n\n            if (vdLower.matches(cancel_trim)\n                    || vdLower.contains(cancel_ + cancel_trim)\n                    || vdLower.endsWith(cancel_that)\n                    || vdLower.matches(never_mind)\n                    || vdLower.matches(shush)\n                    || vdLower.matches(shut_up)\n                    || vdLower.contains(council_ + council_trim)\n                    || vdLower.contains(council_ + cancel_trim)\n                    || vdLower.contains(cancel_ + council_trim)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/clipboard/ClipboardHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.clipboard;\n\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.support.annotation.MainThread;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Helper class to query and place items on the clipboard. Static implementation for ease of access.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic final class ClipboardHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = ClipboardHelper.class.getSimpleName();\n\n    private static String clipboardContent;\n\n    public static String getClipboardContent() {\n        return clipboardContent;\n    }\n\n    /**\n     * Prevent instantiation\n     */\n    public ClipboardHelper() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    @MainThread\n    public static void saveClipboardContent(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"saveClipboardContent\");\n        }\n\n        clipboardContent = null;\n\n        try {\n\n            final ClipboardManager clipboard = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);\n\n            if (clipboard.getPrimaryClip() != null && clipboard.getPrimaryClip().getItemAt(0) != null\n                    && !clipboard.getPrimaryClip().getItemAt(0).getText().toString().trim().isEmpty()) {\n\n                clipboardContent = clipboard.getPrimaryClip().getItemAt(0).getText().toString();\n\n            } else {\n                clipboardContent = null;\n            }\n\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"saveClipboardContent NullPointerException\");\n                e.printStackTrace();\n            }\n            clipboardContent = null;\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"saveClipboardContent Exception\");\n                e.printStackTrace();\n            }\n            clipboardContent = null;\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"saveClipboardContent complete\");\n        }\n    }\n\n    /**\n     * Static method to set clipboard content.\n     *\n     * @param ctx     the application context\n     * @param content the content to add to the clipboard\n     * @return true if the content was successfully set\n     */\n    public static boolean setClipboardContent(@NonNull final Context ctx, @NonNull final String content) {\n\n        // Can only be called from UI thread\n\n        try {\n\n            final ClipboardManager clipboard = (ClipboardManager) ctx\n                    .getSystemService(Context.CLIPBOARD_SERVICE);\n\n            final ClipData clip = ClipData.newPlainText(Constants.SAIY, content);\n            clipboard.setPrimaryClip(clip);\n\n            if (clipboard.getPrimaryClip() != null && clipboard.getPrimaryClip().getItemAt(0) != null\n                    && clipboard.getPrimaryClip().getItemAt(0).getText().toString().matches(content)) {\n                return true;\n            }\n\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setClipboardContent NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setClipboardContent Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Static method to get the primary clipboard content.\n     *\n     * @param ctx the application context\n     * @return the clipboard content or error text\n     */\n    @MainThread\n    public static String getClipboardContent(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        try {\n\n            final ClipboardManager clipboard = (ClipboardManager) ctx\n                    .getSystemService(Context.CLIPBOARD_SERVICE);\n\n            if (clipboard.getPrimaryClip() != null && clipboard.getPrimaryClip().getItemAt(0) != null\n                    && !clipboard.getPrimaryClip().getItemAt(0).getText().toString().trim().isEmpty()) {\n\n                return clipboard.getPrimaryClip().getItemAt(0).getText().toString();\n\n            } else {\n\n                return PersonalityResponse.getClipboardDataError(ctx, sl);\n            }\n\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getClipboardContent NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getClipboardContent Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return PersonalityResponse.getClipboardAccessError(ctx, sl);\n    }\n\n    /**\n     * Static method to get the primary clipboard content, or the relevant error string.\n     *\n     * @param ctx the application context\n     * @return the clipboard content or error text\n     */\n    @MainThread\n    public static Pair<Boolean, String> getClipboardContentPair(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final String content = getClipboardContent();\n\n        if (UtilsString.notNaked(content)) {\n            return new Pair<>(true, content);\n        } else {\n            return new Pair<>(false, PersonalityResponse.getClipboardDataError(ctx, sl));\n        }\n    }\n\n    /**\n     * Static method to check if the clipboard has primary content.\n     *\n     * @param ctx the application context\n     * @return true if there is content\n     */\n    public static boolean clipboardHasContent(@NonNull final Context ctx) {\n\n        try {\n\n            final ClipboardManager clipboard = (ClipboardManager) ctx\n                    .getSystemService(Context.CLIPBOARD_SERVICE);\n\n            return clipboard.getPrimaryClip() != null && clipboard.getPrimaryClip().getItemAt(0) != null\n                    && !clipboard.getPrimaryClip().getItemAt(0).getText().toString().isEmpty();\n\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"clipboardHasContent NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"clipboardHasContent Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * See if the command required the translation to be from the current clipboard content\n     *\n     * @param utterance the utterance\n     * @return true if the translation is required from the clipboard content. False otherwise\n     */\n    public static boolean isClipboard(@NonNull final Context ctx, @NonNull final String utterance) {\n        return utterance.contains(ctx.getString(ai.saiy.android.R.string.clip_board))\n                || utterance.contains(ctx.getString(ai.saiy.android.R.string.clipboard));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/custom/CommandCustom.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.custom;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.net.URISyntaxException;\nimport java.util.Set;\n\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.custom.CCC;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Resolve the user's {@link CustomCommand} and action the required outcome.\n * <p>\n * Created by benrandall76@gmail.com on 22/04/2016.\n */\npublic class CommandCustom {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandCustom.class.getSimpleName();\n\n    private static final int CUSTOM_COMMAND_VERBOSE_LIMIT = 3;\n\n    /**\n     * Action the custom command request and return the {@link Outcome}\n     *\n     * @param ctx           the application context\n     * @param customCommand the identified {@link CustomCommand}\n     * @param sl            the {@link SupportedLanguage}\n     * @return the created {@link Outcome}\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final CustomCommand customCommand,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n\n        final long then = System.nanoTime();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getCustomAction: \" + customCommand.getCustomAction().name());\n            MyLog.i(CLS_NAME, \"isExactMatch: \" + customCommand.isExactMatch());\n            MyLog.i(CLS_NAME, \"getAlgorithm: \" + customCommand.getAlgorithm().name());\n            MyLog.i(CLS_NAME, \"getKeyphrase: \" + customCommand.getKeyphrase());\n            MyLog.i(CLS_NAME, \"getResponseError: \" + customCommand.getResponseError());\n            MyLog.i(CLS_NAME, \"getResponseSuccess: \" + customCommand.getResponseSuccess());\n            MyLog.i(CLS_NAME, \"getTTSLocale: \" + customCommand.getTTSLocale());\n            MyLog.i(CLS_NAME, \"getVRLocale: \" + customCommand.getVRLocale());\n            MyLog.i(CLS_NAME, \"getAction: \" + customCommand.getAction());\n            MyLog.i(CLS_NAME, \"getScore: \" + customCommand.getScore());\n            MyLog.i(CLS_NAME, \"getCommandConstant: \" + customCommand.getCommandConstant().name());\n            MyLog.i(CLS_NAME, \"getIntent: \" + customCommand.getIntent());\n            MyLog.i(CLS_NAME, \"getIntent: \" + customCommand.getExtraText());\n\n            if (customCommand.getAlgorithm() != null) {\n                MyLog.i(CLS_NAME, \"getAlgorithm: \" + customCommand.getAlgorithm().name());\n            }\n        }\n\n        final Outcome outcome = new Outcome();\n\n        switch (customCommand.getCustomAction()) {\n\n            case CUSTOM_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_SPEECH.name());\n                }\n\n                outcome.setUtterance(customCommand.getResponseSuccess());\n                outcome.setAction(customCommand.getAction());\n                outcome.setOutcome(Outcome.SUCCESS);\n\n                break;\n            case CUSTOM_DISPLAY_CONTACT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_DISPLAY_CONTACT.name());\n                }\n                break;\n            case CUSTOM_TASKER_TASK:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_TASKER_TASK.name());\n                }\n                break;\n            case CUSTOM_ACTIVITY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_ACTIVITY.name());\n                }\n\n                Intent intent = null;\n\n                try {\n\n                    intent = Intent.parseUri(customCommand.getIntent(), 0);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Intent:\" + intent.toUri(0));\n                        examineIntent(intent);\n                    }\n\n                } catch (final URISyntaxException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Intent.parseUri: URISyntaxException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (intent != null && ExecuteIntent.executeIntent(ctx, intent)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Execute remoteIntent success\");\n                    }\n                    outcome.setUtterance(UtilsString.notNaked(customCommand.getResponseSuccess())\n                            ? customCommand.getResponseSuccess() : SaiyRequestParams.SILENCE);\n                    outcome.setAction(customCommand.getAction());\n                    outcome.setOutcome(Outcome.SUCCESS);\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Execute remoteIntent failed\");\n                    }\n                    outcome.setUtterance(UtilsString.notNaked(customCommand.getResponseError())\n                            ? customCommand.getResponseError() : SaiyRequestParams.SILENCE);\n                    outcome.setAction(customCommand.getAction());\n                    outcome.setOutcome(Outcome.FAILURE);\n                }\n\n                break;\n            case CUSTOM_CALL_CONTACT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_CALL_CONTACT.name());\n                }\n\n                break;\n            case CUSTOM_LAUNCH_APPLICATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_LAUNCH_APPLICATION.name());\n                }\n\n                break;\n            case CUSTOM_LAUNCH_SHORTCUT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_LAUNCH_SHORTCUT.name());\n                }\n                break;\n            case CUSTOM_SEARCHABLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_SEARCHABLE.name());\n                }\n                break;\n            case CUSTOM_INTENT_SERVICE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, CCC.CUSTOM_INTENT_SERVICE.name());\n                }\n\n                Intent remoteIntent = null;\n\n                try {\n\n                    remoteIntent = Intent.parseUri(customCommand.getIntent(), 0);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"remoteIntent:\" + remoteIntent.toUri(0));\n                        examineIntent(remoteIntent);\n                    }\n\n                } catch (final URISyntaxException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"remoteIntent.parseUri: URISyntaxException\");\n                        e.printStackTrace();\n                    }\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"remoteIntent.parseUri: NullPointerException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (remoteIntent != null) {\n\n                    final Pair<Boolean, String> pair = UtilsApplication.getAppNameFromPackage(ctx, remoteIntent.getPackage());\n\n                    if (pair.first) {\n\n                        Bundle bundle = remoteIntent.getExtras();\n\n                        if (bundle == null) {\n                            bundle = new Bundle();\n                        }\n\n                        bundle.putStringArrayList(Request.RESULTS_RECOGNITION, cr.getResultsArray());\n                        bundle.putFloatArray(Request.CONFIDENCE_SCORES, cr.getConfidenceArray());\n                        remoteIntent.putExtras(bundle);\n\n                        final String appName = pair.second;\n\n                        if (ExecuteIntent.startService(ctx, remoteIntent)) {\n\n                            final String verboseWords;\n\n                            if (SPH.getRemoteCommandVerbose(ctx) >= CUSTOM_COMMAND_VERBOSE_LIMIT) {\n                                verboseWords = SaiyRequestParams.SILENCE;\n                            } else {\n                                SPH.incrementRemoteCommandVerbose(ctx);\n                                verboseWords = PersonalityResponse.getRemoteSuccess(ctx, sl, appName);\n                            }\n\n                            outcome.setUtterance(verboseWords);\n                            outcome.setAction(customCommand.getAction());\n                            outcome.setOutcome(Outcome.SUCCESS);\n\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"Execute remoteIntent failed\");\n                            }\n                            outcome.setUtterance(PersonalityResponse.getErrorRemoteFailed(ctx, sl, appName));\n                            outcome.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                            outcome.setOutcome(Outcome.FAILURE);\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"remoteIntent package name unknown\");\n                        }\n\n                        outcome.setUtterance(PersonalityResponse.getErrorRemoteFailedUnknown(ctx, sl));\n                        outcome.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        outcome.setOutcome(Outcome.FAILURE);\n                    }\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"remoteIntent failed\");\n                    }\n                    outcome.setUtterance(PersonalityResponse.getErrorRemoteFailedUnknown(ctx, sl));\n                    outcome.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    outcome.setOutcome(Outcome.FAILURE);\n                }\n\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"DEFAULT\");\n                }\n                break;\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return outcome;\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param intent containing potential extras\n     */\n    private void examineIntent(@NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineIntent\");\n        }\n\n        final Bundle bundle = intent.getExtras();\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            for (final String key : keys) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"examineIntent: \" + key + \" ~ \" + bundle.get(key));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/emotion/CommandEmotion.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.emotion;\n\n/**\n * Created by benrandall76@gmail.com on 13/08/2016.\n */\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to process a command request. Used only to decide which introduction the user should hear.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandEmotion {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandEmotion.class.getSimpleName();\n\n    private static final int COMMAND_EMOTION_EXTRA_VERBOSE_LIMIT = 1;\n    private static final int COMMAND_EMOTION_VERBOSE_LIMIT = 2;\n\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n\n        final Outcome outcome = new Outcome();\n        outcome.setOutcome(Outcome.SUCCESS);\n\n        switch (SPH.getEmotionCommandVerbose(ctx)) {\n\n            case 0:\n            case COMMAND_EMOTION_EXTRA_VERBOSE_LIMIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"COMMAND_EMOTION_EXTRA_VERBOSE_LIMIT\");\n                }\n\n                outcome.setUtterance(PersonalityResponse.getBeyondVerbalExtraVerboseResponse(ctx, sl));\n                SPH.incrementEmotionCommandVerbose(ctx);\n                break;\n            case COMMAND_EMOTION_VERBOSE_LIMIT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"COMMAND_EMOTION_VERBOSE_LIMIT\");\n                }\n\n                outcome.setUtterance(PersonalityResponse.getBeyondVerbalVerboseResponse(ctx, sl));\n                SPH.incrementEmotionCommandVerbose(ctx);\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Standard response\");\n                }\n\n                outcome.setUtterance(PersonalityResponse.getBeyondVerbalIntroResponse(ctx, sl));\n                break;\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CommandEmotion.class.getSimpleName(), then);\n        }\n\n        return outcome;\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/emotion/Emotion.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.emotion;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the required command\n * <p/>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Emotion implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object emotion;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Emotion(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                   @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                emotion = new Emotion_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                emotion = new Emotion_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                emotion = new Emotion_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Emotion_en) emotion).detectCallable();\n            case ENGLISH_US:\n                return ((Emotion_en) emotion).detectCallable();\n            default:\n                return ((Emotion_en) emotion).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/emotion/Emotion_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.emotion;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to emotion analysis commands.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Emotion_en {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Emotion_en.class.getSimpleName();\n\n    private static String emotion;\n    private static String feeling;\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    public Emotion_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                      @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (emotion == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        emotion = sr.getString(R.string.emotion);\n        feeling = sr.getString(R.string.feeling);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            String word;\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(emotion) || vdLower.contains(feeling)) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length > 5) {\n\n                        for (int j = 0; j < 6; j++) {\n                            word = wordsList[j];\n                            if (word.contains(emotion) || word.contains(feeling)) {\n                                toReturn.add(new Pair<>(CC.COMMAND_EMOTION, confidence[i]));\n                                break;\n                            }\n                        }\n\n                    } else {\n                        toReturn.add(new Pair<>(CC.COMMAND_EMOTION, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"emotion: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/helper/CC.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.helper;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class (Command Constants) that lists all possible commands as enum constants. In the scheme of\n * things, using enums over integers here is of little concern - less concern than type safety and\n * the potential overhead of an equivalent implementation.\n * <p/>\n * The ordinal value of these enums should not be used as they are not guaranteed for future releases.\n * For logging purposes, use {@link Enum#name()}\n * <p/>\n * Created by benrandall76@gmail.com on 09/02/2016.\n */\npublic enum CC {\n\n    /*\n     * Inbuilt\n     */\n    COMMAND_UNKNOWN(false, false),\n    COMMAND_CANCEL(false, false),\n    COMMAND_SPELL(false, false),\n    COMMAND_TRANSLATE(true, false),\n    COMMAND_PARDON(false, false),\n    COMMAND_USER_NAME(false, false),\n    COMMAND_BATTERY(false, false),\n    COMMAND_SONG_RECOGNITION(false, false),\n    COMMAND_WOLFRAM_ALPHA(true, false),\n    COMMAND_TASKER(false, true),\n    COMMAND_EMOTION(true, true),\n    COMMAND_HOTWORD(false, false),\n    COMMAND_VOICE_IDENTIFY(true, false),\n\n    /*\n     * Custom\n     */\n    COMMAND_USER_CUSTOM(false, false),\n\n    /*\n     * Errors\n     */\n    COMMAND_EMPTY_ARRAY(false, false),\n    COMMAND_SOMETHING_WEIRD(false, false);\n\n    private final boolean requiresNetwork;\n    private final boolean isSecure;\n\n    /**\n     * Hardcoded parameter that denotes if the command can be processed without a network connection.\n     *\n     * @param requiresNetwork true if a network connection is required, false otherwise\n     * @param isSecure        true if the command must be handled securely, false otherwise\n     */\n    CC(final boolean requiresNetwork, final boolean isSecure) {\n        this.requiresNetwork = requiresNetwork;\n        this.isSecure = isSecure;\n    }\n\n    public boolean requiresNetwork() {\n        return requiresNetwork;\n    }\n\n    public boolean isSecure() {\n        return isSecure;\n    }\n\n    public static boolean isSecure(@NonNull final CC cc) {\n        return cc.isSecure();\n    }\n\n    public static boolean requiresNetwork(@NonNull final CC cc) {\n        return cc.requiresNetwork();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/helper/CommandRequest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.helper;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Helper class to prepare the parameters required to resolve a command.\n * <p/>\n * Created by benrandall76@gmail.com on 15/02/2016.\n */\npublic class CommandRequest {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandRequest.class.getSimpleName();\n\n    private CC cc;\n    private Object variableData;\n    private boolean isResolved;\n    private boolean wasSecure;\n    private ArrayList<String> resultsArray;\n    private float[] confidenceArray;\n    private String utterance;\n    private int action = LocalRequest.ACTION_SPEAK_ONLY;\n\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n\n    /**\n     * @param vrLocale  the {@link Locale} of the voice recognition\n     * @param ttsLocale the {@link Locale} of the text to speech engine\n     */\n    public CommandRequest(@NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                          @NonNull final SupportedLanguage sl) {\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n    }\n\n    /**\n     * Get the SupportedLanguage\n     *\n     * @return the {@link SupportedLanguage}\n     */\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    /**\n     * Get the Text to Speech Locale\n     *\n     * @param ctx the application context\n     * @return the Text to Speech {@link Locale}\n     */\n    public Locale getTTSLocale(@NonNull final Context ctx) {\n        if (ttsLocale != null) {\n            return ttsLocale;\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"ttsLocale null\");\n            }\n        }\n\n        return SPH.getTTSLocale(ctx);\n    }\n\n    /**\n     * Get the Voice Recognition Locale\n     *\n     * @param ctx the application context\n     * @return the Voice Recognition {@link Locale}\n     */\n    public Locale getVRLocale(@NonNull final Context ctx) {\n        if (vrLocale != null) {\n            return vrLocale;\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Locale null\");\n            }\n        }\n\n        return SPH.getVRLocale(ctx);\n    }\n\n    public CC getCC() {\n        return cc;\n    }\n\n    public void setCC(CC cc) {\n        this.cc = cc;\n    }\n\n    /**\n     * Get an Object holding further command information\n     *\n     * @return an Object of command information\n     */\n    public Object getVariableData() {\n        return variableData;\n    }\n\n    /**\n     * Set variable command information\n     *\n     * @param variableData an Object of command data\n     */\n    public void setVariableData(@NonNull final Object variableData) {\n        this.variableData = variableData;\n    }\n\n    /**\n     * Get the array of voice data\n     *\n     * @return the ArrayList<String> of voice data\n     */\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    /**\n     * Set the array of voice data\n     *\n     * @param resultsArray the array of voice data\n     */\n    public void setResultsArray(@NonNull final ArrayList<String> resultsArray) {\n        this.resultsArray = resultsArray;\n    }\n\n    /**\n     * Get the array of confidence scores associated with the voice data\n     *\n     * @return the float array of confidence scores\n     */\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n\n    /**\n     * Set the confidence scores of the voice data\n     *\n     * @param confidenceArray float of scores\n     */\n    public void setConfidenceArray(@NonNull float[] confidenceArray) {\n        this.confidenceArray = confidenceArray;\n    }\n\n    public boolean isResolved() {\n        return isResolved;\n    }\n\n    public void setResolved(final boolean resolved) {\n        isResolved = resolved;\n    }\n\n    /**\n     * Get if the request has been made in secure mode\n     *\n     * @return true if the request was made in secure mode, false otherwise\n     */\n    public boolean wasSecure() {\n        return wasSecure;\n    }\n\n    /**\n     * Set the security of the command\n     *\n     * @param wasSecure true if the request was actioned in secure mode, false otherwise\n     */\n    public void setWasSecure(final boolean wasSecure) {\n        this.wasSecure = wasSecure;\n    }\n\n    public static boolean inError(final CC commandInt) {\n\n        if (commandInt != null) {\n\n            switch (commandInt) {\n                case COMMAND_UNKNOWN:\n                case COMMAND_EMPTY_ARRAY:\n                case COMMAND_SOMETHING_WEIRD:\n                    return true;\n                default:\n                    return false;\n            }\n        }\n\n        return false;\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    public int getAction() {\n        return action;\n    }\n\n    public void setAction(final int action) {\n        this.action = action;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/hotword/Hotword.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.hotword;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the required command\n * <p/>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Hotword implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object hotword;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Hotword(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                   @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                hotword = new Hotword_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                hotword = new Hotword_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                hotword = new Hotword_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Hotword_en) hotword).detectCallable();\n            case ENGLISH_US:\n                return ((Hotword_en) hotword).detectCallable();\n            default:\n                return ((Hotword_en) hotword).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/hotword/Hotword_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.hotword;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve hotword commands.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Hotword_en {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Hotword_en.class.getSimpleName();\n\n    private static String start;\n    private static String stop;\n    private static String listening;\n    private static String hotword;\n    private static String hot_word;\n    private static String toggle;\n    private static String turn;\n    private static String on;\n    private static String off;\n    private static String word_switch;\n    private static String enable;\n    private static String disable;\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    public Hotword_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                      @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (listening == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        listening = sr.getString(R.string.listening);\n        hotword = sr.getString(R.string.hotword);\n        hot_word = sr.getString(R.string.hot_word);\n        enable = sr.getString(R.string.enable);\n        disable = sr.getString(R.string.disable);\n        start = sr.getString(R.string.start);\n        stop = sr.getString(R.string.stop);\n        toggle = sr.getString(R.string.toggle);\n        turn = sr.getString(R.string.turn);\n        on = sr.getString(R.string.on);\n        off = sr.getString(R.string.off);\n        word_switch = sr.getString(R.string.word_switch);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            String word;\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(hot_word) || vdLower.contains(hotword) || vdLower.contains(listening)) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length > 6) {\n\n                        for (int j = 0; j < 7; j++) {\n                            word = wordsList[j];\n                            if (word.contains(start) || word.contains(stop)\n                                    || word.contains(enable) || word.contains(disable)\n                                    || word.contains(toggle) || word.contains(turn)\n                                    || word.contains(on) || word.contains(off)\n                                    || word.contains(word_switch)) {\n\n                                toReturn.add(new Pair<>(CC.COMMAND_HOTWORD, confidence[i]));\n                                break;\n                            }\n                        }\n\n                    } else {\n                        toReturn.add(new Pair<>(CC.COMMAND_HOTWORD, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"hotword: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/pardon/Pardon.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.pardon;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the required command\n * <p/>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Pardon implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object pardon;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Pardon(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                  @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                pardon = new Pardon_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                pardon = new Pardon_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                pardon = new Pardon_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Pardon_en) pardon).detectCallable();\n            case ENGLISH_US:\n                return ((Pardon_en) pardon).detectCallable();\n            default:\n                return ((Pardon_en) pardon).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/pardon/Pardon_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.pardon;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve pardon/repeat commands.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Pardon_en {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Pardon_en.class.getSimpleName();\n\n    private static String pardon;\n    private static String say_that_again;\n    private static String what_did_you_say;\n    private static String come_again;\n    private static String repeat;\n    private static String said;\n    private static String that;\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    public Pardon_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                     @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (pardon == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        pardon = sr.getString(ai.saiy.android.R.string.pardon);\n        say_that_again = sr.getString(ai.saiy.android.R.string.say_that_again);\n        what_did_you_say = sr.getString(ai.saiy.android.R.string.what_did_you_say);\n        come_again = sr.getString(ai.saiy.android.R.string.come_again);\n        repeat = sr.getString(ai.saiy.android.R.string.repeat);\n        said = sr.getString(ai.saiy.android.R.string.said);\n        that = sr.getString(ai.saiy.android.R.string.that);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.startsWith(pardon)\n                        || vdLower.contains(say_that_again)\n                        || vdLower.contains(what_did_you_say)\n                        || vdLower.startsWith(come_again)\n                        || (vdLower.contains(repeat) && (vdLower.contains(said) || vdLower.contains(that)))) {\n                    toReturn.add(new Pair<>(CC.COMMAND_PARDON, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"pardon: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/songrecognition/CommandSongRecognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.songrecognition;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.defaults.ApplicationDefaults;\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionChooser;\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionProvider;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class CommandSongRecognition {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandSongRecognition.class.getSimpleName();\n\n    private final Outcome outcome = new Outcome();\n    private final Qubit qubit = new Qubit();\n    private final Intent intent = new Intent();\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                  This is not necessarily the Locale of the device, as the user may be\n     *                  multi-lingual and/or have set a custom recognition language in a launcher short-cut.\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);\n        outcome.setOutcome(Outcome.SUCCESS);\n\n        final SongRecognitionProvider provider = ApplicationDefaults.getSongRecognitionProvider(ctx);\n\n        if (setProvider(provider)) {\n            if (packageInstalled(ctx, provider)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"provider resolved. Starting\");\n                }\n                startProvider(ctx, sl, provider);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"default provider no longer installed\");\n                }\n\n                // check if only one\n                final ArrayList<SongRecognitionProvider> providers = getSongRecognitionProviders(ctx);\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSongRecognitionProviders: \" + providers.size());\n                }\n\n                switch (providers.size()) {\n\n                    case 0:\n                        // no applications installed, show chooser\n                        outcome.setUtterance(PersonalityResponse.getSongRecognitionErrorAppUninstalled(\n                                ctx, sl, SongRecognitionProvider.getApplicationName(ctx, sl, provider)));\n                        outcome.setOutcome(Outcome.FAILURE);\n                        prepareChooser(ctx, sl);\n                        outcome.setQubit(qubit);\n                        break;\n                    case 1:\n                        // Only one - start\n                        final SongRecognitionProvider singleProvider = providers.get(0);\n                        setProvider(singleProvider);\n                        startProvider(ctx, sl, provider);\n                        break;\n                    default:\n                        // More than one, show chooser\n                        outcome.setUtterance(PersonalityResponse.getSongRecognitionErrorAppUninstalled(\n                                ctx, sl, SongRecognitionProvider.getApplicationName(ctx, sl, provider)));\n                        outcome.setOutcome(Outcome.FAILURE);\n                        prepareChooser(ctx, sl);\n                        outcome.setQubit(qubit);\n                        break;\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no default application\");\n            }\n\n            // check if only one\n            final ArrayList<SongRecognitionProvider> providers = getSongRecognitionProviders(ctx);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getSongRecognitionProviders: \" + providers.size());\n            }\n\n            switch (providers.size()) {\n\n                case 1:\n                    // Only one - start\n                    final SongRecognitionProvider singleProvider = providers.get(0);\n                    setProvider(singleProvider);\n                    startProvider(ctx, sl, provider);\n                    break;\n                default:\n                    // More than one or none, show chooser\n                    outcome.setUtterance(PersonalityResponse.getSongRecognitionErrorNoApp(ctx, sl));\n                    outcome.setOutcome(Outcome.FAILURE);\n                    prepareChooser(ctx, sl);\n                    outcome.setQubit(qubit);\n                    break;\n            }\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Prepare the {@link Qubit} to contain an {@link ArrayList} of {@link SongRecognitionChooser}\n     * so the user can select or install a default choice.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     */\n    private void prepareChooser(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        qubit.setSongRecognitionChooserList(SongRecognitionChooser.prepareChooser(ctx, sl));\n    }\n\n    /**\n     * Get a list of supported install song recognition providers\n     *\n     * @param ctx the application context\n     * @return an {@link ArrayList} of {@link SongRecognitionProvider}\n     */\n    private ArrayList<SongRecognitionProvider> getSongRecognitionProviders(@NonNull final Context ctx) {\n        return Installed.getSongRecognitionProviders(ctx);\n    }\n\n    /**\n     * A provider is resolved. Attempt to start the provider.\n     * <p>\n     * If this process fails, it suggests there is a problem with the song recognition application.\n     * Attempting to take further action to help the user resolve this, could end up with the command\n     * stuck in a loop.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param provider the {@link SongRecognitionProvider} provider\n     */\n    private void startProvider(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                               @NonNull final SongRecognitionProvider provider) {\n\n        outcome.setOutcome(Outcome.SUCCESS);\n\n        if (ExecuteIntent.executeIntent(ctx, intent)) {\n            outcome.setUtterance(getResponseUtterance(ctx, sl, provider));\n        } else {\n            outcome.setUtterance(PersonalityResponse.getSongRecognitionErrorAppResponse(\n                    ctx, sl, SongRecognitionProvider.getApplicationName(ctx, sl, provider)));\n        }\n    }\n\n    /**\n     * Set the {@link Intent#setAction(String)}\n     *\n     * @param provider the user's default {@link SongRecognitionProvider} provider\n     * @return true if the process was successful or false if the {@link SongRecognitionProvider#UNKNOWN}\n     */\n    private boolean setProvider(@NonNull final SongRecognitionProvider provider) {\n\n        switch (provider) {\n\n            case SHAZAM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setProvider: \" + SongRecognitionProvider.SHAZAM.name());\n                }\n                intent.setAction(SongRecognitionProvider.SHAZAM_ACTION);\n                break;\n            case SOUND_HOUND:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setProvider: \" + SongRecognitionProvider.SOUND_HOUND.name());\n                }\n                intent.setAction(SongRecognitionProvider.SOUND_HOUND_ACTION);\n                break;\n            case TRACK_ID:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setProvider: \" + SongRecognitionProvider.TRACK_ID.name());\n                }\n                intent.setAction(SongRecognitionProvider.TRACK_ID_ACTION);\n                break;\n            case GOOGLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setProvider: \" + SongRecognitionProvider.GOOGLE.name());\n                }\n                intent.setAction(SongRecognitionProvider.GOOGLE_ACTION);\n                break;\n            case UNKNOWN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setProvider: \" + SongRecognitionProvider.UNKNOWN.name());\n                }\n                return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Check if the user's default {@link SongRecognitionProvider} provider is still installed.\n     *\n     * @param ctx      the application context\n     * @param provider the user's default {@link SongRecognitionProvider} provider\n     * @return true if the package is still installed. False otherwise.\n     */\n    private boolean packageInstalled(@NonNull final Context ctx, @NonNull final SongRecognitionProvider provider) {\n\n        switch (provider) {\n            case SHAZAM:\n                return Installed.shazamInstalled(ctx);\n            case SOUND_HOUND:\n                return Installed.soundHoundInstalled(ctx);\n            case TRACK_ID:\n                return Installed.isPackageInstalled(ctx, Installed.PACKAGE_TRACK_ID);\n            case GOOGLE:\n                return Installed.isPackageInstalled(ctx, Installed.PACKAGE_GOOGLE_SOUND_SEARCH);\n        }\n\n        return false;\n    }\n\n    /**\n     * Get the utterance to speak whilst starting the song recognition application.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param provider the {@link SongRecognitionProvider}\n     * @return the String utterance\n     */\n    private String getResponseUtterance(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                        @NonNull final SongRecognitionProvider provider) {\n        return PersonalityResponse.getSongRecognitionResponse(ctx, sl,\n                SongRecognitionProvider.getApplicationName(ctx, sl, provider));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/songrecognition/SongRecognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.songrecognition;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p/>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p/>\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class SongRecognition implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object songRecognition;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public SongRecognition(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                           @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                songRecognition = new SongRecognition_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                songRecognition = new SongRecognition_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                songRecognition = new SongRecognition_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((SongRecognition_en) songRecognition).detectCallable();\n            case ENGLISH_US:\n                return ((SongRecognition_en) songRecognition).detectCallable();\n            default:\n                return ((SongRecognition_en) songRecognition).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/songrecognition/SongRecognition_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.songrecognition;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic class SongRecognition_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SongRecognition_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String name_that_tune;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public SongRecognition_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                              @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (name_that_tune == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        name_that_tune = sr.getString(ai.saiy.android.R.string.name_that_tune);\n    }\n\n    /**\n     * Iterate through the voice data array to see if the user has requested to cancel the current\n     * speech recognition session.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(name_that_tune)) {\n                    toReturn.add(new Pair<>(CC.COMMAND_SONG_RECOGNITION, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"song recognition: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/spell/CommandSpell.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.spell;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.processing.Position;\nimport ai.saiy.android.processing.EntangledPair;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 10/02/2016.\n * <p>\n * A pretty basic class to add white space between each letter of the requested spelling. Demonstrates\n * how we use UI and background threads in commands.\n * <p>\n * The spelling needs to be copied to the clipboard and 'toasted' - neither of which can be\n * performed from a background thread.\n * <p>\n * The toast is performed in onProgressUpdate and the text copied to the clipboard\n * in onPostExecute. Simples.\n */\npublic class CommandSpell {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandSpell.class.getSimpleName();\n\n    public static final int COMMAND_SPELL_VERBOSE_LIMIT = 3;\n\n    private Outcome outcome = new Outcome();\n    private final EntangledPair entangledPair = new EntangledPair(Position.TOAST_LONG, CC.COMMAND_SPELL);\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                  This is not necessarily the Locale of the device, as the user may be\n     *                  multi-lingual and/or have set a custom recognition language in a launcher short-cut.\n     * @param cr        the {@link CommandRequest}\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final long then = System.nanoTime();\n\n        if (cr.isResolved()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: true\");\n            }\n\n            final CommandSpellValues spv = (CommandSpellValues) cr.getVariableData();\n            setOutcomeParams(ctx, sl, spv.getText());\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: false\");\n            }\n            outcome = new CommandSpellLocal().getResponse(ctx, voiceData, sl);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Set the parameters to the {@link Outcome}\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param toSpell to word to spell\n     */\n    private void setOutcomeParams(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                  @NonNull final String toSpell) {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"spell: \" + toSpell);\n        }\n\n        final Qubit qubit = new Qubit();\n        qubit.setSpellContent(toSpell);\n        outcome.setQubit(qubit);\n\n        final String separated = getSeparated(toSpell);\n        outcome.setUtterance(getResponseUtterance(ctx, sl, separated));\n        entangledPair.setToastContent(separated);\n        outcome.setEntangledPair(entangledPair);\n        outcome.setOutcome(Outcome.SUCCESS);\n    }\n\n    /**\n     * Resolve the response utterance which may or may not include a verbose explanation. This is\n     * decided by how many times the user has already heard it and won't exceed\n     * {@link #COMMAND_SPELL_VERBOSE_LIMIT}\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param toSpell the word to spell\n     * @return the prepared response utterance\n     */\n    private String getResponseUtterance(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                        @NonNull final String toSpell) {\n\n        if (SPH.getSpellCommandVerbose(ctx) >= COMMAND_SPELL_VERBOSE_LIMIT) {\n            return toSpell;\n        } else {\n            SPH.incrementSpellCommandVerbose(ctx);\n            return toSpell + \". \" + PersonalityResponse.getClipboardSpell(ctx, sl);\n        }\n    }\n\n    /**\n     * Add spaces between each character for toasting and the utterance\n     *\n     * @param toSpell the String to spell\n     * @return a space separated String\n     */\n    private String getSeparated(@NonNull final String toSpell) {\n        return toSpell.replaceAll(\".(?=.)\", \"$0 \").trim();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/spell/CommandSpellLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.spell;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.processing.Position;\nimport ai.saiy.android.processing.EntangledPair;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 10/02/2016.\n * <p>\n * A pretty basic class to add white space between each letter of the requested spelling. Demonstrates\n * how we use UI and background threads in commands.\n * <p>\n * The spelling needs to be copied to the clipboard and 'toasted' - neither of which can be\n * performed from a background thread.\n * <p>\n * The toast is performed in onProgressUpdate and the text copied to the clipboard\n * in onPostExecute. Simples.\n */\npublic class CommandSpellLocal {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandSpellLocal.class.getSimpleName();\n\n    private final Outcome outcome = new Outcome();\n    private final EntangledPair entangledPair = new EntangledPair(Position.TOAST_LONG, CC.COMMAND_SPELL);\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                  This is not necessarily the Locale of the device, as the user may be\n     *                  multi-lingual and/or have set a custom recognition language in a launcher short-cut.\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n\n        final ArrayList<String> spellData = new Spell(sl).sort(ctx, voiceData);\n\n        if (!spellData.isEmpty()) {\n            setOutcomeParams(ctx, sl, spellData.get(0));\n        } else {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"spell data empty\");\n            }\n\n            outcome.setUtterance(PersonalityResponse.getSpellError(ctx, sl));\n            outcome.setOutcome(Outcome.FAILURE);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Set the parameters to the {@link Outcome}\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param toSpell to word to spell\n     */\n    private void setOutcomeParams(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                  @NonNull final String toSpell) {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"spell: \" + toSpell);\n        }\n\n        final Qubit qubit = new Qubit();\n        qubit.setSpellContent(toSpell);\n        outcome.setQubit(qubit);\n\n        final String separated = getSeparated(toSpell);\n        outcome.setUtterance(getResponseUtterance(ctx, sl, separated));\n        entangledPair.setToastContent(separated);\n        outcome.setEntangledPair(entangledPair);\n        outcome.setOutcome(Outcome.SUCCESS);\n    }\n\n    /**\n     * Resolve the response utterance which may or may not include a verbose explanation. This is\n     * decided by how many times the user has already heard it and won't exceed\n     * {@link CommandSpell#COMMAND_SPELL_VERBOSE_LIMIT}\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param toSpell the word to spell\n     * @return the prepared response utterance\n     */\n    private String getResponseUtterance(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                        @NonNull final String toSpell) {\n\n        if (SPH.getSpellCommandVerbose(ctx) >= CommandSpell.COMMAND_SPELL_VERBOSE_LIMIT) {\n            return toSpell;\n        } else {\n            SPH.incrementSpellCommandVerbose(ctx);\n            return toSpell + \". \" + PersonalityResponse.getClipboardSpell(ctx, sl);\n        }\n    }\n\n    /**\n     * Add spaces between each character for toasting and the utterance\n     *\n     * @param toSpell the String to spell\n     * @return a space separated String\n     */\n    private String getSeparated(@NonNull final String toSpell) {\n        return toSpell.replaceAll(\".(?=.)\", \"$0 \").trim();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/spell/CommandSpellValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.spell;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 01/06/2016.\n */\npublic class CommandSpellValues {\n\n    private String text;\n    private long startIndex;\n    private long endIndex;\n    private int[][] ranges;\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n\n    public void setRanges(@NonNull final int[][] ranges) {\n        this.ranges = ranges;\n    }\n\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public void setEndIndex(final long endIndex) {\n        this.endIndex = endIndex;\n    }\n\n    public void setStartIndex(final long startIndex) {\n        this.startIndex = startIndex;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(@NonNull final String text) {\n        this.text = text;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/spell/Spell.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.spell;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Spell implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object spell;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Spell(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                 @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                spell = new Spell_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                spell = new Spell_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                spell = new Spell_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public Spell(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Spell_en) spell).detectCallable();\n            case ENGLISH_US:\n                return ((Spell_en) spell).detectCallable();\n            default:\n                return ((Spell_en) spell).detectCallable();\n        }\n    }\n\n    /**\n     * Strip out all but the required command and prepare the strings to use\n     *\n     * @param voiceData ArrayList<String> of voice data\n     * @return only the voice data associated with this command, prepared to use.\n     */\n    public ArrayList<String> sort(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return Spell_en.sortSpell(ctx, voiceData, sl);\n            case ENGLISH_US:\n                return Spell_en.sortSpell(ctx, voiceData, sl);\n            default:\n                return Spell_en.sortSpell(ctx, voiceData, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/spell/Spell_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.spell;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve spelling commands.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Spell_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Spell_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String spell_;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Spell_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                    @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (spell_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        spell_ = sr.getString(R.string.spell_);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final String spell = spell_.trim();\n\n            String word;\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(spell_)) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length > 6) {\n\n                        for (int j = 0; j < 7; j++) {\n                            word = wordsList[j];\n                            if (word.contains(spell)) {\n                                toReturn.add(new Pair<>(CC.COMMAND_SPELL, confidence[i]));\n                                break;\n                            }\n                        }\n\n                    } else {\n                        toReturn.add(new Pair<>(CC.COMMAND_SPELL, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"spell: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Static method.\n     * <p>\n     * Iterate through the voice data array to return only the voice data associated with the\n     * required command.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @return an array list containing only the required data\n     */\n    public static ArrayList<String> sortSpell(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                                              @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n        final ArrayList<String> toReturn = new ArrayList<>();\n\n        if (spell_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        String vdLower;\n        String[] separated;\n        for (final String vd : voiceData) {\n            vdLower = vd.toLowerCase(loc).trim();\n\n            if (vdLower.startsWith(spell_) || vdLower.contains(spell_)) {\n                separated = vdLower.split(spell_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            }\n        }\n\n        if (!toReturn.isEmpty()) {\n            final Set<String> deduplicated = new LinkedHashSet<>(toReturn);\n            toReturn.clear();\n            toReturn.addAll(deduplicated);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/tasker/CommandTasker.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.tasker;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.applications.Install;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.AlgorithmicContainer;\nimport ai.saiy.android.nlu.local.AlgorithmicResolver;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.thirdparty.tasker.TaskerHelper;\nimport ai.saiy.android.thirdparty.tasker.TaskerTask;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to process a command request. Handles both remote NLP intents and falling back to\n * resolving locally.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandTasker {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandTasker.class.getSimpleName();\n\n    private long then;\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @param cr        the {@link CommandRequest}\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        then = System.nanoTime();\n\n        final Outcome outcome = new Outcome();\n\n        final TaskerHelper taskerHelper = new TaskerHelper();\n        final Pair<Boolean, String> taskerPair = taskerHelper.isTaskerInstalled(ctx);\n        String taskerPackage;\n\n        if (taskerPair.first) {\n\n            taskerPackage = taskerPair.second;\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"tasker installed: \" + taskerPackage);\n            }\n\n            final Pair<Boolean, Boolean> taskerStatusPair = taskerHelper.canInteract(ctx);\n\n            if (taskerStatusPair.first) {\n                if (taskerStatusPair.second) {\n                    if (taskerHelper.receiverExists(ctx)) {\n\n                        final ArrayList<TaskerTask> taskList = taskerHelper.getTasks(ctx);\n\n                        if (UtilsList.notNaked(taskList)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"task count: \" + taskList.size());\n                            }\n\n                            final ArrayList<String> taskNames = new ArrayList<>();\n                            String taskName;\n\n                            if (cr.isResolved()) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"isResolved: true\");\n                                }\n\n                                final CommandTaskerValues cwav = (CommandTaskerValues) cr.getVariableData();\n                                taskName = cwav.getTaskName();\n\n                                if (UtilsString.notNaked(taskName)) {\n                                    taskNames.add(taskName);\n                                }\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"isResolved: false\");\n                                }\n                                taskNames.addAll(new CommandTaskerLocal().getResponse(ctx, voiceData, sl));\n                            }\n\n                            if (UtilsList.notNaked(taskNames)) {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"taskNames size: \" + taskNames.size());\n                                }\n\n                                final ArrayList<String> taskNameList = new ArrayList<>(taskList.size());\n\n                                for (final TaskerTask taskerTask : taskList) {\n                                    taskNameList.add(taskerTask.getTaskName());\n                                }\n\n                                final AlgorithmicResolver resolver = new AlgorithmicResolver(ctx,\n                                        Algorithm.getAlgorithms(ctx, sl), sl.getLocale(), taskNames,\n                                        taskNameList, AlgorithmicResolver.THREADS_TIMEOUT_500, false);\n\n                                final AlgorithmicContainer container = resolver.resolve();\n\n                                if (container != null) {\n\n                                    final boolean exactMatch = container.isExactMatch();\n                                    if (DEBUG) {\n                                        MyLog.d(CLS_NAME, \"container exactMatch: \" + exactMatch);\n                                        MyLog.d(CLS_NAME, \"container getInput: \" + container.getInput());\n                                        MyLog.d(CLS_NAME, \"container getGenericMatch: \" + container.getGenericMatch());\n                                        MyLog.d(CLS_NAME, \"container getAlgorithm: \" + container.getAlgorithm().name());\n                                        MyLog.d(CLS_NAME, \"container getScore: \" + container.getScore());\n                                        MyLog.d(CLS_NAME, \"container getParentPosition: \" + container.getParentPosition());\n                                        MyLog.d(CLS_NAME, \"container getVariableData: \" + container.getVariableData());\n                                    }\n\n                                    TaskerTask taskerTask = null;\n\n                                    try {\n                                        taskerTask = taskList.get(container.getParentPosition());\n                                    } catch (final IndexOutOfBoundsException e) {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"taskList IndexOutOfBoundsException\");\n                                            e.printStackTrace();\n                                        }\n                                    }\n\n                                    if (taskerTask != null) {\n                                        if (DEBUG) {\n                                            MyLog.d(CLS_NAME, \"taskerTask getProjectName: \" + taskerTask.getProjectName());\n                                            MyLog.d(CLS_NAME, \"taskerTask getTaskName: \" + taskerTask.getTaskName());\n                                        }\n\n                                        if (taskerHelper.executeTask(ctx, taskerTask.getTaskName())) {\n\n                                            if (SPH.getAnnounceTasker(ctx)) {\n                                                outcome.setUtterance(PersonalityResponse.getTaskerTaskExecutedResponse(\n                                                        ctx, sl, taskerTask.getTaskName()));\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.w(CLS_NAME, \"taskerTask don't announce\");\n                                                }\n\n                                                outcome.setUtterance(SaiyRequestParams.SILENCE);\n                                            }\n\n                                            outcome.setOutcome(Outcome.SUCCESS);\n\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.w(CLS_NAME, \"taskerTask failed to execute\");\n                                            }\n\n                                            outcome.setUtterance(PersonalityResponse.getTaskerTaskFailedResponse(ctx, sl,\n                                                    taskerTask.getTaskName()));\n                                            outcome.setOutcome(Outcome.FAILURE);\n                                            return returnOutcome(outcome);\n                                        }\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"index out of bounds\");\n                                        }\n\n                                        outcome.setUtterance(PersonalityResponse.getTaskerTaskNotMatchedResponse(ctx, sl));\n                                        outcome.setOutcome(Outcome.FAILURE);\n                                        return returnOutcome(outcome);\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"failed to find a match\");\n                                    }\n\n                                    outcome.setUtterance(PersonalityResponse.getTaskerTaskNotMatchedResponse(ctx, sl));\n                                    outcome.setOutcome(Outcome.FAILURE);\n                                    return returnOutcome(outcome);\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"empty task name request\");\n                                }\n\n                                outcome.setUtterance(PersonalityResponse.getTaskerTaskNotMatchedResponse(ctx, sl));\n                                outcome.setOutcome(Outcome.FAILURE);\n                                return returnOutcome(outcome);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"no tasks\");\n                            }\n\n                            outcome.setUtterance(PersonalityResponse.getTaskerNoTasksResponse(ctx, sl));\n                            outcome.setOutcome(Outcome.FAILURE);\n                            return returnOutcome(outcome);\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"no receiver\");\n                        }\n\n                        final Bundle bundle = new Bundle();\n                        bundle.putInt(ActivityHome.FRAGMENT_INDEX, ActivityHome.INDEX_FRAGMENT_BUGS);\n                        ExecuteIntent.saiyActivity(ctx, ActivityHome.class, bundle, true);\n\n                        outcome.setUtterance(PersonalityResponse.getTaskerInstallOrderResponse(ctx, sl));\n                        outcome.setOutcome(Outcome.FAILURE);\n                        return returnOutcome(outcome);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"remote access error\");\n                    }\n\n                    taskerHelper.showTaskerExternalAccess(ctx);\n\n                    outcome.setUtterance(PersonalityResponse.getTaskerExternalAccessResponse(ctx, sl));\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return returnOutcome(outcome);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"tasker disabled\");\n                }\n\n                UtilsApplication.launchAppFromPackageName(ctx, taskerPackage);\n\n                outcome.setUtterance(PersonalityResponse.getTaskerDisabledResponse(ctx, sl));\n                outcome.setOutcome(Outcome.FAILURE);\n                return returnOutcome(outcome);\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"tasker not installed\");\n            }\n\n            Install.showInstallLink(ctx, Installed.PACKAGE_TASKER_MARKET);\n\n            outcome.setUtterance(PersonalityResponse.getTaskerInstallResponse(ctx, sl));\n            outcome.setOutcome(Outcome.FAILURE);\n            return returnOutcome(outcome);\n        }\n\n        return returnOutcome(outcome);\n    }\n\n    /**\n     * A single point of return to check the elapsed time for debugging.\n     *\n     * @param outcome the constructed {@link Outcome}\n     * @return the constructed {@link Outcome}\n     */\n    private Outcome returnOutcome(@NonNull final Outcome outcome) {\n        if (DEBUG) {\n            MyLog.getElapsed(CommandTasker.class.getSimpleName(), then);\n        }\n        return outcome;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/tasker/CommandTaskerLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.tasker;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class that uses strings to extract the Tasker Task the user wishes to execute\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandTaskerLocal {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandTaskerLocal.class.getSimpleName();\n\n    /**\n     * Resolve the possible task names and return them in an ArrayList.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @return the ArrayList of task names\n     */\n    public ArrayList<String> getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                                         @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final ArrayList<String> nameData = new Tasker(sl).sort(ctx, voiceData);\n        if (UtilsList.notNaked(nameData)) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"nameData: \" + nameData.size() + \" : \" + nameData.toString());\n            }\n            return nameData;\n        } else {\n            return new ArrayList<>();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/tasker/CommandTaskerValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.tasker;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class to package the remote NLP request\n * <p>\n * Created by benrandall76@gmail.com on 10/08/2016.\n */\n\npublic class CommandTaskerValues {\n\n    private String taskName;\n    private int[][] ranges;\n    private long startIndex;\n    private long endIndex;\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n\n    public void setRanges(@NonNull final int[][] ranges) {\n        this.ranges = ranges;\n    }\n\n    public String getTaskName() {\n        return taskName;\n    }\n\n    public void setTaskName(@NonNull final String taskName) {\n        this.taskName = taskName;\n    }\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public void setEndIndex(final long endIndex) {\n        this.endIndex = endIndex;\n    }\n\n    public void setStartIndex(final long startIndex) {\n        this.startIndex = startIndex;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/tasker/Tasker.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.tasker;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Tasker implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object tasker;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Tasker(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                  @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                tasker = new Tasker_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                tasker = new Tasker_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                tasker = new Tasker_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public Tasker(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Tasker_en) tasker).detectCallable();\n            case ENGLISH_US:\n                return ((Tasker_en) tasker).detectCallable();\n            default:\n                return ((Tasker_en) tasker).detectCallable();\n        }\n    }\n\n    /**\n     * Strip out all but the required command and prepare the strings to use\n     *\n     * @param voiceData ArrayList<String> of voice data\n     * @return only the voice data associated with this command, prepared to use.\n     */\n    public ArrayList<String> sort(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return Tasker_en.sortTasker(ctx, voiceData, sl);\n            case ENGLISH_US:\n                return Tasker_en.sortTasker(ctx, voiceData, sl);\n            default:\n                return Tasker_en.sortTasker(ctx, voiceData, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/tasker/Tasker_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.tasker;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve Tasker commands.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class Tasker_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Tasker_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String tasker;\n    private static String task_;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Tasker_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                     @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (tasker == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        tasker = sr.getString(R.string.tasker);\n        task_ = sr.getString(R.string.task_);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            String word;\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(task_) && vdLower.contains(tasker)) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length > 4) {\n\n                        for (int j = 0; j < 5; j++) {\n                            word = wordsList[j];\n                            if (word.contains(task_) || word.contains(tasker)) {\n                                toReturn.add(new Pair<>(CC.COMMAND_TASKER, confidence[i]));\n                                break;\n                            }\n                        }\n\n                    } else {\n                        toReturn.add(new Pair<>(CC.COMMAND_TASKER, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Tasker: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Static method.\n     * <p>\n     * Iterate through the voice data array to return only the voice data associated with the\n     * required command.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @return an array list containing only the required data\n     */\n    public static ArrayList<String> sortTasker(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                                               @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n        final ArrayList<String> toReturn = new ArrayList<>();\n\n        final SaiyResources sr = new SaiyResources(ctx, sl);\n\n        if (task_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        final String called_ = sr.getString(R.string.called_);\n        final String named_ = sr.getString(R.string.named_);\n        sr.reset();\n\n        String vdLower;\n        String[] separated;\n        String separatedString;\n        for (final String vd : voiceData) {\n            vdLower = vd.toLowerCase(loc).trim();\n\n            if (vdLower.contains(task_) && vdLower.contains(tasker)) {\n                separated = vdLower.split(task_);\n\n                if (separated.length > 1) {\n\n                    separatedString = separated[1].trim();\n\n                    if (separatedString.startsWith(called_)) {\n\n                        separated = separatedString.split(called_);\n\n                        if (separated.length > 1) {\n                            toReturn.add(separated[1].trim());\n                        } else {\n                            toReturn.add(separatedString);\n                        }\n\n                    } else if (separatedString.startsWith(named_)) {\n\n                        separated = separatedString.split(named_);\n\n                        if (separated.length > 1) {\n                            toReturn.add(separated[1].trim());\n                        } else {\n                            toReturn.add(separatedString);\n                        }\n\n                    } else {\n                        toReturn.add(separatedString);\n                    }\n                }\n            }\n        }\n\n        if (!toReturn.isEmpty()) {\n            final Set<String> deduplicated = new LinkedHashSet<>(toReturn);\n            toReturn.clear();\n            toReturn.addAll(deduplicated);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/CommandTranslate.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.command.translate.provider.bing.BingTranslate;\nimport ai.saiy.android.command.translate.provider.google.GoogleTranslate;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to process a Translation request. Handles both remote NLP intents and falling back to\n * resolving locally.\n * <p>\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic class CommandTranslate {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = CommandTranslate.class.getSimpleName();\n\n    public static final long CLIPBOARD_DELAY = 175L;\n\n    private long then;\n\n    /**\n     * Resolve the translation request and return the {@link Outcome}\n     *\n     * @param ctx       the application context\n     * @param voiceData the array list of voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @param cr        the {@link CommandRequest}\n     * @return the created {@link Outcome}\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        then = System.nanoTime();\n\n        if (cr.isResolved()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: true\");\n            }\n\n            switch (SPH.getDefaultTranslationProvider(ctx)) {\n\n                case TranslationProvider.TRANSLATION_PROVIDER_BING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"TRANSLATION_PROVIDER_BING\");\n                    }\n                    return returnOutcome(new BingTranslate(ctx, sl, cr).getResponse());\n                case TranslationProvider.TRANSLATION_PROVIDER_GOOGLE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"TRANSLATION_PROVIDER_GOOGLE\");\n                    }\n                    return returnOutcome(new GoogleTranslate(ctx, sl, cr).getResponse());\n                case TranslationProvider.TRANSLATION_PROVIDER_UNKNOWN:\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"TRANSLATION_PROVIDER_UNKNOWN\");\n                    }\n                    return returnOutcome(new BingTranslate(ctx, sl, cr).getResponse());\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: false\");\n            }\n\n            return returnOutcome(new CommandTranslateLocal().getResponse(ctx, voiceData, sl, cr));\n        }\n    }\n\n    /**\n     * A single point of return to check the elapsed time in debugging.\n     *\n     * @param outcome the constructed {@link Outcome}\n     * @return the constructed {@link Outcome}\n     */\n    private Outcome returnOutcome(@NonNull final Outcome outcome) {\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n        return outcome;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/CommandTranslateLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.clipboard.ClipboardHelper;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.command.translate.provider.bing.BingTranslate;\nimport ai.saiy.android.command.translate.provider.bing.TranslationLanguageBing;\nimport ai.saiy.android.command.translate.provider.google.GoogleTranslate;\nimport ai.saiy.android.command.translate.provider.google.TranslationLanguageGoogle;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.processing.EntangledPair;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.processing.Position;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsLocale;\n\nimport static ai.saiy.android.command.translate.CommandTranslate.CLIPBOARD_DELAY;\n\n/**\n * Created by benrandall76@gmail.com on 03/05/2016.\n */\npublic class CommandTranslateLocal {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = CommandTranslate.class.getSimpleName();\n\n    private final Outcome outcome = new Outcome();\n    private final EntangledPair entangledPair = new EntangledPair(Position.TOAST_LONG, CC.COMMAND_TRANSLATE);\n\n    /**\n     * Resolve the translation request and return the {@link Outcome}\n     *\n     * @param ctx       the application context\n     * @param voiceData the array list of voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @param cr        the {@link CommandRequest}\n     * @return the created {@link Outcome}\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        outcome.setTTSLocale(cr.getTTSLocale(ctx));\n\n        switch (SPH.getDefaultTranslationProvider(ctx)) {\n\n            case TranslationProvider.TRANSLATION_PROVIDER_BING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"TRANSLATION_PROVIDER_BING\");\n                }\n\n                final Pair<TranslationLanguageBing, String> bingPair = BingTranslate.extract(ctx, voiceData, sl);\n\n                if (bingPair.first != null && bingPair.second != null) {\n\n                    String translationRequest = bingPair.second;\n\n                    if (ClipboardHelper.isClipboard(ctx, translationRequest)) {\n\n                        try {\n                            Thread.sleep(CLIPBOARD_DELAY);\n                        } catch (final InterruptedException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"InterruptedException\");\n                                e.printStackTrace();\n                            }\n                        }\n\n                        final Pair<Boolean, String> clipboardPair = ClipboardHelper.getClipboardContentPair(ctx,\n                                cr.getSupportedLanguage());\n\n                        if (clipboardPair.first) {\n                            translationRequest = clipboardPair.second;\n                        } else {\n                            outcome.setUtterance(clipboardPair.second);\n                            outcome.setOutcome(Outcome.FAILURE);\n                            return outcome;\n                        }\n                    }\n\n                    if (!BingTranslate.tooLong(translationRequest)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"translationRequest: \" + translationRequest);\n                        }\n\n                        final Pair<Boolean, String> translationResult = BingTranslate.execute(ctx,\n                                bingPair.first, translationRequest);\n\n                        if (translationResult.first) {\n                            outcome.setUtterance(translationResult.second);\n                            outcome.setOutcome(Outcome.SUCCESS);\n                            entangledPair.setToastContent(translationResult.second);\n                            outcome.setEntangledPair(entangledPair);\n\n                            final Qubit qubit = new Qubit();\n                            qubit.setTranslatedText(translationResult.second);\n                            outcome.setQubit(qubit);\n                            outcome.setTTSLocale(bingPair.first.getLocale());\n                        } else {\n                            outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate,\n                                    PersonalityHelper.getUserNameOrNot(ctx)));\n                            outcome.setOutcome(Outcome.FAILURE);\n                        }\n                    } else {\n                        outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate_length,\n                                PersonalityHelper.getUserNameOrNot(ctx)));\n                        outcome.setOutcome(Outcome.FAILURE);\n                    }\n                } else {\n                    outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate_unsupported,\n                            PersonalityHelper.getUserNameOrNot(ctx)));\n                    outcome.setOutcome(Outcome.FAILURE);\n                }\n\n                return outcome;\n            case TranslationProvider.TRANSLATION_PROVIDER_GOOGLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"TRANSLATION_PROVIDER_GOOGLE\");\n                }\n\n                final Pair<TranslationLanguageGoogle, String> googlePair = GoogleTranslate.extract(ctx, voiceData, sl);\n\n                if (googlePair.first != null && googlePair.second != null) {\n\n                    String translationRequest = googlePair.second;\n\n                    if (ClipboardHelper.isClipboard(ctx, translationRequest)) {\n\n                        try {\n                            Thread.sleep(CLIPBOARD_DELAY);\n                        } catch (final InterruptedException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"InterruptedException\");\n                                e.printStackTrace();\n                            }\n                        }\n\n                        final Pair<Boolean, String> clipboardPair = ClipboardHelper.getClipboardContentPair(ctx,\n                                cr.getSupportedLanguage());\n\n                        if (clipboardPair.first) {\n                            translationRequest = clipboardPair.second;\n                        } else {\n                            outcome.setUtterance(clipboardPair.second);\n                            outcome.setOutcome(Outcome.FAILURE);\n                            return outcome;\n                        }\n                    }\n\n                    if (!GoogleTranslate.tooLong(translationRequest)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"translationRequest: \" + translationRequest);\n                        }\n\n                        final Pair<Boolean, String> translationResult = GoogleTranslate.execute(ctx,\n                                googlePair.first, translationRequest);\n\n                        if (translationResult.first) {\n                            outcome.setUtterance(translationResult.second);\n                            outcome.setOutcome(Outcome.SUCCESS);\n                            entangledPair.setToastContent(translationResult.second);\n                            outcome.setEntangledPair(entangledPair);\n                            final Qubit qubit = new Qubit();\n                            qubit.setTranslatedText(translationResult.second);\n                            outcome.setQubit(qubit);\n                            outcome.setTTSLocale(UtilsLocale.stringToLocale(googlePair.first.getLanguage()));\n                        } else {\n                            outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate,\n                                    PersonalityHelper.getUserNameOrNot(ctx)));\n                            outcome.setOutcome(Outcome.FAILURE);\n                        }\n                    } else {\n                        outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate_length,\n                                PersonalityHelper.getUserNameOrNot(ctx)));\n                        outcome.setOutcome(Outcome.FAILURE);\n                    }\n                } else {\n                    outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate_unsupported,\n                            PersonalityHelper.getUserNameOrNot(ctx)));\n                    outcome.setOutcome(Outcome.FAILURE);\n                }\n\n                return outcome;\n            case TranslationProvider.TRANSLATION_PROVIDER_UNKNOWN:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"TRANSLATION_PROVIDER_UNKNOWN\");\n                }\n                outcome.setUtterance(ctx.getString(ai.saiy.android.R.string.error_translate_unsupported,\n                        PersonalityHelper.getUserNameOrNot(ctx)));\n                outcome.setOutcome(Outcome.FAILURE);\n                return outcome;\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/CommandTranslateValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 01/06/2016.\n */\npublic class CommandTranslateValues {\n\n    private String language;\n    private String text;\n    private int[][] LanguageRanges;\n    private int[][] textRanges;\n    private long languageStartIndex;\n    private long languageEndIndex;\n    private long textStartIndex;\n    private long textEndIndex;\n\n    public long getLanguageEndIndex() {\n        return languageEndIndex;\n    }\n\n    public void setLanguageEndIndex(final long languageEndIndex) {\n        this.languageEndIndex = languageEndIndex;\n    }\n\n    public long getLanguageStartIndex() {\n        return languageStartIndex;\n    }\n\n    public void setLanguageStartIndex(final long languageStartIndex) {\n        this.languageStartIndex = languageStartIndex;\n    }\n\n    public long getTextEndIndex() {\n        return textEndIndex;\n    }\n\n    public void setTextEndIndex(final long textEndIndex) {\n        this.textEndIndex = textEndIndex;\n    }\n\n    public long getTextStartIndex() {\n        return textStartIndex;\n    }\n\n    public void setTextStartIndex(final long textStartIndex) {\n        this.textStartIndex = textStartIndex;\n    }\n\n    public int[][] getLanguageRanges() {\n        return LanguageRanges;\n    }\n\n    public void setLanguageRanges(@NonNull final int[][] languageRanges) {\n        LanguageRanges = languageRanges;\n    }\n\n    public int[][] getTextRanges() {\n        return textRanges;\n    }\n\n    public void setTextRanges(final int[][] textRanges) {\n        this.textRanges = textRanges;\n    }\n\n    public void setLanguage(@NonNull final String language) {\n        this.language = language;\n    }\n\n    public String getLanguage() {\n        return language;\n    }\n\n    public void setText(@NonNull final String text) {\n        this.text = text;\n    }\n\n    public String getText() {\n        return text;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/Translate.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.partial.PartialHelper;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class Translate implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object translate;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Translate(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                     @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                translate = new Translate_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                translate = new Translate_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                translate = new Translate_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used in the {@link PartialHelper}\n     * <p>\n     * Used when a {@link SaiyResources} object is being created elsewhere.\n     *\n     * @param sl    the {@link SupportedLanguage}\n     * @param sr    the {@link SaiyResources}\n     * @param reset true if the {@link SaiyResources} should be reset.\n     */\n    public Translate(@NonNull final SupportedLanguage sl, @NonNull final SaiyResources sr,\n                     final boolean reset) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                translate = new Translate_en(sr, reset);\n                break;\n            case ENGLISH_US:\n                translate = new Translate_en(sr, reset);\n                break;\n            default:\n                translate = new Translate_en(sr, reset);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a Translate command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public Translate(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((Translate_en) translate).detectCallable();\n            case ENGLISH_US:\n                return ((Translate_en) translate).detectCallable();\n            default:\n                return ((Translate_en) translate).detectCallable();\n        }\n    }\n\n    /**\n     * Strip out all but the required command and prepare the strings to use\n     *\n     * @param ctx       the application context\n     * @param utterance the utterance\n     * @param language  the language of which the user requested\n     * @return only the voice data associated with this command, that is required to be used.\n     */\n    public String resolveBody(@NonNull final Context ctx, @NonNull final String utterance,\n                              @NonNull final String language) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return Translate_en.resolveBody(ctx, utterance, language, sl);\n            case ENGLISH_US:\n                return Translate_en.resolveBody(ctx, utterance, language, sl);\n            default:\n                return Translate_en.resolveBody(ctx, utterance, language, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/TranslatePartial.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.partial.Partial;\nimport ai.saiy.android.partial.PartialHelper;\n\n/**\n * Created by benrandall76@gmail.com on 26/04/2016.\n */\npublic class TranslatePartial implements Callable<Pair<Boolean, Integer>> {\n\n    private final SupportedLanguage sl;\n    private final Object translate;\n    private Bundle results;\n\n    /**\n     * Constructor (used in the {@link PartialHelper}\n     * <p>\n     * Used when a {@link SaiyResources} object is being handled elsewhere\n     *\n     * @param sl the {@link SupportedLanguage}\n     * @param sr the {@link SaiyResources}\n     */\n    public TranslatePartial(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                translate = new Translate_en(sr, false);\n                break;\n            case ENGLISH_US:\n                translate = new Translate_en(sr, false);\n                break;\n            default:\n                translate = new Translate_en(sr, false);\n                break;\n        }\n    }\n\n    /**\n     * Set the partial results to analyse during a recognition loop.\n     *\n     * @param results the {@link Bundle} of recognition results\n     */\n    public void setPartialData(@NonNull final Bundle results) {\n        this.results = results;\n    }\n\n    /**\n     * Will loop through an array to detect the command. The initialisation of any localised resources\n     * will only take place once in the constructor, which is better for performance.\n     * <p>\n     * The language to be used is decided by the {@link SupportedLanguage} object\n     *\n     * @return a {@link Pair} with the first parameter denoting detection and the second the\n     * {@link Partial} constant.\n     */\n    public Pair<Boolean, Integer> detectPartial() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return new Pair<>(((Translate_en) translate).detectPartial(sl.getLocale(), results),\n                        Partial.TRANSLATE);\n            case ENGLISH_US:\n                return new Pair<>(((Translate_en) translate).detectPartial(sl.getLocale(), results),\n                        Partial.TRANSLATE);\n            default:\n                return new Pair<>(((Translate_en) translate).detectPartial(SupportedLanguage.ENGLISH.getLocale(),\n                        results), Partial.TRANSLATE);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Pair<Boolean, Integer> call() throws Exception {\n        return detectPartial();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/Translate_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.translate.provider.bing.TranslationLanguageBing;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Helper class to resolve translation commands\n * <p/>\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class Translate_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Translate_en.class.getSimpleName();\n\n    private SupportedLanguage sl;\n    private ArrayList<String> voiceData;\n    private float[] confidence;\n\n    private static String translate_;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public Translate_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                        @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (translate_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        translate_ = sr.getString(ai.saiy.android.R.string.translate_);\n    }\n\n    /**\n     * Constructor\n     *\n     * @param sr    the {@link SaiyResources}\n     * @param reset true if the {@link SaiyResources} should be reset immediately\n     */\n    public Translate_en(@NonNull final SaiyResources sr, final boolean reset) {\n        translate_ = sr.getString(ai.saiy.android.R.string.translate_);\n\n        if (reset) {\n            sr.reset();\n        }\n    }\n\n    /**\n     * Iterate through the voice data array to see if the user has requested to cancel the current\n     * speech recognition session.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(translate_)) {\n                    toReturn.add(new Pair<>(CC.COMMAND_TRANSLATE, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"translate: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Iterate through the voice data array to see if the user has requested a possible translation\n     * command.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, perhaps implementing a\n     * matcher, would probably be overkill.\n     *\n     * @param results {@link Bundle} containing the voice data\n     * @param loc     the {@link SupportedLanguage} {@link Locale}\n     * @return true if a command variant is detected\n     */\n    public boolean detectPartial(@NonNull final Locale loc, @NonNull final Bundle results) {\n\n        final long then = System.nanoTime();\n        boolean translate = false;\n\n        if (UtilsBundle.notNaked(results)) {\n            if (!UtilsBundle.isSuspicious(results)) {\n\n                final ArrayList<String> partialData = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n\n                /* handles empty string bug */\n                if (UtilsList.notNaked(partialData)) {\n                    partialData.removeAll(Collections.singleton(\"\"));\n\n                    if (!partialData.isEmpty()) {\n\n                        String vdLower;\n                        int size = partialData.size();\n                        for (int i = 0; i < size; i++) {\n                            vdLower = partialData.get(i).toLowerCase(loc).trim();\n                            if (vdLower.startsWith(translate_)) {\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"partial vd: \" + vdLower);\n                                }\n                                translate = true;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                if (!translate) {\n\n                    final ArrayList<String> unstableData = results.getStringArrayList(RecognitionNative.UNSTABLE_RESULTS);\n\n                    /* handles empty string bug */\n                    if (UtilsList.notNaked(unstableData)) {\n                        unstableData.removeAll(Collections.singleton(\"\"));\n\n                        if (!unstableData.isEmpty()) {\n\n                            String vdLower;\n                            int size = unstableData.size();\n                            for (int i = 0; i < size; i++) {\n                                vdLower = unstableData.get(i).toLowerCase(loc).trim();\n                                if (vdLower.startsWith(translate_)) {\n\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"unstable vd: \" + vdLower);\n                                    }\n                                    translate = true;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"isTranslate: bundle has been tampered with\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isTranslate: returning ~ \" + translate);\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return translate;\n    }\n\n    /**\n     * @param ctx       the application context\n     * @param utterance the utterance\n     * @param language  the detected language one of {@link TranslationLanguageBing}\n     * @param sl        the {@link SupportedLanguage}\n     * @return the prepared text ready for the command\n     */\n    public static String resolveBody(@NonNull final Context ctx, @NonNull String utterance,\n                                     @NonNull final String language, @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolveBody: \" + utterance);\n        }\n\n        final SaiyResources sr = new SaiyResources(ctx, sl);\n        final String in_to = sr.getString(ai.saiy.android.R.string.in_to);\n        final String into = sr.getString(ai.saiy.android.R.string.into);\n        final String to = sr.getString(ai.saiy.android.R.string.to);\n\n        if (translate_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        sr.reset();\n\n        utterance = utterance.replaceFirst(translate_, \"\");\n\n        if (utterance.endsWith(language)) {\n\n            if (utterance.endsWith(in_to + \" \" + language)) {\n                utterance = utterance.replace(in_to + \" \" + language, \"\").trim();\n            } else if (utterance.endsWith(into + \" \" + language)) {\n                utterance = utterance.replace(into + \" \" + language, \"\").trim();\n            } else if (utterance.endsWith(to + \" \" + language)) {\n                utterance = utterance.replace(to + \" \" + language, \"\").trim();\n            } else {\n                utterance = utterance.replaceFirst(\"(?s)(.*)\" + language, \"$1\" + \"\");\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"resolveBody: returning: \" + utterance);\n            }\n\n            return utterance;\n\n        } else {\n\n            final String[] strips = utterance.split(language);\n\n            if (strips.length > 1 && UtilsString.notNaked(strips[1])) {\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"resolveBody: returning: \" + strips[1].trim());\n                }\n\n                return strips[1].trim();\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/TranslationProvider.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 19/04/2016.\n */\npublic class TranslationProvider {\n\n    public static final int TRANSLATION_PROVIDER_UNKNOWN = 0;\n    public static final int TRANSLATION_PROVIDER_BING = 1;\n    public static final int TRANSLATION_PROVIDER_GOOGLE = 2;\n\n    private static final int COMMAND_TRANSLATE_VERBOSE_LIMIT = 3;\n\n    /**\n     * Check to see if the completed translation command needs verbose explanation.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     */\n    public static boolean shouldAction(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        if (SPH.getTranslateCommandVerbose(ctx) <= COMMAND_TRANSLATE_VERBOSE_LIMIT) {\n            SPH.incrementTranslateCommandVerbose(ctx);\n\n            final Bundle bundle = new Bundle();\n            bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n            bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_ONLY);\n            bundle.putInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_IGNORE);\n            bundle.putString(LocalRequest.EXTRA_UTTERANCE, PersonalityResponse.getClipboardSpell(ctx, sl));\n            bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(ctx).toString());\n            new LocalRequest(ctx, bundle).execute();\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/bing/BingCredentials.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.bing;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to hold OAuth credentials for Bing\n * <p/>\n * Created by benrandall76@gmail.com on 18/04/2016.\n */\npublic class BingCredentials {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BingCredentials.class.getSimpleName();\n\n    private static long then;\n\n    @SerializedName(\"access_token\")\n    private final String refreshToken;\n\n    @SerializedName(\"expires_in\")\n    private final long expires;\n\n    public long getExpires() {\n        return expires;\n    }\n\n    /**\n     * Constructor\n     *\n     * @param refreshToken the token\n     * @param expires      the token expiry time\n     */\n    public BingCredentials(@NonNull final String refreshToken, final long expires) {\n        this.refreshToken = refreshToken;\n        this.expires = expires;\n    }\n\n    public String getRefreshToken() {\n        return refreshToken;\n    }\n\n    /**\n     * Check to see if the Bing OAuth token is valid\n     *\n     * @param ctx the application context\n     * @return true if the token is valid. False otherwise\n     */\n    public static boolean isTokenValid(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isTokenValid\");\n        }\n\n        if (UtilsString.notNaked(SPH.getBingToken(ctx)) && System.currentTimeMillis() < SPH.getBingTokenExpiryTime(ctx)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isTokenValid: valid\");\n            }\n            return true;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isTokenValid: invalid\");\n            }\n            return false;\n        }\n    }\n\n    /**\n     * Check to see if the Bing OAuth token is valid and asynchronously validate if required\n     *\n     * @param ctx the application context\n     */\n    public static void refreshTokenIfRequired(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"refreshTokenIfRequired\");\n        }\n\n        if (SPH.getDefaultTranslationProvider(ctx) == TranslationProvider.TRANSLATION_PROVIDER_BING) {\n            if (!isTokenValid(ctx)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"refreshTokenIfRequired invalid. Requesting async\");\n                }\n\n                if (System.currentTimeMillis() > (then + 5000)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"refreshTokenIfRequired invalid. Requesting async: delay ok\");\n                    }\n                    then = System.currentTimeMillis();\n                    new BingOAuth().execute(ctx, false);\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"refreshTokenIfRequired invalid. Requesting async: delay too short\");\n                    }\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"refreshTokenIfRequired: not using Bing\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/bing/BingOAuth.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.bing;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.StringRequest;\nimport com.android.volley.toolbox.Volley;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 18/04/2016.\n */\npublic class BingOAuth {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BingOAuth.class.getSimpleName();\n\n    private static final String ENCODING = \"UTF-8\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n    private static final String BING_OAUTH_URL = \"https://api.cognitive.microsoft.com/sts/v1.0/issueToken\";\n\n    private static final String OCP_SUBSCRIPTION_KEY_HEADER = \"Ocp-Apim-Subscription-Key\";\n\n    private static final String PARAM_CONTENT_TYPE = \"Content-Type\";\n    private static final String PARAM_ACCEPT_CHARSET = \"Accept-Charset\";\n    private static final long THREAD_TIMEOUT = 7L;\n\n    /**\n     * Get the Bing OAuth refresh token and store in {@link BingCredentials}\n     *\n     * @param ctx         the application context\n     * @param synchronous whether or not this request should execute synchronously\n     * @return true if the process succeeded. False otherwise\n     */\n    public boolean execute(@NonNull final Context ctx, final boolean synchronous) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n\n        final long then = System.nanoTime();\n\n        final String encodedSubscriptionKey;\n\n        try {\n            encodedSubscriptionKey = URLEncoder.encode(MicrosoftConfiguration.MS_TRANSLATE_SUBSCRIPTION_KEY, ENCODING);\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"execute: UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n\n            return false;\n        }\n\n        final RequestQueue queue = Volley.newRequestQueue(ctx);\n        queue.start();\n\n        Response.Listener<String> listener;\n        RequestFuture<String> future = null;\n\n        if (synchronous) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"synchronous: true\");\n            }\n\n            future = RequestFuture.newFuture();\n            listener = future;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"synchronous: false\");\n            }\n\n            listener = new Response.Listener<String>() {\n                @Override\n                public void onResponse(final String response) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onResponse: \" + response);\n                    }\n\n                    queue.stop();\n\n                    final BingCredentials credentials = new BingCredentials(response, 600);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onResponse: subscription_key: \" + credentials.getRefreshToken());\n                        MyLog.i(CLS_NAME, \"onResponse: expires_in: \" + credentials.getExpires());\n                    }\n\n                    SPH.setBingToken(ctx, credentials.getRefreshToken());\n                    SPH.setBingTokenExpiryTime(ctx, (System.currentTimeMillis() + (credentials.getExpires() * 1000)));\n                }\n            };\n        }\n\n        final StringRequest request = new StringRequest(Request.Method.POST, BING_OAUTH_URL, listener,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            BingOAuth.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                })\n\n        {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(PARAM_CONTENT_TYPE, HEADER_CONTENT_TYPE);\n                params.put(PARAM_ACCEPT_CHARSET, ENCODING);\n                params.put(OCP_SUBSCRIPTION_KEY_HEADER, encodedSubscriptionKey);\n                return params;\n            }\n        };\n\n        request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(request);\n\n        String jsonResponse;\n\n        if (synchronous) {\n            try {\n                jsonResponse = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n\n                final BingCredentials credentials = new BingCredentials(jsonResponse, 600);\n\n                SPH.setBingToken(ctx, credentials.getRefreshToken());\n                SPH.setBingTokenExpiryTime(ctx, (System.currentTimeMillis() + (credentials.getExpires() * 1000)));\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onResponse: subscription_key: \" + credentials.getRefreshToken());\n                    MyLog.i(CLS_NAME, \"onResponse: expires_in: \" + credentials.getExpires());\n                    MyLog.getElapsed(CLS_NAME, then);\n                }\n\n                return true;\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            } catch (final InterruptedException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: InterruptedException\");\n                    e.printStackTrace();\n                }\n            } catch (ExecutionException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: ExecutionException\");\n                    e.printStackTrace();\n                }\n            } catch (TimeoutException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: TimeoutException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: Exception\");\n                    e.printStackTrace();\n                }\n            } finally {\n                queue.stop();\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(BingOAuth.class.getSimpleName(), then);\n        }\n\n        return false;\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/bing/BingTranslate.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.bing;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.clipboard.ClipboardHelper;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.translate.Translate;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.processing.EntangledPair;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.processing.Position;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\nimport static ai.saiy.android.command.translate.CommandTranslate.CLIPBOARD_DELAY;\n\n/**\n * Helper Class to resolve elements of a translation request using Bing Translator\n * <p>\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class BingTranslate {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BingTranslate.class.getSimpleName();\n\n    private static final String B_START = \".*\\\\b\";\n    private static final String B_END = \"\\\\b.*\";\n\n    private static final int MAX_TRANSLATE_LENGTH = 2000;\n\n    private final Context mContext;\n    private final SupportedLanguage sl;\n    private final CommandRequest cr;\n    private final Outcome outcome;\n\n    private static Pattern pGERMAN;\n    private static Pattern pFRENCH;\n    private static Pattern pITALIAN;\n    private static Pattern pPOLISH;\n    private static Pattern pSPANISH;\n    private static Pattern pROMANIAN;\n    private static Pattern pENGLISH;\n    private static Pattern pARABIC;\n    private static Pattern pBULGARIAN;\n    private static Pattern pCATALAN;\n    private static Pattern pCHINESE_S;\n    private static Pattern pCHINESE_T;\n    private static Pattern pCHINESE;\n    private static Pattern pDANISH;\n    private static Pattern pDUTCH;\n    private static Pattern pESTONIAN;\n    private static Pattern pFINNISH;\n    private static Pattern pGREEK;\n    private static Pattern pHEBREW;\n    private static Pattern pHINDI;\n    private static Pattern pHUNGARIAN;\n    private static Pattern pINDONESIAN;\n    private static Pattern pJAPANESE;\n    private static Pattern pKOREAN;\n    private static Pattern pLATVIAN;\n    private static Pattern pLITHUANIAN;\n    private static Pattern pNORWEGIAN;\n    private static Pattern pPORTUGUESE;\n    private static Pattern pPORTUGUESE_BRAZILIAN;\n    private static Pattern pBRAZILIAN;\n    private static Pattern pRUSSIAN;\n    private static Pattern pSWEDISH;\n    private static Pattern pTHAI;\n    private static Pattern pTURKISH;\n    private static Pattern pUKRAINIAN;\n    private static Pattern pVIETNAMESE;\n    private static Pattern pMALTESE;\n    private static Pattern pPERSIAN;\n    private static Pattern pSLOVAK;\n    private static Pattern pSLOVENIAN;\n    private static Pattern pWELSH;\n    private static Pattern pCROATIAN;\n    private static Pattern pCZECH;\n    private static Pattern pCZECHOSLOVAKIAN;\n    private static Pattern pSWAHILI;\n    private static Pattern pKLINGON;\n    private static Pattern pMALAY;\n    private static Pattern pMALAYSIAN;\n    private static Pattern pSERBIAN;\n    private static Pattern pURDU;\n    private static Pattern pMANDARIN;\n    private static Pattern pCANTONESE;\n    private static Pattern pSERBIAN_CYRILLIC;\n    private static Pattern pSERBIAN_LATIN;\n    private static Pattern pHAITIAN_CREOLE;\n    private static Pattern pHAITIAN;\n    private static Pattern pKISWAHILI;\n    private static Pattern pSLOVAKIAN;\n    private static Pattern pBOSNIAN;\n    private static Pattern pTranslate;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param cr       the {@link CommandRequest}\n     */\n    public BingTranslate(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                         @NonNull final CommandRequest cr) {\n        this.mContext = mContext;\n        this.sl = sl;\n        this.cr = cr;\n\n        outcome = new Outcome();\n        outcome.setTTSLocale(cr.getTTSLocale(mContext));\n    }\n\n\n    /**\n     * Resolve the translation request and return the {@link Outcome}\n     *\n     * @return the created {@link Outcome}\n     */\n    public Outcome getResponse() {\n\n        final CommandTranslateValues ctv = (CommandTranslateValues) cr.getVariableData();\n        final TranslationLanguageBing language = resolveLanguage(ctv.getLanguage());\n\n        if (language != TranslationLanguageBing.AUTO_DETECT) {\n\n            String translationRequest = ctv.getText();\n\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"language: \" + language.name());\n                MyLog.d(CLS_NAME, \"request: \" + translationRequest);\n            }\n\n            if (ClipboardHelper.isClipboard(mContext, translationRequest)) {\n\n                try {\n                    Thread.sleep(CLIPBOARD_DELAY);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                final Pair<Boolean, String> clipboardPair = ClipboardHelper.getClipboardContentPair(mContext, sl);\n\n                if (clipboardPair.first) {\n                    translationRequest = clipboardPair.second;\n                } else {\n                    outcome.setUtterance(clipboardPair.second);\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return outcome;\n                }\n            }\n\n            if (!tooLong(translationRequest)) {\n\n                final Pair<Boolean, String> translationResult = execute(mContext, language, translationRequest);\n\n                if (translationResult.first) {\n                    outcome.setUtterance(translationResult.second);\n                    outcome.setOutcome(Outcome.SUCCESS);\n\n                    final EntangledPair entangledPair = new EntangledPair(Position.TOAST_LONG, CC.COMMAND_TRANSLATE);\n                    entangledPair.setToastContent(translationResult.second);\n                    outcome.setEntangledPair(entangledPair);\n\n                    final Qubit qubit = new Qubit();\n                    qubit.setTranslatedText(translationResult.second);\n                    outcome.setQubit(qubit);\n                    outcome.setTTSLocale(language.getLocale());\n                } else {\n                    outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate,\n                            PersonalityHelper.getUserNameOrNot(mContext)));\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return outcome;\n                }\n\n            } else {\n                outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate_length,\n                        PersonalityHelper.getUserNameOrNot(mContext)));\n                outcome.setOutcome(Outcome.FAILURE);\n                return outcome;\n            }\n        } else {\n            outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate_unsupported,\n                    PersonalityHelper.getUserNameOrNot(mContext)));\n            outcome.setOutcome(Outcome.FAILURE);\n            return outcome;\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Execute the translation request\n     *\n     * @param ctx               the application context\n     * @param language          the {@link TranslationLanguageBing}\n     * @param translationString the content to translate\n     * @return the translation result {@link Pair} with the first parameter denoting success\n     */\n    public static Pair<Boolean, String> execute(@NonNull final Context ctx,\n                                                @NonNull final TranslationLanguageBing language,\n                                                @NonNull final String translationString) {\n\n        if (BingCredentials.isTokenValid(ctx)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"execute: token valid\");\n            }\n            return new BingTranslateAPI().execute(ctx, SPH.getBingToken(ctx), translationString,\n                    TranslationLanguageBing.AUTO_DETECT, language);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"execute: token invalid\");\n            }\n            if (new BingOAuth().execute(ctx, true)) {\n                return new BingTranslateAPI().execute(ctx, SPH.getBingToken(ctx), translationString,\n                        TranslationLanguageBing.AUTO_DETECT, language);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"execute: refreshing token failed\");\n                }\n                return new Pair<>(false, null);\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n\n        pGERMAN = Pattern.compile(B_START + sr.getString(R.string.german) + B_END);\n        pFRENCH = Pattern.compile(B_START + sr.getString(R.string.french) + B_END);\n        pITALIAN = Pattern.compile(B_START + sr.getString(R.string.italian) + B_END);\n        pPOLISH = Pattern.compile(B_START + sr.getString(R.string.polish) + B_END);\n        pSPANISH = Pattern.compile(B_START + sr.getString(R.string.spanish) + B_END);\n        pROMANIAN = Pattern.compile(B_START + sr.getString(R.string.romanian) + B_END);\n        pENGLISH = Pattern.compile(B_START + sr.getString(R.string.english) + B_END);\n        pARABIC = Pattern.compile(B_START + sr.getString(R.string.arabic) + B_END);\n        pBULGARIAN = Pattern.compile(B_START + sr.getString(R.string.bulgarian) + B_END);\n        pCATALAN = Pattern.compile(B_START + sr.getString(R.string.catalan) + B_END);\n        pCHINESE_S = Pattern.compile(B_START + sr.getString(R.string.chinese_simplified) + B_END);\n        pCHINESE_T = Pattern.compile(B_START + sr.getString(R.string.chinese_traditional) + B_END);\n        pCHINESE = Pattern.compile(B_START + sr.getString(R.string.chinese) + B_END);\n        pDANISH = Pattern.compile(B_START + sr.getString(R.string.danish) + B_END);\n        pDUTCH = Pattern.compile(B_START + sr.getString(R.string.dutch) + B_END);\n        pESTONIAN = Pattern.compile(B_START + sr.getString(R.string.estonian) + B_END);\n        pFINNISH = Pattern.compile(B_START + sr.getString(R.string.finnish) + B_END);\n        pGREEK = Pattern.compile(B_START + sr.getString(R.string.greek) + B_END);\n        pHEBREW = Pattern.compile(B_START + sr.getString(R.string.hebrew) + B_END);\n        pHINDI = Pattern.compile(B_START + sr.getString(R.string.hindi) + B_END);\n        pHUNGARIAN = Pattern.compile(B_START + sr.getString(R.string.hungarian) + B_END);\n        pINDONESIAN = Pattern.compile(B_START + sr.getString(R.string.indonesian) + B_END);\n        pJAPANESE = Pattern.compile(B_START + sr.getString(R.string.japanese) + B_END);\n        pKOREAN = Pattern.compile(B_START + sr.getString(R.string.korean) + B_END);\n        pLATVIAN = Pattern.compile(B_START + sr.getString(R.string.latvian) + B_END);\n        pLITHUANIAN = Pattern.compile(B_START + sr.getString(R.string.lithuanian) + B_END);\n        pNORWEGIAN = Pattern.compile(B_START + sr.getString(R.string.norwegian) + B_END);\n        pPORTUGUESE = Pattern.compile(B_START + sr.getString(R.string.portuguese) + B_END);\n        pPORTUGUESE_BRAZILIAN = Pattern.compile(B_START + sr.getString(R.string.portuguese_brazilian) + B_END);\n        pBRAZILIAN = Pattern.compile(B_START + sr.getString(R.string.brazilian) + B_END);\n        pRUSSIAN = Pattern.compile(B_START + sr.getString(R.string.russian) + B_END);\n        pSWEDISH = Pattern.compile(B_START + sr.getString(R.string.swedish) + B_END);\n        pTHAI = Pattern.compile(B_START + sr.getString(R.string.thai) + B_END);\n        pTURKISH = Pattern.compile(B_START + sr.getString(R.string.turkish) + B_END);\n        pUKRAINIAN = Pattern.compile(B_START + sr.getString(R.string.ukrainian) + B_END);\n        pVIETNAMESE = Pattern.compile(B_START + sr.getString(R.string.vietnamese) + B_END);\n        pMALTESE = Pattern.compile(B_START + sr.getString(R.string.maltese) + B_END);\n        pPERSIAN = Pattern.compile(B_START + sr.getString(R.string.persian) + B_END);\n        pSLOVAK = Pattern.compile(B_START + sr.getString(R.string.slovak) + B_END);\n        pSLOVENIAN = Pattern.compile(B_START + sr.getString(R.string.slovenian) + B_END);\n        pWELSH = Pattern.compile(B_START + sr.getString(R.string.welsh) + B_END);\n        pCROATIAN = Pattern.compile(B_START + sr.getString(R.string.croatian) + B_END);\n        pCZECH = Pattern.compile(B_START + sr.getString(R.string.czech) + B_END);\n        pCZECHOSLOVAKIAN = Pattern.compile(B_START + sr.getString(R.string.czechoslovakian) + B_END);\n        pSWAHILI = Pattern.compile(B_START + sr.getString(R.string.swahili) + B_END);\n        pKLINGON = Pattern.compile(B_START + sr.getString(R.string.klingon) + B_END);\n        pMALAY = Pattern.compile(B_START + sr.getString(R.string.malay) + B_END);\n        pMALAYSIAN = Pattern.compile(B_START + sr.getString(R.string.malaysian) + B_END);\n        pSERBIAN = Pattern.compile(B_START + sr.getString(R.string.serbian) + B_END);\n        pURDU = Pattern.compile(B_START + sr.getString(R.string.urdu) + B_END);\n        pMANDARIN = Pattern.compile(B_START + sr.getString(R.string.mandarin) + B_END);\n        pCANTONESE = Pattern.compile(B_START + sr.getString(R.string.cantonese) + B_END);\n        pTranslate = Pattern.compile(B_START + sr.getString(R.string.translate_) + B_END);\n        pSERBIAN_CYRILLIC = Pattern.compile(B_START + sr.getString(R.string.serbian_cyrillic) + B_END);\n        pSERBIAN_LATIN = Pattern.compile(B_START + sr.getString(R.string.serbian_latin) + B_END);\n        pHAITIAN_CREOLE = Pattern.compile(B_START + sr.getString(R.string.haitian_creole) + B_END);\n        pHAITIAN = Pattern.compile(B_START + sr.getString(R.string.haitian) + B_END);\n        pKISWAHILI = Pattern.compile(B_START + sr.getString(R.string.kiswahili) + B_END);\n        pBOSNIAN = Pattern.compile(B_START + sr.getString(R.string.bosnian) + B_END);\n        pSLOVAKIAN = Pattern.compile(B_START + sr.getString(R.string.slovakian) + B_END);\n    }\n\n\n    /**\n     * Extract the required translation {@link TranslationLanguageBing} and translation string\n     *\n     * @param ctx       the application context\n     * @param voiceData the utterance to which the extraction will take place\n     * @param sl        the {@link SupportedLanguage}\n     * @return a {@link Pair} with the first parameter denoting success\n     */\n    public static Pair<TranslationLanguageBing, String> extract(@NonNull final Context ctx,\n                                                                @NonNull final ArrayList<String> voiceData,\n                                                                @NonNull final SupportedLanguage sl) {\n        final long then = System.nanoTime();\n\n        TranslationLanguageBing language = null;\n        String toTranslate = null;\n\n        if (pTranslate == null) {\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        }\n\n        final Locale loc = sl.getLocale();\n        Pair<TranslationLanguageBing, String> translationPair;\n\n        int size = voiceData.size();\n\n        String vd;\n        for (int i = 0; i < size; i++) {\n            vd = voiceData.get(i).toLowerCase(loc).trim();\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"vd: \" + vd);\n            }\n\n            if (pTranslate.matcher(vd).matches()) {\n\n                translationPair = getTranslationPair(ctx, vd, sl);\n                language = translationPair.first;\n                toTranslate = translationPair.second;\n\n                if (language != null && UtilsString.notNaked(toTranslate)) {\n                    break;\n                }\n\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(BingTranslate.class.getSimpleName(), then);\n        }\n\n        return new Pair<>(language, toTranslate);\n    }\n\n    private static Pair<TranslationLanguageBing, String> getTranslationPair(@NonNull final Context ctx,\n                                                                            @NonNull final String vd,\n                                                                            @NonNull final SupportedLanguage sl) {\n\n        TranslationLanguageBing language = null;\n        String toTranslate = null;\n\n        if (pGERMAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.GERMAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.german));\n        } else if (pMANDARIN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.MANDARIN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.mandarin));\n        } else if (pCANTONESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CANTONESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.cantonese));\n        } else if (pURDU.matcher(vd).matches()) {\n            language = TranslationLanguageBing.URDU;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.urdu));\n        } else if (pSERBIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SERBIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.serbian));\n        } else if (pSERBIAN_LATIN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SERBIAN_LATIN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.serbian_latin));\n        } else if (pSERBIAN_CYRILLIC.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SERBIAN_CYRILLIC;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.serbian_cyrillic));\n        } else if (pMALAYSIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.MALAYSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.malaysian));\n        } else if (pMALAY.matcher(vd).matches()) {\n            language = TranslationLanguageBing.MALAY;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.malay));\n        } else if (pKLINGON.matcher(vd).matches()) {\n            language = TranslationLanguageBing.KLINGON;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.klingon));\n        } else if (pSWAHILI.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SWAHILI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.swahili));\n        } else if (pKISWAHILI.matcher(vd).matches()) {\n            language = TranslationLanguageBing.KISWAHILI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.kiswahili));\n        } else if (pHAITIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.HAITIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.haitian));\n        } else if (pHAITIAN_CREOLE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.HAITIAN_CREOLE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.haitian_creole));\n        } else if (pCZECH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CZECH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.czech));\n        } else if (pCZECHOSLOVAKIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CZECHOSLOVAKIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.czechoslovakian));\n        } else if (pCROATIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CROATIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.croatian));\n        } else if (pBOSNIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.BOSNIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.bosnian));\n        } else if (pWELSH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.WELSH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.welsh));\n        } else if (pSLOVENIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SLOVENIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.slovenian));\n        } else if (pSLOVAKIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SLOVAKIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.slovakian));\n        } else if (pSLOVAK.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SLOVAK;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.slovak));\n        } else if (pPERSIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.PERSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.persian));\n        } else if (pMALTESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.MALTESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.maltese));\n        } else if (pFRENCH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.FRENCH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.french));\n        } else if (pITALIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.ITALIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.italian));\n        } else if (pSPANISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SPANISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.spanish));\n        } else if (pPOLISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.POLISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.polish));\n        } else if (pROMANIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.ROMANIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.romanian));\n        } else if (pENGLISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.ENGLISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.english));\n        } else if (pARABIC.matcher(vd).matches()) {\n            language = TranslationLanguageBing.ARABIC;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.arabic));\n        } else if (pBULGARIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.BULGARIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.bulgarian));\n        } else if (pCATALAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CATALAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.catalan));\n        } else if (pCHINESE_S.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CHINESE_SIMPLIFIED;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.chinese_simplified));\n        } else if (pCHINESE_T.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CHINESE_TRADITIONAL;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.chinese_traditional));\n        } else if (pCHINESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.CHINESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.chinese));\n        } else if (pDANISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.DANISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.danish));\n        } else if (pDUTCH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.DUTCH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.dutch));\n        } else if (pESTONIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.ESTONIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.estonian));\n        } else if (pFINNISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.FINNISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.finnish));\n        } else if (pGREEK.matcher(vd).matches()) {\n            language = TranslationLanguageBing.GREEK;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.greek));\n        } else if (pHEBREW.matcher(vd).matches()) {\n            language = TranslationLanguageBing.HEBREW;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.hebrew));\n        } else if (pHINDI.matcher(vd).matches()) {\n            language = TranslationLanguageBing.HINDI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.hindi));\n        } else if (pHUNGARIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.HUNGARIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.hungarian));\n        } else if (pINDONESIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.INDONESIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.indonesian));\n        } else if (pJAPANESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.JAPANESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.japanese));\n        } else if (pKOREAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.KOREAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.korean));\n        } else if (pLATVIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.LATVIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.latvian));\n        } else if (pLITHUANIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.LITHUANIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.lithuanian));\n        } else if (pNORWEGIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.NORWEGIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.norwegian));\n        } else if (pPORTUGUESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.PORTUGUESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.portuguese));\n        } else if (pPORTUGUESE_BRAZILIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.PORTUGUESE_BRAZILIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.portuguese_brazilian));\n        } else if (pBRAZILIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.BRAZILIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.brazilian));\n        } else if (pRUSSIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.RUSSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.russian));\n        } else if (pSWEDISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.SWEDISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.swedish));\n        } else if (pTHAI.matcher(vd).matches()) {\n            language = TranslationLanguageBing.THAI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.thai));\n        } else if (pTURKISH.matcher(vd).matches()) {\n            language = TranslationLanguageBing.TURKISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.turkish));\n        } else if (pUKRAINIAN.matcher(vd).matches()) {\n            language = TranslationLanguageBing.UKRAINIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.ukrainian));\n        } else if (pVIETNAMESE.matcher(vd).matches()) {\n            language = TranslationLanguageBing.VIETNAMESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vd, ctx.getString(ai.saiy.android.R.string.vietnamese));\n        }\n\n        return new Pair<>(language, toTranslate);\n    }\n\n    /**\n     * Resolve the required translation language from the voice data\n     *\n     * @param vd the voice data string\n     * @return the matching {@link TranslationLanguageBing} or {@link TranslationLanguageBing#AUTO_DETECT}\n     */\n    private TranslationLanguageBing resolveLanguage(@NonNull final String vd) {\n\n        if (vd.contains(mContext.getString(ai.saiy.android.R.string.german))) {\n            return TranslationLanguageBing.GERMAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.french))) {\n            return TranslationLanguageBing.FRENCH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.italian))) {\n            return TranslationLanguageBing.ITALIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.spanish))) {\n            return TranslationLanguageBing.SPANISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.polish))) {\n            return TranslationLanguageBing.POLISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.romanian))) {\n            return TranslationLanguageBing.ROMANIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.english))) {\n            return TranslationLanguageBing.ENGLISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.arabic))) {\n            return TranslationLanguageBing.ARABIC;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.bulgarian))) {\n            return TranslationLanguageBing.BULGARIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.catalan))) {\n            return TranslationLanguageBing.CATALAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.chinese_simplified))) {\n            return TranslationLanguageBing.CHINESE_SIMPLIFIED;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.chinese_traditional))) {\n            return TranslationLanguageBing.CHINESE_TRADITIONAL;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.chinese))) {\n            return TranslationLanguageBing.CHINESE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.mandarin))) {\n            return TranslationLanguageBing.MANDARIN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.cantonese))) {\n            return TranslationLanguageBing.CANTONESE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.danish))) {\n            return TranslationLanguageBing.DANISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.dutch))) {\n            return TranslationLanguageBing.DUTCH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.estonian))) {\n            return TranslationLanguageBing.ESTONIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.finnish))) {\n            return TranslationLanguageBing.FINNISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.greek))) {\n            return TranslationLanguageBing.GREEK;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.hebrew))) {\n            return TranslationLanguageBing.HEBREW;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.hindi))) {\n            return TranslationLanguageBing.HINDI;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.hungarian))) {\n            return TranslationLanguageBing.HUNGARIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.indonesian))) {\n            return TranslationLanguageBing.INDONESIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.japanese))) {\n            return TranslationLanguageBing.JAPANESE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.korean))) {\n            return TranslationLanguageBing.KOREAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.latvian))) {\n            return TranslationLanguageBing.LATVIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.lithuanian))) {\n            return TranslationLanguageBing.LITHUANIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.norwegian))) {\n            return TranslationLanguageBing.NORWEGIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.portuguese))) {\n            return TranslationLanguageBing.PORTUGUESE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.portuguese_brazilian))) {\n            return TranslationLanguageBing.PORTUGUESE_BRAZILIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.brazilian))) {\n            return TranslationLanguageBing.BRAZILIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.russian))) {\n            return TranslationLanguageBing.RUSSIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.slovenian))) {\n            return TranslationLanguageBing.SLOVENIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.swedish))) {\n            return TranslationLanguageBing.SWEDISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.thai))) {\n            return TranslationLanguageBing.THAI;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.turkish))) {\n            return TranslationLanguageBing.TURKISH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.ukrainian))) {\n            return TranslationLanguageBing.UKRAINIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.vietnamese))) {\n            return TranslationLanguageBing.VIETNAMESE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.urdu))) {\n            return TranslationLanguageBing.URDU;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.serbian))) {\n            return TranslationLanguageBing.SERBIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.serbian_latin))) {\n            return TranslationLanguageBing.SERBIAN_LATIN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.serbian_cyrillic))) {\n            return TranslationLanguageBing.SERBIAN_CYRILLIC;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.malaysian))) {\n            return TranslationLanguageBing.MALAYSIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.malay))) {\n            return TranslationLanguageBing.MALAY;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.klingon))) {\n            return TranslationLanguageBing.KLINGON;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.swahili))) {\n            return TranslationLanguageBing.SWAHILI;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.kiswahili))) {\n            return TranslationLanguageBing.KISWAHILI;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.haitian))) {\n            return TranslationLanguageBing.HAITIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.haitian_creole))) {\n            return TranslationLanguageBing.HAITIAN_CREOLE;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.czech))) {\n            return TranslationLanguageBing.CZECH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.czechoslovakian))) {\n            return TranslationLanguageBing.CZECHOSLOVAKIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.croatian))) {\n            return TranslationLanguageBing.CROATIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.bosnian))) {\n            return TranslationLanguageBing.BOSNIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.welsh))) {\n            return TranslationLanguageBing.WELSH;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.slovakian))) {\n            return TranslationLanguageBing.SLOVAKIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.slovak))) {\n            return TranslationLanguageBing.SLOVAK;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.persian))) {\n            return TranslationLanguageBing.PERSIAN;\n        } else if (vd.contains(mContext.getString(ai.saiy.android.R.string.maltese))) {\n            return TranslationLanguageBing.MALTESE;\n        } else {\n            return TranslationLanguageBing.AUTO_DETECT;\n        }\n    }\n\n\n    /**\n     * Check if the translation request exceeds the {@link #MAX_TRANSLATE_LENGTH}\n     *\n     * @param toTranslate the string to translate\n     * @return true if the string is too long. False otherwise\n     */\n    public static boolean tooLong(@NonNull final String toTranslate) {\n        return toTranslate.length() > MAX_TRANSLATE_LENGTH;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/bing/BingTranslateAPI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.bing;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.android.volley.AuthFailureError;\nimport com.android.volley.Cache;\nimport com.android.volley.DefaultRetryPolicy;\nimport com.android.volley.Network;\nimport com.android.volley.NetworkResponse;\nimport com.android.volley.Request;\nimport com.android.volley.RequestQueue;\nimport com.android.volley.Response;\nimport com.android.volley.ServerError;\nimport com.android.volley.VolleyError;\nimport com.android.volley.toolbox.BasicNetwork;\nimport com.android.volley.toolbox.HttpHeaderParser;\nimport com.android.volley.toolbox.HurlStack;\nimport com.android.volley.toolbox.RequestFuture;\nimport com.android.volley.toolbox.StringRequest;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\nimport ai.saiy.android.utils.UtilsVolley;\n\n/**\n * Created by benrandall76@gmail.com on 18/04/2016.\n */\npublic class BingTranslateAPI {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = BingTranslateAPI.class.getSimpleName();\n\n    private static final String SERVICE_URL = \"http://api.microsofttranslator.com/V2/Ajax.svc/Translate?\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n    private static final String ENCODING = \"UTF-8\";\n    private static final String PARAM_TO_LANGUAGE = \"&to=\";\n    private static final String PARAM_FROM_LANGUAGE = \"&from=\";\n    private static final String PARAM_TEXT = \"&text=\";\n\n    private static final long THREAD_TIMEOUT = 7L;\n    private static final String ARGUMENT_EXCEPTION = \"Argument\";\n\n    /**\n     * Perform a synchronous translation request\n     *\n     * @param ctx   the application context\n     * @param token the Bing OAuth refresh token\n     * @param text  the text to be translated\n     * @param from  the {@link TranslationLanguageBing} to translate from\n     * @param to    the {@link TranslationLanguageBing} to translate to\n     * @return a {@link Pair} with the first parameter donating success and the second the result\n     */\n    public Pair<Boolean, String> execute(@NonNull final Context ctx, final String token, final String text,\n                                         final TranslationLanguageBing from, final TranslationLanguageBing to) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n\n        final long then = System.nanoTime();\n        final String params;\n\n        try {\n\n            params = PARAM_FROM_LANGUAGE + URLEncoder.encode(from.getLanguage(), ENCODING)\n                    + PARAM_TO_LANGUAGE + URLEncoder.encode(to.getLanguage(), ENCODING)\n                    + PARAM_TEXT + URLEncoder.encode(text, ENCODING);\n\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n            return new Pair<>(false, null);\n        }\n\n        final RequestFuture<String> future = RequestFuture.newFuture();\n        final Cache cache = UtilsVolley.getCache(ctx);\n        final Network network = new BasicNetwork(new HurlStack());\n        final RequestQueue queue = new RequestQueue(cache, network);\n        queue.start();\n\n        final String url = SERVICE_URL + params;\n\n        final StringRequest request = new StringRequest(Request.Method.GET, url, future,\n                new Response.ErrorListener() {\n                    @Override\n                    public void onErrorResponse(final VolleyError error) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onErrorResponse: \" + error.toString());\n                            BingTranslateAPI.this.verboseError(error);\n                        }\n                        queue.stop();\n                    }\n                }) {\n\n            @Override\n            public Map<String, String> getHeaders() throws AuthFailureError {\n                final Map<String, String> params = new HashMap<>();\n                params.put(\"Content-Type\", HEADER_CONTENT_TYPE);\n                params.put(\"Accept-Charset\", ENCODING);\n                params.put(\"Authorization\", \"Bearer \" + token);\n                return params;\n            }\n        };\n\n        request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,\n                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));\n        queue.add(request);\n\n        String translation = \"\";\n\n        try {\n            translation = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (TimeoutException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: TimeoutException\");\n                e.printStackTrace();\n            }\n        } finally {\n            queue.stop();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        if (UtilsString.notNaked(translation) && !translation.contains(ARGUMENT_EXCEPTION)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"translation: \" + translation);\n            }\n\n            try {\n                translation = new GsonBuilder().disableHtmlEscaping().create().fromJson(translation, String.class);\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"translation: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            }\n\n            return new Pair<>(true, translation);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"translation: failed\");\n            }\n            return new Pair<>(false, null);\n        }\n    }\n\n    /**\n     * Used for debugging only to view verbose error information\n     *\n     * @param error the {@link VolleyError}\n     */\n    private void verboseError(@NonNull final VolleyError error) {\n\n        final NetworkResponse response = error.networkResponse;\n\n        if (response != null && error instanceof ServerError) {\n\n            try {\n                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));\n                MyLog.i(CLS_NAME, \"result: \" + result);\n            } catch (final UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/bing/TranslationLanguageBing.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.bing;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.Locale;\n\nimport ai.saiy.android.utils.UtilsLocale;\n\n/**\n * Enum class that details all translation languages supported by Bing. As well as the language codes\n * that are required to send to Bing, the equivalent {@link Locale} is included, so that the Text to\n * Speech engine will be able to initialise any region specific engine.\n * <p>\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic enum TranslationLanguageBing {\n    AUTO_DETECT(\"\", \"\"),\n    ARABIC(\"ar\", \"ar\"),\n    BOSNIAN(\"bs-Latn\", \"bs_BA\"),\n    BULGARIAN(\"bg\", \"bg_BG\"),\n    CATALAN(\"ca\", \"ca_ES\"),\n    CHINESE(\"zh-CHS\", \"zh_CN\"),\n    MANDARIN(\"zh-CHS\", \"zh_CN\"),\n    CANTONESE(\"zh-CHT\", \"zh_TW\"),\n    CHINESE_SIMPLIFIED(\"zh-CHS\", \"zh_CN\"),\n    CHINESE_TRADITIONAL(\"zh-CHT\", \"zh_TW\"),\n    CROATIAN(\"hr\", \"hr_HR\"),\n    CZECH(\"cs\", \"cs_CZ\"),\n    CZECHOSLOVAKIAN(\"cs\", \"cs_CZ\"),\n    DANISH(\"da\", \"da_DK\"),\n    DUTCH(\"nl\", \"nl_NL\"),\n    ENGLISH(\"en\", \"en\"),\n    ESTONIAN(\"et\", \"et_EE\"),\n    FINNISH(\"fi\", \"fi_FI\"),\n    FRENCH(\"fr\", \"fr_FR\"),\n    GERMAN(\"de\", \"de_DE\"),\n    GREEK(\"el\", \"el_GR\"),\n    HAITIAN_CREOLE(\"ht\", \"ht_HT\"),\n    HAITIAN(\"ht\", \"ht_HT\"),\n    HEBREW(\"he\", \"iw_IL\"),\n    HINDI(\"hi\", \"hi_IN\"),\n    HUNGARIAN(\"hu\", \"hu_HU\"),\n    INDONESIAN(\"id\", \"in_ID\"),\n    ITALIAN(\"it\", \"it_IT\"),\n    JAPANESE(\"ja\", \"ja_JP\"),\n    KISWAHILI(\"sw\", \"sw\"),\n    SWAHILI(\"sw\", \"sw\"),\n    KLINGON(\"tlh\", \"tlh\"),\n    KOREAN(\"ko\", \"ko_KR\"),\n    LATVIAN(\"lv\", \"lv_LV\"),\n    LITHUANIAN(\"lt\", \"lt_LT\"),\n    MALAY(\"ms\", \"ms_MY\"),\n    MALAYSIAN(\"ms\", \"ms_MY\"),\n    MALTESE(\"mt\", \"mt_MT\"),\n    NORWEGIAN(\"no\", \"no_NO\"),\n    PERSIAN(\"fa\", \"fa_IR\"),\n    POLISH(\"pl\", \"pl_PL\"),\n    PORTUGUESE(\"pt\", \"pt_PT\"),\n    PORTUGUESE_BRAZILIAN(\"pt\", \"pt_BR\"),\n    BRAZILIAN(\"pt\", \"pt_BR\"),\n    ROMANIAN(\"ro\", \"ro_RO\"),\n    RUSSIAN(\"ru\", \"ru_RU\"),\n    SERBIAN(\"sr-Cyrl\", \"sr_RS\"),\n    SERBIAN_CYRILLIC(\"sr-Cyrl\", \"sr_RS\"),\n    SERBIAN_LATIN(\"sr-Latn\", \"sr_Latn_RS\"),\n    SLOVAK(\"sk\", \"sk_SK\"),\n    SLOVAKIAN(\"sk\", \"sk_SK\"),\n    SLOVENIAN(\"sl\", \"sl_SI\"),\n    SPANISH(\"es\", \"es_ES\"),\n    SWEDISH(\"sv\", \"sv_SE\"),\n    THAI(\"th\", \"th_TH\"),\n    TURKISH(\"tr\", \"tr_TR\"),\n    UKRAINIAN(\"uk\", \"uk_UA\"),\n    URDU(\"ur\", \"ur_PK\"),\n    VIETNAMESE(\"vi\", \"vi_VN\"),\n    WELSH(\"cy\", \"cy_GB\");\n\n    private final String language;\n    private final String locale;\n\n    /**\n     * Constructor.\n     *\n     * @param language the Bing language identifier.\n     * @param locale   the String locale\n     */\n    TranslationLanguageBing(@NonNull final String language, @NonNull final String locale) {\n        this.language = language;\n        this.locale = locale;\n    }\n\n    /**\n     * Get the {@link Locale} of the Bing language\n     *\n     * @return the {@link Locale}\n     */\n    public Locale getLocale() {\n        return UtilsLocale.stringToLocale(locale);\n    }\n\n    /**\n     * Get the language code used by Bing\n     *\n     * @return the language code\n     */\n    public String getLanguage() {\n        return language;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/google/GoogleTranslate.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.google;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.clipboard.ClipboardHelper;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.translate.Translate;\nimport ai.saiy.android.command.translate.provider.bing.TranslationLanguageBing;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.processing.EntangledPair;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.processing.Position;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\nimport static ai.saiy.android.command.translate.CommandTranslate.CLIPBOARD_DELAY;\n\n/**\n * Helper Class to resolve elements of a translation request using Google Translate\n * <p>\n * Created by benrandall76@gmail.com on 19/04/2016.\n */\npublic class GoogleTranslate {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = GoogleTranslate.class.getSimpleName();\n\n    private static final String B_START = \".*\\\\b\";\n    private static final String B_END = \"\\\\b.*\";\n\n    private static final int MAX_TRANSLATE_LENGTH = 2000;\n\n    private final Context mContext;\n    private final SupportedLanguage sl;\n    private final CommandRequest cr;\n    private final Outcome outcome;\n\n    private static Pattern pGERMAN;\n    private static Pattern pFRENCH;\n    private static Pattern pITALIAN;\n    private static Pattern pPOLISH;\n    private static Pattern pSPANISH;\n    private static Pattern pROMANIAN;\n    private static Pattern pENGLISH;\n    private static Pattern pARABIC;\n    private static Pattern pBULGARIAN;\n    private static Pattern pCATALAN;\n    private static Pattern pCHINESE_S;\n    private static Pattern pCHINESE_T;\n    private static Pattern pCHINESE;\n    private static Pattern pDANISH;\n    private static Pattern pDUTCH;\n    private static Pattern pESTONIAN;\n    private static Pattern pFINNISH;\n    private static Pattern pGREEK;\n    private static Pattern pHEBREW;\n    private static Pattern pHINDI;\n    private static Pattern pHUNGARIAN;\n    private static Pattern pINDONESIAN;\n    private static Pattern pJAPANESE;\n    private static Pattern pKOREAN;\n    private static Pattern pLATVIAN;\n    private static Pattern pLITHUANIAN;\n    private static Pattern pNORWEGIAN;\n    private static Pattern pPORTUGUESE;\n    private static Pattern pPORTUGUESE_BRAZILIAN;\n    private static Pattern pBRAZILIAN;\n    private static Pattern pRUSSIAN;\n    private static Pattern pSWEDISH;\n    private static Pattern pTHAI;\n    private static Pattern pTURKISH;\n    private static Pattern pUKRAINIAN;\n    private static Pattern pVIETNAMESE;\n    private static Pattern pMALTESE;\n    private static Pattern pPERSIAN;\n    private static Pattern pSLOVAK;\n    private static Pattern pSLOVENIAN;\n    private static Pattern pWELSH;\n    private static Pattern pCROATIAN;\n    private static Pattern pCZECH;\n    private static Pattern pCZECHOSLOVAKIAN;\n    private static Pattern pSWAHILI;\n    private static Pattern pKLINGON;\n    private static Pattern pMALAY;\n    private static Pattern pMALAYSIAN;\n    private static Pattern pSERBIAN;\n    private static Pattern pURDU;\n    private static Pattern pMANDARIN;\n    private static Pattern pCANTONESE;\n    private static Pattern pTranslate;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param cr       the {@link CommandRequest}\n     */\n    public GoogleTranslate(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                           @NonNull final CommandRequest cr) {\n        this.mContext = mContext;\n        this.sl = sl;\n        this.cr = cr;\n\n        outcome = new Outcome();\n        outcome.setTTSLocale(cr.getTTSLocale(mContext));\n\n    }\n\n    /**\n     * Resolve the translation request and return the {@link Outcome}\n     *\n     * @return the created {@link Outcome}\n     */\n    public Outcome getResponse() {\n\n        final CommandTranslateValues ctv = (CommandTranslateValues) cr.getVariableData();\n        final TranslationLanguageGoogle language = resolveLanguage(ctv.getLanguage());\n\n        if (language != TranslationLanguageGoogle.AUTO_DETECT) {\n\n            String translationRequest = ctv.getText();\n\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"language: \" + language.name());\n                MyLog.d(CLS_NAME, \"request: \" + translationRequest);\n            }\n\n            if (ClipboardHelper.isClipboard(mContext, translationRequest)) {\n\n                try {\n                    Thread.sleep(CLIPBOARD_DELAY);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                final Pair<Boolean, String> clipboardPair = ClipboardHelper.getClipboardContentPair(mContext, sl);\n\n                if (clipboardPair.first) {\n                    translationRequest = clipboardPair.second;\n                } else {\n                    outcome.setUtterance(clipboardPair.second);\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return outcome;\n                }\n            }\n\n            if (!tooLong(translationRequest)) {\n\n                final Pair<Boolean, String> translationResult = execute(mContext, language, translationRequest);\n\n                if (translationResult.first) {\n                    outcome.setUtterance(translationResult.second);\n                    outcome.setOutcome(Outcome.SUCCESS);\n\n                    final EntangledPair entangledPair = new EntangledPair(Position.TOAST_LONG, CC.COMMAND_TRANSLATE);\n                    entangledPair.setToastContent(translationResult.second);\n                    outcome.setEntangledPair(entangledPair);\n                    final Qubit qubit = new Qubit();\n                    qubit.setTranslatedText(translationResult.second);\n                    outcome.setQubit(qubit);\n                    outcome.setTTSLocale(UtilsLocale.stringToLocale(language.getLanguage()));\n                } else {\n                    outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate,\n                            PersonalityHelper.getUserNameOrNot(mContext)));\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return outcome;\n                }\n\n            } else {\n                outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate_length,\n                        PersonalityHelper.getUserNameOrNot(mContext)));\n                outcome.setOutcome(Outcome.FAILURE);\n                return outcome;\n            }\n        } else {\n            outcome.setUtterance(mContext.getString(ai.saiy.android.R.string.error_translate_unsupported,\n                    PersonalityHelper.getUserNameOrNot(mContext)));\n            outcome.setOutcome(Outcome.FAILURE);\n            return outcome;\n        }\n\n        return outcome;\n    }\n\n    /**\n     * Execute the translation request\n     *\n     * @param ctx               the application context\n     * @param language          the {@link TranslationLanguageBing}\n     * @param translationString the content to translate\n     * @return the translation result {@link Pair} with the first parameter denoting success\n     */\n    public static Pair<Boolean, String> execute(@NonNull final Context ctx,\n                                                @NonNull final TranslationLanguageGoogle language,\n                                                @NonNull final String translationString) {\n        return new GoogleTranslateAPI().execute(ctx, translationString, language);\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n\n        pGERMAN = Pattern.compile(B_START + sr.getString(R.string.german) + B_END);\n        pFRENCH = Pattern.compile(B_START + sr.getString(R.string.french) + B_END);\n        pITALIAN = Pattern.compile(B_START + sr.getString(R.string.italian) + B_END);\n        pPOLISH = Pattern.compile(B_START + sr.getString(R.string.polish) + B_END);\n        pSPANISH = Pattern.compile(B_START + sr.getString(R.string.spanish) + B_END);\n        pROMANIAN = Pattern.compile(B_START + sr.getString(R.string.romanian) + B_END);\n        pENGLISH = Pattern.compile(B_START + sr.getString(R.string.english) + B_END);\n        pARABIC = Pattern.compile(B_START + sr.getString(R.string.arabic) + B_END);\n        pBULGARIAN = Pattern.compile(B_START + sr.getString(R.string.bulgarian) + B_END);\n        pCATALAN = Pattern.compile(B_START + sr.getString(R.string.catalan) + B_END);\n        pCHINESE_S = Pattern.compile(B_START + sr.getString(R.string.chinese_simplified) + B_END);\n        pCHINESE_T = Pattern.compile(B_START + sr.getString(R.string.chinese_traditional) + B_END);\n        pCHINESE = Pattern.compile(B_START + sr.getString(R.string.chinese) + B_END);\n        pDANISH = Pattern.compile(B_START + sr.getString(R.string.danish) + B_END);\n        pDUTCH = Pattern.compile(B_START + sr.getString(R.string.dutch) + B_END);\n        pESTONIAN = Pattern.compile(B_START + sr.getString(R.string.estonian) + B_END);\n        pFINNISH = Pattern.compile(B_START + sr.getString(R.string.finnish) + B_END);\n        pGREEK = Pattern.compile(B_START + sr.getString(R.string.greek) + B_END);\n        pHEBREW = Pattern.compile(B_START + sr.getString(R.string.hebrew) + B_END);\n        pHINDI = Pattern.compile(B_START + sr.getString(R.string.hindi) + B_END);\n        pHUNGARIAN = Pattern.compile(B_START + sr.getString(R.string.hungarian) + B_END);\n        pINDONESIAN = Pattern.compile(B_START + sr.getString(R.string.indonesian) + B_END);\n        pJAPANESE = Pattern.compile(B_START + sr.getString(R.string.japanese) + B_END);\n        pKOREAN = Pattern.compile(B_START + sr.getString(R.string.korean) + B_END);\n        pLATVIAN = Pattern.compile(B_START + sr.getString(R.string.latvian) + B_END);\n        pLITHUANIAN = Pattern.compile(B_START + sr.getString(R.string.lithuanian) + B_END);\n        pNORWEGIAN = Pattern.compile(B_START + sr.getString(R.string.norwegian) + B_END);\n        pPORTUGUESE = Pattern.compile(B_START + sr.getString(R.string.portuguese) + B_END);\n        pPORTUGUESE_BRAZILIAN = Pattern.compile(B_START + sr.getString(R.string.portuguese_brazilian) + B_END);\n        pBRAZILIAN = Pattern.compile(B_START + sr.getString(R.string.brazilian) + B_END);\n        pRUSSIAN = Pattern.compile(B_START + sr.getString(R.string.russian) + B_END);\n        pSWEDISH = Pattern.compile(B_START + sr.getString(R.string.swedish) + B_END);\n        pTHAI = Pattern.compile(B_START + sr.getString(R.string.thai) + B_END);\n        pTURKISH = Pattern.compile(B_START + sr.getString(R.string.turkish) + B_END);\n        pUKRAINIAN = Pattern.compile(B_START + sr.getString(R.string.ukrainian) + B_END);\n        pVIETNAMESE = Pattern.compile(B_START + sr.getString(R.string.vietnamese) + B_END);\n        pMALTESE = Pattern.compile(B_START + sr.getString(R.string.maltese) + B_END);\n        pPERSIAN = Pattern.compile(B_START + sr.getString(R.string.persian) + B_END);\n        pSLOVAK = Pattern.compile(B_START + sr.getString(R.string.slovak) + B_END);\n        pSLOVENIAN = Pattern.compile(B_START + sr.getString(R.string.slovenian) + B_END);\n        pWELSH = Pattern.compile(B_START + sr.getString(R.string.welsh) + B_END);\n        pCROATIAN = Pattern.compile(B_START + sr.getString(R.string.croatian) + B_END);\n        pCZECH = Pattern.compile(B_START + sr.getString(R.string.czech) + B_END);\n        pCZECHOSLOVAKIAN = Pattern.compile(B_START + sr.getString(R.string.czechoslovakian) + B_END);\n        pSWAHILI = Pattern.compile(B_START + sr.getString(R.string.swahili) + B_END);\n        pKLINGON = Pattern.compile(B_START + sr.getString(R.string.klingon) + B_END);\n        pMALAY = Pattern.compile(B_START + sr.getString(R.string.malay) + B_END);\n        pMALAYSIAN = Pattern.compile(B_START + sr.getString(R.string.malaysian) + B_END);\n        pSERBIAN = Pattern.compile(B_START + sr.getString(R.string.serbian) + B_END);\n        pURDU = Pattern.compile(B_START + sr.getString(R.string.urdu) + B_END);\n        pMANDARIN = Pattern.compile(B_START + sr.getString(R.string.mandarin) + B_END);\n        pCANTONESE = Pattern.compile(B_START + sr.getString(R.string.cantonese) + B_END);\n        pTranslate = Pattern.compile(B_START + sr.getString(R.string.translate_) + B_END);\n    }\n\n    /**\n     * Extract the required translation {@link TranslationLanguageGoogle} and translation string\n     *\n     * @param ctx       the application context\n     * @param voiceData the utterance to which the extraction will take place\n     * @param sl        the {@link SupportedLanguage}\n     * @return a {@link Pair} with the first parameter denoting success\n     */\n    public static Pair<TranslationLanguageGoogle, String> extract(@NonNull final Context ctx,\n                                                                  @NonNull final ArrayList<String> voiceData,\n                                                                  @NonNull final SupportedLanguage sl) {\n        final long then = System.nanoTime();\n\n        TranslationLanguageGoogle language = null;\n        String toTranslate = null;\n\n        if (pTranslate == null) {\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        }\n\n        final Locale loc = sl.getLocale();\n        Pair<TranslationLanguageGoogle, String> translationPair;\n\n        int size = voiceData.size();\n        String vdLower;\n        for (int i = 0; i < size; i++) {\n            vdLower = voiceData.get(i).toLowerCase(loc).trim();\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"vdLower: \" + vdLower);\n            }\n\n            if (pTranslate.matcher(vdLower).matches()) {\n\n                translationPair = getTranslationPair(ctx, vdLower, sl);\n                language = translationPair.first;\n                toTranslate = translationPair.second;\n\n                if (language != null && UtilsString.notNaked(toTranslate)) {\n                    break;\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return new Pair<>(language, toTranslate);\n    }\n\n    private static Pair<TranslationLanguageGoogle, String> getTranslationPair(@NonNull final Context ctx,\n                                                                              @NonNull final String vdLower,\n                                                                              @NonNull final SupportedLanguage sl) {\n\n        TranslationLanguageGoogle language = null;\n        String toTranslate = null;\n\n        if (pGERMAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.GERMAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.german));\n        } else if (pMANDARIN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.MANDARIN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.mandarin));\n        } else if (pCANTONESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CANTONESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.cantonese));\n        } else if (pURDU.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.URDU;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.urdu));\n        } else if (pSERBIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SERBIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.serbian));\n        } else if (pMALAYSIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.MALAYSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.malaysian));\n        } else if (pMALAY.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.MALAY;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.malay));\n        } else if (pKLINGON.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.KLINGON;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.klingon));\n        } else if (pSWAHILI.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SWAHILI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.swahili));\n        } else if (pCZECH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CZECH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.czech));\n        } else if (pCZECHOSLOVAKIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CZECHOSLOVAKIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.czechoslovakian));\n        } else if (pCROATIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CROATIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.croatian));\n        } else if (pWELSH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.WELSH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.welsh));\n        } else if (pSLOVENIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SLOVENIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.slovenian));\n        } else if (pSLOVAK.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SLOVAK;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.slovak));\n        } else if (pPERSIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.PERSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.persian));\n        } else if (pMALTESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.MALTESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.maltese));\n        } else if (pFRENCH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.FRENCH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.french));\n        } else if (pITALIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.ITALIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.italian));\n        } else if (pSPANISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SPANISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.spanish));\n        } else if (pPOLISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.POLISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.polish));\n        } else if (pROMANIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.ROMANIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.romanian));\n        } else if (pENGLISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.ENGLISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.english));\n        } else if (pARABIC.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.ARABIC;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.arabic));\n        } else if (pBULGARIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.BULGARIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.bulgarian));\n        } else if (pCATALAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CATALAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.catalan));\n        } else if (pCHINESE_S.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CHINESE_SIMPLIFIED;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.chinese_simplified));\n        } else if (pCHINESE_T.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CHINESE_TRADITIONAL;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.chinese_traditional));\n        } else if (pCHINESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.CHINESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.chinese));\n        } else if (pDANISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.DANISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.danish));\n        } else if (pDUTCH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.DUTCH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.dutch));\n        } else if (pESTONIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.ESTONIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.estonian));\n        } else if (pFINNISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.FINNISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.finnish));\n        } else if (pGREEK.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.GREEK;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.greek));\n        } else if (pHEBREW.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.HEBREW;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.hebrew));\n        } else if (pHINDI.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.HINDI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.hindi));\n        } else if (pHUNGARIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.HUNGARIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.hungarian));\n        } else if (pINDONESIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.INDONESIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.indonesian));\n        } else if (pJAPANESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.JAPANESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.japanese));\n        } else if (pKOREAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.KOREAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.korean));\n        } else if (pLATVIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.LATVIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.latvian));\n        } else if (pLITHUANIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.LITHUANIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.lithuanian));\n        } else if (pNORWEGIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.NORWEGIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.norwegian));\n        } else if (pPORTUGUESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.PORTUGUESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.portuguese));\n        } else if (pPORTUGUESE_BRAZILIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.PORTUGUESE_BRAZILIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.portuguese_brazilian));\n        } else if (pBRAZILIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.BRAZILIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.brazilian));\n        } else if (pRUSSIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.RUSSIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.russian));\n        } else if (pSWEDISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.SWEDISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.swedish));\n        } else if (pTHAI.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.THAI;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.thai));\n        } else if (pTURKISH.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.TURKISH;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.turkish));\n        } else if (pUKRAINIAN.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.UKRAINIAN;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.ukrainian));\n        } else if (pVIETNAMESE.matcher(vdLower).matches()) {\n            language = TranslationLanguageGoogle.VIETNAMESE;\n            toTranslate = new Translate(sl).resolveBody(ctx, vdLower, ctx.getString(R.string.vietnamese));\n        }\n\n        return new Pair<>(language, toTranslate);\n    }\n\n    /**\n     * Resolve the required translation language from the voice data\n     *\n     * @param vd the voice data string\n     * @return the matching {@link TranslationLanguageGoogle} or {@link TranslationLanguageGoogle#AUTO_DETECT}\n     */\n    private TranslationLanguageGoogle resolveLanguage(@NonNull final String vd) {\n\n        if (vd.contains(mContext.getString(R.string.german))) {\n            return TranslationLanguageGoogle.GERMAN;\n        } else if (vd.contains(mContext.getString(R.string.french))) {\n            return TranslationLanguageGoogle.FRENCH;\n        } else if (vd.contains(mContext.getString(R.string.italian))) {\n            return TranslationLanguageGoogle.ITALIAN;\n        } else if (vd.contains(mContext.getString(R.string.spanish))) {\n            return TranslationLanguageGoogle.SPANISH;\n        } else if (vd.contains(mContext.getString(R.string.polish))) {\n            return TranslationLanguageGoogle.POLISH;\n        } else if (vd.contains(mContext.getString(R.string.romanian))) {\n            return TranslationLanguageGoogle.ROMANIAN;\n        } else if (vd.contains(mContext.getString(R.string.english))) {\n            return TranslationLanguageGoogle.ENGLISH;\n        } else if (vd.contains(mContext.getString(R.string.arabic))) {\n            return TranslationLanguageGoogle.ARABIC;\n        } else if (vd.contains(mContext.getString(R.string.bulgarian))) {\n            return TranslationLanguageGoogle.BULGARIAN;\n        } else if (vd.contains(mContext.getString(R.string.catalan))) {\n            return TranslationLanguageGoogle.CATALAN;\n        } else if (vd.contains(mContext.getString(R.string.chinese_simplified))) {\n            return TranslationLanguageGoogle.CHINESE_SIMPLIFIED;\n        } else if (vd.contains(mContext.getString(R.string.chinese_traditional))) {\n            return TranslationLanguageGoogle.CHINESE_TRADITIONAL;\n        } else if (vd.contains(mContext.getString(R.string.chinese))) {\n            return TranslationLanguageGoogle.CHINESE;\n        } else if (vd.contains(mContext.getString(R.string.mandarin))) {\n            return TranslationLanguageGoogle.MANDARIN;\n        } else if (vd.contains(mContext.getString(R.string.cantonese))) {\n            return TranslationLanguageGoogle.CANTONESE;\n        } else if (vd.contains(mContext.getString(R.string.danish))) {\n            return TranslationLanguageGoogle.DANISH;\n        } else if (vd.contains(mContext.getString(R.string.dutch))) {\n            return TranslationLanguageGoogle.DUTCH;\n        } else if (vd.contains(mContext.getString(R.string.estonian))) {\n            return TranslationLanguageGoogle.ESTONIAN;\n        } else if (vd.contains(mContext.getString(R.string.finnish))) {\n            return TranslationLanguageGoogle.FINNISH;\n        } else if (vd.contains(mContext.getString(R.string.greek))) {\n            return TranslationLanguageGoogle.GREEK;\n        } else if (vd.contains(mContext.getString(R.string.hebrew))) {\n            return TranslationLanguageGoogle.HEBREW;\n        } else if (vd.contains(mContext.getString(R.string.hindi))) {\n            return TranslationLanguageGoogle.HINDI;\n        } else if (vd.contains(mContext.getString(R.string.hungarian))) {\n            return TranslationLanguageGoogle.HUNGARIAN;\n        } else if (vd.contains(mContext.getString(R.string.indonesian))) {\n            return TranslationLanguageGoogle.INDONESIAN;\n        } else if (vd.contains(mContext.getString(R.string.japanese))) {\n            return TranslationLanguageGoogle.JAPANESE;\n        } else if (vd.contains(mContext.getString(R.string.korean))) {\n            return TranslationLanguageGoogle.KOREAN;\n        } else if (vd.contains(mContext.getString(R.string.latvian))) {\n            return TranslationLanguageGoogle.LATVIAN;\n        } else if (vd.contains(mContext.getString(R.string.lithuanian))) {\n            return TranslationLanguageGoogle.LITHUANIAN;\n        } else if (vd.contains(mContext.getString(R.string.norwegian))) {\n            return TranslationLanguageGoogle.NORWEGIAN;\n        } else if (vd.contains(mContext.getString(R.string.portuguese))) {\n            return TranslationLanguageGoogle.PORTUGUESE;\n        } else if (vd.contains(mContext.getString(R.string.portuguese_brazilian))) {\n            return TranslationLanguageGoogle.PORTUGUESE_BRAZILIAN;\n        } else if (vd.contains(mContext.getString(R.string.brazilian))) {\n            return TranslationLanguageGoogle.BRAZILIAN;\n        } else if (vd.contains(mContext.getString(R.string.russian))) {\n            return TranslationLanguageGoogle.RUSSIAN;\n        } else if (vd.contains(mContext.getString(R.string.slovenian))) {\n            return TranslationLanguageGoogle.SLOVENIAN;\n        } else if (vd.contains(mContext.getString(R.string.swedish))) {\n            return TranslationLanguageGoogle.SWEDISH;\n        } else if (vd.contains(mContext.getString(R.string.thai))) {\n            return TranslationLanguageGoogle.THAI;\n        } else if (vd.contains(mContext.getString(R.string.turkish))) {\n            return TranslationLanguageGoogle.TURKISH;\n        } else if (vd.contains(mContext.getString(R.string.ukrainian))) {\n            return TranslationLanguageGoogle.UKRAINIAN;\n        } else if (vd.contains(mContext.getString(R.string.vietnamese))) {\n            return TranslationLanguageGoogle.VIETNAMESE;\n        } else if (vd.contains(mContext.getString(R.string.urdu))) {\n            return TranslationLanguageGoogle.URDU;\n        } else if (vd.contains(mContext.getString(R.string.serbian))) {\n            return TranslationLanguageGoogle.SERBIAN;\n        } else if (vd.contains(mContext.getString(R.string.malaysian))) {\n            return TranslationLanguageGoogle.MALAYSIAN;\n        } else if (vd.contains(mContext.getString(R.string.malay))) {\n            return TranslationLanguageGoogle.MALAY;\n        } else if (vd.contains(mContext.getString(R.string.klingon))) {\n            return TranslationLanguageGoogle.KLINGON;\n        } else if (vd.contains(mContext.getString(R.string.swahili))) {\n            return TranslationLanguageGoogle.SWAHILI;\n        } else if (vd.contains(mContext.getString(R.string.czech))) {\n            return TranslationLanguageGoogle.CZECH;\n        } else if (vd.contains(mContext.getString(R.string.czechoslovakian))) {\n            return TranslationLanguageGoogle.CZECHOSLOVAKIAN;\n        } else if (vd.contains(mContext.getString(R.string.croatian))) {\n            return TranslationLanguageGoogle.CROATIAN;\n        } else if (vd.contains(mContext.getString(R.string.welsh))) {\n            return TranslationLanguageGoogle.WELSH;\n        } else if (vd.contains(mContext.getString(R.string.slovak))) {\n            return TranslationLanguageGoogle.SLOVAK;\n        } else if (vd.contains(mContext.getString(R.string.persian))) {\n            return TranslationLanguageGoogle.PERSIAN;\n        } else if (vd.contains(mContext.getString(R.string.maltese))) {\n            return TranslationLanguageGoogle.MALTESE;\n        } else {\n            return TranslationLanguageGoogle.AUTO_DETECT;\n        }\n    }\n\n    /**\n     * Check if the translation request exceeds the {@link #MAX_TRANSLATE_LENGTH}\n     *\n     * @param toTranslate the string to translate\n     * @return true if the string is too long. False otherwise\n     */\n    public static boolean tooLong(@NonNull final String toTranslate) {\n        return toTranslate.length() > MAX_TRANSLATE_LENGTH;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/google/GoogleTranslateAPI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.google;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.google.api.client.http.javanet.NetHttpTransport;\nimport com.google.api.client.json.gson.GsonFactory;\nimport com.google.api.services.translate.Translate;\nimport com.google.api.services.translate.model.TranslationsListResponse;\nimport com.google.api.services.translate.model.TranslationsResource;\n\nimport org.apache.commons.lang3.StringEscapeUtils;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.configuration.GoogleConfiguration;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 02/05/2016.\n */\npublic class GoogleTranslateAPI {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = GoogleTranslateAPI.class.getSimpleName();\n\n    /**\n     * Perform a synchronous translation request\n     *\n     * @param ctx      the application context\n     * @param text     the text to be translated\n     * @param language the {@link TranslationLanguageGoogle}\n     * @return a {@link Pair} with the first parameter donating success and the second the result\n     */\n    public Pair<Boolean, String> execute(@NonNull final Context ctx, @NonNull final String text,\n                                         @NonNull final TranslationLanguageGoogle language) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n\n        final long then = System.nanoTime();\n\n        try {\n\n            final Translate translate = new Translate.Builder(new NetHttpTransport(),\n                    GsonFactory.getDefaultInstance(), null).setApplicationName(ctx.getString(R.string.app_name)).build();\n\n            final List<String> textList = new ArrayList<>();\n            textList.add(text);\n\n            final Translate.Translations.List list = translate.new Translations().list(textList,\n                    language.toString());\n            list.setKey(GoogleConfiguration.GOOGLE_TRANSLATE_API_KEY);\n\n            final TranslationsListResponse response = list.execute();\n\n            if (response != null && !response.isEmpty()) {\n                if (DEBUG) {\n                    for (final TranslationsResource resource : response.getTranslations()) {\n                        MyLog.i(CLS_NAME, \"resource: \" + resource.getTranslatedText());\n                    }\n                }\n\n                if (DEBUG) {\n                    MyLog.getElapsed(CLS_NAME, then);\n                }\n\n                return new Pair<>(true, StringEscapeUtils.unescapeHtml4(response.getTranslations().get(0).getTranslatedText()));\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: IOException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"execute: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return new Pair<>(false, null);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/translate/provider/google/TranslationLanguageGoogle.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Copyright (C) 2007,  Richard Midwinter\n *\n *      This file is part of google-api-translate-java.\n *\n *      google-api-translate-java is free software: you can redistribute it and/or\n *      modify it under the terms of the GNU Lesser General Public License as\n *      published by the Free Software Foundation, either version 3 of the License,\n *      or (at your option) any later version.\n *\n *      google-api-translate-java 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 Lesser General Public License\n *      along with google-api-translate-java. If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.translate.provider.google;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 02/05/2016.\n * <p>\n * Defines language information for the Google Translate API.\n *\n * @author Richard Midwinter\n * @author alosii\n * @author bjkuczynski\n */\npublic enum TranslationLanguageGoogle {\n    AUTO_DETECT(\"\"),\n    AFRIKAANS(\"af\"),\n    ALBANIAN(\"sq\"),\n    AMHARIC(\"am\"),\n    ARABIC(\"ar\"),\n    ARMENIAN(\"hy\"),\n    AZERBAIJANI(\"az\"),\n    BASQUE(\"eu\"),\n    BELARUSIAN(\"be\"),\n    BELARUS(\"be\"),\n    BENGALI(\"bn\"),\n    BIHARI(\"bh\"),\n    BULGARIAN(\"bg\"),\n    BURMESE(\"my\"),\n    CATALAN(\"ca\"),\n    CHEROKEE(\"chr\"),\n    CHINESE(\"zh\"),\n    CHINESE_SIMPLIFIED(\"zh-CN\"),\n    CHINESE_TRADITIONAL(\"zh-TW\"),\n    CANTONESE(\"zh-TW\"),\n    MANDARIN(\"zh-CN\"),\n    CROATIAN(\"hr\"),\n    CZECH(\"cs\"),\n    CZECHOSLOVAKIAN(\"cs\"),\n    DANISH(\"da\"),\n    DHIVEHI(\"dv\"),\n    DUTCH(\"nl\"),\n    ENGLISH(\"en\"),\n    ESPERANTO(\"eo\"),\n    ESTONIAN(\"et\"),\n    FILIPINO(\"tl\"),\n    FINNISH(\"fi\"),\n    FRENCH(\"fr\"),\n    GALICIAN(\"gl\"),\n    GEORGIAN(\"ka\"),\n    GERMAN(\"de\"),\n    GREEK(\"el\"),\n    GUARANI(\"gn\"),\n    GUJARATI(\"gu\"),\n    HEBREW(\"iw\"),\n    HINDI(\"hi\"),\n    HUNGARIAN(\"hu\"),\n    ICELANDIC(\"is\"),\n    INDONESIAN(\"id\"),\n    INUKTITUT(\"iu\"),\n    IRISH(\"ga\"),\n    ITALIAN(\"it\"),\n    JAPANESE(\"ja\"),\n    KANNADA(\"kn\"),\n    KAZAKH(\"kk\"),\n    KHMER(\"km\"),\n    KLINGON(\"tlh\"),\n    KOREAN(\"ko\"),\n    KURDISH(\"ku\"),\n    KYRGYZ(\"ky\"),\n    LAOTHIAN(\"lo\"),\n    LATVIAN(\"lv\"),\n    LITHUANIAN(\"lt\"),\n    MACEDONIAN(\"mk\"),\n    MALAY(\"ms\"),\n    MALAYSIAN(\"ms\"),\n    MALAYALAM(\"ml\"),\n    MALTESE(\"mt\"),\n    MARATHI(\"mr\"),\n    MONGOLIAN(\"mn\"),\n    NEPALI(\"ne\"),\n    NORWEGIAN(\"no\"),\n    ORIYA(\"or\"),\n    PASHTO(\"ps\"),\n    PERSIAN(\"fa\"),\n    POLISH(\"pl\"),\n    PORTUGUESE(\"pt\"),\n    PORTUGUESE_BRAZILIAN(\"pt-BR\"),\n    BRAZILIAN(\"pt-BR\"),\n    PUNJABI(\"pa\"),\n    ROMANIAN(\"ro\"),\n    RUSSIAN(\"ru\"),\n    SANSKRIT(\"sa\"),\n    SERBIAN(\"sr\"),\n    SINDHI(\"sd\"),\n    SINHALESE(\"si\"),\n    SLOVAK(\"sk\"),\n    SLOVENIAN(\"sl\"),\n    SPANISH(\"es\"),\n    SWAHILI(\"sw\"),\n    SWEDISH(\"sv\"),\n    TAJIK(\"tg\"),\n    TAMIL(\"ta\"),\n    TAGALOG(\"tl\"),\n    TELUGU(\"te\"),\n    THAI(\"th\"),\n    TIBETAN(\"bo\"),\n    TURKISH(\"tr\"),\n    UKRAINIAN(\"uk\"),\n    URDU(\"ur\"),\n    UZBEK(\"uz\"),\n    UIGHUR(\"ug\"),\n    VIETNAMESE(\"vi\"),\n    WELSH(\"cy\"),\n    YIDDISH(\"yi\");\n\n    /**\n     * Google's String representation of this language.\n     */\n    private final String language;\n\n    /**\n     * Enum constructor.\n     *\n     * @param language The language identifier.\n     */\n    TranslationLanguageGoogle(@NonNull final String language) {\n        this.language = language;\n    }\n\n    /**\n     * Returns the String representation of the {@link java.util.Locale}\n     *\n     * @return the String representation\n     */\n    public String getLanguage() {\n        return this.language;\n    }\n\n\n    /**\n     * Returns the String representation of this language.\n     *\n     * @return The String representation of this language.\n     */\n    @Override\n    public String toString() {\n        return this.language;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/unknown/Unknown.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.unknown;\n\n/**\n * Created by benrandall76@gmail.com on 29/08/2016.\n */\n\npublic class Unknown {\n\n    public static final int UNKNOWN_STATE = 0;\n    public static final int UNKNOWN_REPEAT = 1;\n    public static final int UNKNOWN_GOOGLE_SEARCH = 2;\n    public static final int UNKNOWN_WOLFRAM_ALPHA = 3;\n    public static final int UNKNOWN_TASKER = 4;\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/username/CommandUserName.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.username;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to process a user name request. Handles both remote NLP intents and falling back to\n * resolving locally.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandUserName {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandUserName.class.getSimpleName();\n\n    private long then;\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @param cr        the {@link CommandRequest}\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        then = System.nanoTime();\n\n        final Outcome outcome = new Outcome();\n\n        String name;\n\n        if (cr.isResolved()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: true\");\n            }\n\n            final CommandUserNameValues cunv = (CommandUserNameValues) cr.getVariableData();\n            name = cunv.getName();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: false\");\n            }\n            name = new CommandUserNameLocal().getResponse(ctx, voiceData, sl);\n        }\n\n        if (UtilsString.notNaked(name)) {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"name: \" + name);\n            }\n\n            final String response;\n            final String currentName = SPH.getUserName(ctx);\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"currentName: \" + currentName);\n            }\n\n            if (currentName.matches(name)) {\n                response = String.format(PersonalityResponse.getUserNameRepeat(ctx, sl), currentName);\n            } else {\n                SPH.setUserName(ctx, name);\n                response = String.format(PersonalityResponse.getUserName(ctx, sl), name);\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"response: \" + response);\n                }\n            }\n\n            outcome.setUtterance(response);\n            outcome.setOutcome(Outcome.SUCCESS);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"name naked\");\n            }\n\n            name = PersonalityResponse.getUserNameError(ctx, sl);\n            outcome.setUtterance(name);\n            outcome.setOutcome(Outcome.FAILURE);\n            return returnOutcome(outcome);\n        }\n\n        return returnOutcome(outcome);\n    }\n\n    /**\n     * A single point of return to check the elapsed time for debugging.\n     *\n     * @param outcome the constructed {@link Outcome}\n     * @return the constructed {@link Outcome}\n     */\n    private Outcome returnOutcome(@NonNull final Outcome outcome) {\n        if (DEBUG) {\n            MyLog.getElapsed(CommandUserName.class.getSimpleName(), then);\n        }\n        return outcome;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/username/CommandUserNameLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.username;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class that uses strings to extract the name by which the user wants to\n * be known.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandUserNameLocal {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandUserNameLocal.class.getSimpleName();\n\n    /**\n     * Resolve the name by which the user wishes to be addressed and return it as a String.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @return the name by which the user wishes to be addressed\n     */\n    public String getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                              @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final ArrayList<String> nameData = new UserName(sl).fetch(ctx, voiceData);\n        if (UtilsList.notNaked(nameData)) {\n            return nameData.get(0);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/username/CommandUserNameValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.username;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class for getters and setters of the user name command parameters\n * <p>\n * Created by benrandall76@gmail.com on 01/06/2016.\n */\npublic class CommandUserNameValues {\n\n    private String name;\n    private int[][] ranges;\n    private long startIndex;\n    private long endIndex;\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n\n    public void setRanges(@NonNull final int[][] ranges) {\n        this.ranges = ranges;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(@NonNull final String name) {\n        this.name = name;\n    }\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public void setEndIndex(final long endIndex) {\n        this.endIndex = endIndex;\n    }\n\n    public void setStartIndex(final long startIndex) {\n        this.startIndex = startIndex;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/username/UserName.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.username;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the required command\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class UserName implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object userName;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public UserName(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                    @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                userName = new UserName_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                userName = new UserName_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                userName = new UserName_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public UserName(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((UserName_en) userName).detectCallable();\n            case ENGLISH_US:\n                return ((UserName_en) userName).detectCallable();\n            default:\n                return ((UserName_en) userName).detectCallable();\n        }\n    }\n\n    /**\n     * Strip out all but the required command and prepare the strings to use\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> of voice data\n     * @return only the voice data associated with this command, prepared to use.\n     */\n    public ArrayList<String> fetch(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return UserName_en.sortUserName(ctx, voiceData, sl);\n            case ENGLISH_US:\n                return UserName_en.sortUserName(ctx, voiceData, sl);\n            default:\n                return UserName_en.sortUserName(ctx, voiceData, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/username/UserName_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.username;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve user name commands.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class UserName_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = UserName_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String call_me_;\n    private static String address_me_as_;\n    private static String addressed_as_;\n    private static String my_name_is_;\n    private static String be_called_;\n    private static String be_known_as_;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public UserName_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                       @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (call_me_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        call_me_ = sr.getString(R.string.call_me_);\n        address_me_as_ = sr.getString(R.string.address_me_as_);\n        addressed_as_ = sr.getString(R.string.addressed_as_);\n        my_name_is_ = sr.getString(R.string.my_name_is_);\n        be_called_ = sr.getString(R.string.be_called_);\n        be_known_as_ = sr.getString(R.string.be_known_as_);\n    }\n\n    /**\n     * Iterate through the voice data array to see if the command can be detected.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(call_me_) || vdLower.contains(address_me_as_)\n                        || vdLower.contains(my_name_is_) || vdLower.contains(addressed_as_)\n                        || vdLower.contains(be_called_) || vdLower.contains(be_known_as_)) {\n\n                    toReturn.add(new Pair<>(CC.COMMAND_USER_NAME, confidence[i]));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"userName: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Static method.\n     * <p>\n     * Iterate through the voice data array to return only the voice data associated with the\n     * required command.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @return an array list containing only the required data\n     */\n    public static ArrayList<String> sortUserName(@NonNull final Context ctx, final ArrayList<String> voiceData,\n                                                 @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n        final ArrayList<String> toReturn = new ArrayList<>();\n\n        if (call_me_ == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        String vdLower;\n        String[] separated;\n        for (final String vd : voiceData) {\n            vdLower = vd.toLowerCase(loc).trim();\n\n            if (vdLower.contains(call_me_)) {\n                separated = vdLower.split(call_me_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            } else if (vdLower.contains(address_me_as_)) {\n                separated = vdLower.split(address_me_as_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            } else if (vdLower.contains(my_name_is_)) {\n                separated = vdLower.split(my_name_is_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            } else if (vdLower.contains(addressed_as_)) {\n                separated = vdLower.split(addressed_as_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            } else if (vdLower.contains(be_called_)) {\n                separated = vdLower.split(be_called_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            } else if (vdLower.contains(be_known_as_)) {\n                separated = vdLower.split(be_known_as_);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            }\n        }\n\n        if (!toReturn.isEmpty()) {\n            final Set<String> deduplicated = new LinkedHashSet<>(toReturn);\n            toReturn.clear();\n            toReturn.addAll(deduplicated);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/vocalrecognition/CommandVocalRecognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.vocalrecognition;\n\n/**\n * Created by benrandall76@gmail.com on 13/08/2016.\n */\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileItem;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Qubit;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.user.SaiyAccount;\nimport ai.saiy.android.user.SaiyAccountHelper;\nimport ai.saiy.android.user.SaiyAccountList;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to process a command request. Used only to decide which introduction the user should hear.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandVocalRecognition {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandVocalRecognition.class.getSimpleName();\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n\n        final Outcome outcome = new Outcome();\n        outcome.setQubit(new Qubit());\n\n        final SaiyAccountList saiyAccountList = SaiyAccountHelper.getAccounts(ctx);\n\n        if (saiyAccountList != null && saiyAccountList.size() > 0) {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"saiyAccountList.size: \" + saiyAccountList.size());\n            }\n\n            // TODO - handle multiple accounts\n\n            switch (saiyAccountList.size()) {\n\n                case 1:\n                default:\n\n                    final SaiyAccount saiyAccount = saiyAccountList.getSaiyAccountList().get(0);\n\n                    if (saiyAccount != null) {\n\n                        final ProfileItem profileItem = saiyAccount.getProfileItem();\n\n                        if (profileItem != null) {\n\n                            final String profileId = profileItem.getId();\n\n                            if (UtilsString.notNaked(profileId)) {\n                                if (DEBUG) {\n                                    MyLog.d(CLS_NAME, \"profileId: \" + profileId);\n                                }\n\n                                final Speaker.Status status = Speaker.Status.getStatus(profileItem.getStatus());\n                                if (DEBUG) {\n                                    MyLog.d(CLS_NAME, \"status: \" + status.name());\n                                }\n\n                                switch (status) {\n\n                                    case SUCCEEDED:\n\n                                        outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                                                ctx, sl, R.string.speech_enroll_instructions_15));\n                                        outcome.setAction(LocalRequest.ACTION_SPEAK_LISTEN);\n                                        outcome.setOutcome(Outcome.SUCCESS);\n                                        outcome.setExtra(profileId);\n\n                                        break;\n                                    default:\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"enrollment status\");\n                                        }\n\n                                        outcome.setOutcome(Outcome.FAILURE);\n                                        outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                                                ctx, sl, R.string.error_vi_status));\n                                        break;\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"profile id naked\");\n                                }\n\n                                outcome.setOutcome(Outcome.FAILURE);\n                                outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                                        ctx, sl, R.string.error_vi_status));\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"profile item null\");\n                            }\n\n                            outcome.setOutcome(Outcome.FAILURE);\n                            outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                                    ctx, sl, R.string.error_vi_status));\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"account null\");\n                        }\n\n                        outcome.setOutcome(Outcome.FAILURE);\n                        outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                                ctx, sl, R.string.error_vi_no_account));\n                    }\n\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"no accounts\");\n            }\n\n            outcome.setOutcome(Outcome.FAILURE);\n            outcome.setUtterance(SaiyResourcesHelper.getStringResource(\n                    ctx, sl, R.string.error_vi_no_account));\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return outcome;\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/vocalrecognition/VocalRecognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.vocalrecognition;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to detect the required command\n * <p/>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class VocalRecognition implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private final Object vocalRecognition;\n\n    /**\n     * Constructor\n     * <p/>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public VocalRecognition(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                   @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                vocalRecognition = new VocalRecognition_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                vocalRecognition = new VocalRecognition_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                vocalRecognition = new VocalRecognition_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((VocalRecognition_en) vocalRecognition).detectCallable();\n            case ENGLISH_US:\n                return ((VocalRecognition_en) vocalRecognition).detectCallable();\n            default:\n                return ((VocalRecognition_en) vocalRecognition).detectCallable();\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/vocalrecognition/VocalRecognition_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.vocalrecognition;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve vocal id commands.\n * <p/>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class VocalRecognition_en {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = VocalRecognition_en.class.getSimpleName();\n\n    private static String voice;\n    private static String analyse;\n    private static String analysis;\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    public VocalRecognition_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                               @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (voice == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        voice = sr.getString(R.string.voice);\n        analyse = sr.getString(R.string.analyse);\n        analysis = sr.getString(R.string.analysis);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p/>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            String vdLower;\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(voice) && (vdLower.contains(analyse) || vdLower.contains(analysis))) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length < 7) {\n                        toReturn.add(new Pair<>(CC.COMMAND_VOICE_IDENTIFY, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"vocal recognition: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/wolframalpha/CommandWolframAlpha.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.wolframalpha;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.WolframAlphaCognitive;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve.WolframAlphaRequest;\nimport ai.saiy.android.cognitive.knowledge.provider.wolframalpha.resolve.WolframAlphaResponse;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Outcome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to process a Wolfram Alpha request. Handles both remote NLP intents and falling back to\n * resolving locally.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandWolframAlpha {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandWolframAlpha.class.getSimpleName();\n\n    private long then;\n\n    /**\n     * Resolve the required command returning an {@link Outcome} object\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @param cr        the {@link CommandRequest}\n     * @return {@link Outcome} containing everything we need to respond to the command.\n     */\n    public Outcome getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                               @NonNull final SupportedLanguage sl, @NonNull final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        then = System.nanoTime();\n\n        final Outcome outcome = new Outcome();\n\n        String question;\n\n        if (cr.isResolved()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: true\");\n            }\n\n            final CommandWolframAlphaValues cwav = (CommandWolframAlphaValues) cr.getVariableData();\n            question = cwav.getQuestion();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isResolved: false\");\n            }\n            question = new CommandWolframAlphaLocal().getResponse(ctx, voiceData, sl);\n        }\n\n        if (UtilsString.notNaked(question)) {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"question: \" + question);\n            }\n\n            final String response;\n\n            if (new WolframAlphaCognitive().validate(question)) {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"validated: true\");\n                }\n\n                final WolframAlphaRequest request = new WolframAlphaRequest();\n                request.setAutoShow(false);\n                request.setQuery(question);\n                request.setType(WolframAlphaRequest.Type.GENERAL);\n\n                final Pair<Boolean, WolframAlphaResponse> responsePair = new WolframAlphaCognitive().execute(request);\n\n                if (responsePair.first) {\n\n                    response = PersonalityResponse.getWolframAlphaIntro(ctx, sl) + \". \"\n                            + responsePair.second.getInterpretation() + \". \"\n                            + responsePair.second.getResult();\n                    outcome.setUtterance(response);\n                    outcome.setOutcome(Outcome.SUCCESS);\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"responsePair: false\");\n                    }\n\n                    question = PersonalityResponse.getWolframAlphaError(ctx, sl);\n                    outcome.setUtterance(question);\n                    outcome.setOutcome(Outcome.FAILURE);\n                    return returnOutcome(outcome);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"validation: false\");\n                }\n\n                question = PersonalityResponse.getWolframAlphaError(ctx, sl);\n                outcome.setUtterance(question);\n                outcome.setOutcome(Outcome.FAILURE);\n                return returnOutcome(outcome);\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"question naked\");\n            }\n\n            question = PersonalityResponse.getWolframAlphaError(ctx, sl);\n            outcome.setUtterance(question);\n            outcome.setOutcome(Outcome.FAILURE);\n            return returnOutcome(outcome);\n        }\n\n        return returnOutcome(outcome);\n    }\n\n    /**\n     * A single point of return to check the elapsed time for debugging.\n     *\n     * @param outcome the constructed {@link Outcome}\n     * @return the constructed {@link Outcome}\n     */\n    private Outcome returnOutcome(@NonNull final Outcome outcome) {\n        if (DEBUG) {\n            MyLog.getElapsed(CommandWolframAlpha.class.getSimpleName(), then);\n        }\n        return outcome;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/wolframalpha/CommandWolframAlphaLocal.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.wolframalpha;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class that uses strings to extract the question the user wishes to ask Wolfram Alpha\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class CommandWolframAlphaLocal {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CommandWolframAlphaLocal.class.getSimpleName();\n\n    /**\n     * Resolve the question the user wishes to ask and return it as a String.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage} we are using to analyse the voice data.\n     * @return the question the user wishes to ask\n     */\n    public String getResponse(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                              @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final ArrayList<String> nameData = new WolframAlpha(sl).sort(ctx, voiceData);\n        if (UtilsList.notNaked(nameData)) {\n            return nameData.get(0);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/wolframalpha/CommandWolframAlphaValues.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.wolframalpha;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class to package the NLP Intent details for a Wolfram Alpha request\n * <p>\n * Created by benrandall76@gmail.com on 10/08/2016.\n */\n\npublic class CommandWolframAlphaValues {\n\n    private String question;\n    private int[][] ranges;\n    private long startIndex;\n    private long endIndex;\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n\n    public void setRanges(@NonNull final int[][] ranges) {\n        this.ranges = ranges;\n    }\n\n    public String getQuestion() {\n        return question;\n    }\n\n    public void setQuestion(@NonNull final String question) {\n        this.question = question;\n    }\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public void setEndIndex(final long endIndex) {\n        this.endIndex = endIndex;\n    }\n\n    public void setStartIndex(final long startIndex) {\n        this.startIndex = startIndex;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/wolframalpha/WolframAlpha.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.wolframalpha;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\n\n/**\n * Helper class to direct any voice data to the correct localisation to resolve the command.\n * <p>\n * Performance is key, so initialising localised resource Strings needs to be done as few times as\n * possible, whenever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class WolframAlpha implements Callable<ArrayList<Pair<CC, Float>>> {\n\n    private final SupportedLanguage sl;\n    private Object spell;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by the {@link Callable} to construct the data ready for {@link Callable#call()}\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public WolframAlpha(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                        @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n\n        switch (sl) {\n\n            case ENGLISH:\n                spell = new WolframAlpha_en(sr, sl, voiceData, confidence);\n                break;\n            case ENGLISH_US:\n                spell = new WolframAlpha_en(sr, sl, voiceData, confidence);\n                break;\n            default:\n                spell = new WolframAlpha_en(sr, SupportedLanguage.ENGLISH, voiceData, confidence);\n                break;\n        }\n    }\n\n    /**\n     * Constructor (used during a command)\n     * <p>\n     * Used when we will be constructing and managing our own {@link SaiyResources} object\n     *\n     * @param sl the {@link SupportedLanguage}\n     */\n    public WolframAlpha(@NonNull final SupportedLanguage sl) {\n        this.sl = sl;\n    }\n\n    /**\n     * Used by the {@link Callable} in {@link Callable#call()}\n     *\n     * @return an array list containing {@link Pair} of {@link CC} and {@link Float} confidence scores\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return ((WolframAlpha_en) spell).detectCallable();\n            case ENGLISH_US:\n                return ((WolframAlpha_en) spell).detectCallable();\n            default:\n                return ((WolframAlpha_en) spell).detectCallable();\n        }\n    }\n\n    /**\n     * Strip out all but the required command and prepare the strings to use\n     *\n     * @param voiceData ArrayList<String> of voice data\n     * @return only the voice data associated with this command, prepared to use.\n     */\n    public ArrayList<String> sort(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n\n        switch (sl) {\n\n            case ENGLISH:\n                return WolframAlpha_en.sortWolframAlpha(ctx, voiceData, sl);\n            case ENGLISH_US:\n                return WolframAlpha_en.sortWolframAlpha(ctx, voiceData, sl);\n            default:\n                return WolframAlpha_en.sortWolframAlpha(ctx, voiceData, SupportedLanguage.ENGLISH);\n        }\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public ArrayList<Pair<CC, Float>> call() throws Exception {\n        return detectCallable();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/command/wolframalpha/WolframAlpha_en.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.command.wolframalpha;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to resolve Wolfram Alpha commands.\n * <p>\n * Created by benrandall76@gmail.com on 06/04/2016.\n */\npublic class WolframAlpha_en {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = WolframAlpha_en.class.getSimpleName();\n\n    private final SupportedLanguage sl;\n    private final ArrayList<String> voiceData;\n    private final float[] confidence;\n\n    private static String wolfram_alpha;\n    private static String alpha;\n\n    /**\n     * Constructor\n     * <p>\n     * Used by a {@link Callable} to prepare everything that is need when\n     * {@link Callable#call()} is executed with the {@link SaiyResources} managed elsewhere.\n     *\n     * @param sr         the {@link SaiyResources}\n     * @param sl         the {@link SupportedLanguage}\n     * @param voiceData  the array of voice data\n     * @param confidence the array of confidence scores\n     */\n    public WolframAlpha_en(@NonNull final SaiyResources sr, @NonNull final SupportedLanguage sl,\n                           @NonNull final ArrayList<String> voiceData, @NonNull float[] confidence) {\n        this.sl = sl;\n        this.voiceData = voiceData;\n        this.confidence = confidence;\n\n        if (wolfram_alpha == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            initStrings(sr);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        wolfram_alpha = sr.getString(R.string.wolfram_alpha);\n        alpha = sr.getString(R.string.alpha);\n    }\n\n    /**\n     * Iterate through the voice data array to see if we can match the command.\n     * <p>\n     * Note - As the speech array will never contain more than ten entries, to consider the static\n     * nature and performance issues here, perhaps implementing a matcher, would probably be overkill.\n     *\n     * @return an Array list of Pairs containing the {@link CC} and float confidence\n     */\n    public ArrayList<Pair<CC, Float>> detectCallable() {\n\n        final long then = System.nanoTime();\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (UtilsList.notNaked(voiceData) && UtilsList.notNaked(confidence)\n                && voiceData.size() == confidence.length) {\n\n            String vdLower;\n            String word;\n            String[] wordsList;\n            final Locale loc = sl.getLocale();\n\n            int size = voiceData.size();\n            for (int i = 0; i < size; i++) {\n                vdLower = voiceData.get(i).toLowerCase(loc).trim();\n\n                if (vdLower.contains(wolfram_alpha)) {\n\n                    wordsList = vdLower.trim().split(\"\\\\s+\");\n\n                    if (wordsList.length > 5) {\n\n                        for (int j = 0; j < 6; j++) {\n                            word = wordsList[j];\n                            if (word.contains(alpha)) {\n                                toReturn.add(new Pair<>(CC.COMMAND_WOLFRAM_ALPHA, confidence[i]));\n                                break;\n                            }\n                        }\n\n                    } else {\n                        toReturn.add(new Pair<>(CC.COMMAND_WOLFRAM_ALPHA, confidence[i]));\n                    }\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Wolfram Alpha: returning ~ \" + toReturn.size());\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Static method.\n     * <p>\n     * Iterate through the voice data array to return only the voice data associated with the\n     * required command.\n     *\n     * @param ctx       the application context\n     * @param voiceData ArrayList<String> containing the voice data\n     * @param sl        the {@link SupportedLanguage}\n     * @return an array list containing only the required data\n     */\n    public static ArrayList<String> sortWolframAlpha(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                                                     @NonNull final SupportedLanguage sl) {\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n        final ArrayList<String> toReturn = new ArrayList<>();\n\n        if (wolfram_alpha == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            final SaiyResources sr = new SaiyResources(ctx, sl);\n            initStrings(sr);\n            sr.reset();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n\n        String vdLower;\n        String[] separated;\n        for (final String vd : voiceData) {\n            vdLower = vd.toLowerCase(loc).trim();\n\n            if (vdLower.contains(wolfram_alpha)) {\n                separated = vdLower.split(wolfram_alpha);\n\n                if (separated.length > 1) {\n                    toReturn.add(separated[1].trim());\n                }\n            }\n        }\n\n        if (!toReturn.isEmpty()) {\n            final Set<String> deduplicated = new LinkedHashSet<>(toReturn);\n            toReturn.clear();\n            toReturn.addAll(deduplicated);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/APIAIConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\nimport android.content.res.Resources;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class APIAIConfiguration {\n\n    /**\n     * Prevent instantiation\n     */\n    public APIAIConfiguration() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final String CLIENT_ACCESS_TOKEN = \"_your_value_here_\";\n    public static final String DEVELOPER_ACCESS_TOKEN = \"_your_value_here_\";\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/BeyondVerbalConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\n/**\n * Created by benrandall76@gmail.com on 09/06/2016.\n */\npublic class BeyondVerbalConfiguration {\n\n    public static final String API_KEY = \"_your_value_here_\";\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/BluemixConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 02/08/2016.\n */\n\npublic class BluemixConfiguration {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = BluemixConfiguration.class.getSimpleName();\n\n    /**\n     * Prevent instantiation\n     */\n    public BluemixConfiguration() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final String BLUEMIX_USERNAME = \"_your_value_here_\";\n    public static final String BLUEMIX_PASSWORD = \"_your_value_here_\";\n    public static final String BLUEMIX_SERVICE_URL = \"wss://stream.watsonplatform.net/speech-to-text/api\";\n    public static final String BLUEMIX_SERVICE_URL_EXT = \"/v1/recognize?model=\";\n\n    public static URI getSpeechURI(@NonNull final String model) {\n\n        try {\n            return new URI(BluemixConfiguration.BLUEMIX_SERVICE_URL + BluemixConfiguration.BLUEMIX_SERVICE_URL_EXT\n                    + model);\n        } catch (final URISyntaxException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"URISyntaxException\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/GoogleConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\nimport android.content.res.Resources;\n\nimport com.google.auth.oauth2.AccessToken;\n\nimport java.util.Date;\n\n/**\n * Enter your Google Chromium Speech API key below. You need to register in the Google Group and\n * enable this in your API console. Without doing both, it WILL NOT WORK!\n * <p/>\n * Created by benrandall76@gmail.com on 12/02/2016.\n */\npublic final class GoogleConfiguration {\n\n    /**\n     * Prevent instantiation\n     */\n    public GoogleConfiguration() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final String GOOGLE_SPEECH_API_KEY = \"_your_value_here_\";\n    public static final String GOOGLE_TRANSLATE_API_KEY = \"_your_value_here_\";\n\n    private static final String GOOGLE_SPEECH_CLOUD_API_KEY = \"_your_value_here_\";\n\n    public static final AccessToken ACCESS_TOKEN = new AccessToken(GoogleConfiguration.GOOGLE_SPEECH_CLOUD_API_KEY,\n            new Date(System.currentTimeMillis() + 3600000L));\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/MicrosoftConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\nimport android.content.res.Resources;\n\n/**\n * Created by benrandall76@gmail.com on 17/04/2016.\n */\npublic class MicrosoftConfiguration {\n\n    /**\n     * Prevent instantiation\n     */\n    public MicrosoftConfiguration() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final String MS_TRANSLATE_SUBSCRIPTION_KEY = \"_your_value_here_\";\n\n    public static final String OXFORD_KEY_1 = \"_your_value_here_\";\n    public static final String OXFORD_KEY_2 = \"_your_value_here_\";\n    public static final String LUIS_APP_ID = \"_your_value_here_\";\n    public static final String LUIS_SUBSCRIPTION_ID = \"_your_value_here_\";\n\n    public static final String OCP_APIM_KEY_1 = \"_your_value_here_\";\n    public static final String OCP_APIM_KEY_2 = \"_your_value_here_\";\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/NuanceConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\nimport android.content.res.Resources;\nimport android.net.Uri;\n\nimport com.nuance.speechkit.PcmFormat;\n\n/**\n * Enter your Nuance configuration details here. They are accessed from the app details in the\n * Nuance developer portal.\n * <p>\n * Created by benrandall76@gmail.com on 07/02/2016.\n */\npublic final class NuanceConfiguration {\n\n    /**\n     * Prevent instantiation\n     */\n    public NuanceConfiguration() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    //All fields are required.\n    //Your credentials can be found in your Nuance Developers portal, under \"Manage My Apps\".\n    public static final String APP_KEY = \"_your_value_here_\";\n    public static final String APP_ID = \"_your_value_here_\";\n    public static final String SERVER_HOST_NLU = \"nmsps.dev.nuance.com\";\n    public static final String SERVER_HOST = \"hiy.nmdp.nuancemobility.net\";\n    public static final String SERVER_PORT = \"443\";\n\n    public static final Uri SERVER_URI = Uri.parse(\"nmsps://\" + APP_ID + \"@\" + SERVER_HOST + \":\" + SERVER_PORT);\n    public static final Uri SERVER_URI_NLU = Uri.parse(\"nmsps://\" + APP_ID + \"@\" + SERVER_HOST_NLU + \":\" + SERVER_PORT);\n\n    //Only needed if using NLU\n    public static final String CONTEXT_TAG = \"_your_value_here_\";\n\n    public static final PcmFormat PCM_FORMAT = new PcmFormat(PcmFormat.SampleFormat.SignedLinear16, 16000, 1);\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/WitConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class WitConfiguration {\n\n    public static final String WIT_ACCESS_TOKEN = \"_your_value_here_\";\n    public static final String WIT_SPEECH_URL = \"https://api.wit.ai/speech?v=20160526\";\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/configuration/WolframConfiguration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.configuration;\n\n/**\n * Created by benrandall76@gmail.com on 06/08/2016.\n */\n\npublic class WolframConfiguration {\n\n    public static final String QUERY_URL = \"http://api.wolframalpha.com/v2/query?input=\";\n    public static final String VALIDATE_URL = \"http://api.wolframalpha.com/v2/validatequery?input=\";\n    public static final String WOLFRAM_APP_ID = \"_your_value_here_\";\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CCC.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\n/**\n * Class the holds the Custom Command Constants (=CCC)\n * <p/>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic enum CCC {\n\n    CUSTOM_SPEECH,\n    CUSTOM_DISPLAY_CONTACT,\n    CUSTOM_TASKER_TASK,\n    CUSTOM_ACTIVITY,\n    CUSTOM_CALL_CONTACT,\n    CUSTOM_LAUNCH_APPLICATION,\n    CUSTOM_LAUNCH_SHORTCUT,\n    CUSTOM_SEARCHABLE,\n    CUSTOM_INTENT_SERVICE\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/Custom.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.custom;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic enum Custom {\n\n    CUSTOM_COMMAND\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomCommand.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.api.request.Regex;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.service.helper.LocalRequest;\n\n/**\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class CustomCommand implements Callable<Boolean> {\n\n    private final CCC customAction;\n    private final CC commandConstant;\n    private final String keyphrase;\n    private final String responseSuccess;\n    private final String responseError;\n    private final String ttsLocale;\n    private final String vrLocale;\n    private final int action;\n\n    private String intent;\n    private boolean exactMatch;\n    private double score;\n    private String utterance;\n    private Algorithm algorithm;\n    private Regex regex;\n    private String regularExpression;\n    private String extraText;\n    private String extraText2;\n    private String serialised;\n\n    /**\n     * Constructor\n     *\n     * @param customAction    the {@link CCC}\n     * @param commandConstant the {@link CC}\n     * @param keyphrase       the phrase to trigger the command\n     * @param responseSuccess the utterance if the command execution failed\n     * @param responseError   the utterance if the command execution was successful\n     * @param ttsLocale       the String representation of the Text to Speech {@link java.util.Locale}\n     * @param vrLocale        the String representation of the Voice Recognition {@link java.util.Locale}\n     * @param action          one of {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     *                        or {@link LocalRequest#ACTION_SPEAK_ONLY}\n     */\n    public CustomCommand(@NonNull final CCC customAction, @NonNull final CC commandConstant,\n                         @NonNull final String keyphrase, @NonNull final String responseSuccess,\n                         @NonNull final String responseError, @NonNull final String ttsLocale,\n                         @NonNull final String vrLocale, final int action) {\n\n        this.customAction = customAction;\n        this.commandConstant = commandConstant;\n        this.keyphrase = keyphrase;\n        this.responseError = responseError;\n        this.responseSuccess = responseSuccess;\n        this.ttsLocale = ttsLocale;\n        this.vrLocale = vrLocale;\n        this.action = action;\n    }\n\n    public String getSerialised() {\n        return serialised;\n    }\n\n    public void setSerialised(@NonNull final String serialised) {\n        this.serialised = serialised;\n    }\n\n    public String getIntent() {\n        return intent;\n    }\n\n    public void setIntent(@NonNull final String intent) {\n        this.intent = intent;\n    }\n\n    public int getAction() {\n        return action;\n    }\n\n    public Algorithm getAlgorithm() {\n        return algorithm;\n    }\n\n    public void setAlgorithm(@NonNull final Algorithm algorithm) {\n        this.algorithm = algorithm;\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public void setScore(final double score) {\n        this.score = score;\n    }\n\n    public boolean isExactMatch() {\n        return exactMatch;\n    }\n\n    public void setExactMatch(final boolean exactMatch) {\n        this.exactMatch = exactMatch;\n    }\n\n    public CCC getCustomAction() {\n        return customAction;\n    }\n\n    public CC getCommandConstant() {\n        return commandConstant;\n    }\n\n    public String getKeyphrase() {\n        return keyphrase;\n    }\n\n    public String getResponseError() {\n        return responseError;\n    }\n\n    public String getResponseSuccess() {\n        return responseSuccess;\n    }\n\n    public String getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public String getVRLocale() {\n        return vrLocale;\n    }\n\n    public Regex getRegex() {\n        return regex == null ? Regex.MATCHES : regex;\n    }\n\n    public void setRegex(@NonNull final Regex regex) {\n        this.regex = regex;\n    }\n\n    public String getRegularExpression() {\n        return regularExpression == null ? \"\" : regularExpression;\n    }\n\n    public void setRegularExpression(@NonNull final String regularExpression) {\n        this.regularExpression = regularExpression;\n    }\n\n    public String getExtraText() {\n        return extraText;\n    }\n\n    public void setExtraText(@NonNull final String extraText) {\n        this.extraText = extraText;\n    }\n\n    public String getExtraText2() {\n        return extraText2;\n    }\n\n    public void setExtraText2(@NonNull final String extraText2) {\n        this.extraText2 = extraText2;\n    }\n\n    /**\n     * Computes a result, or throws an exception if unable to do so.\n     *\n     * @return computed result\n     * @throws Exception if unable to compute a result\n     */\n    @Override\n    public Boolean call() throws Exception {\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomCommandContainer.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.support.annotation.NonNull;\n\nimport java.io.Serializable;\n\nimport ai.saiy.android.api.request.Regex;\n\n/**\n * Created by benrandall76@gmail.com on 21/04/2016.\n */\npublic class CustomCommandContainer implements Serializable {\n\n    private static final long serialVersionUID = -1806338054844964403L;\n\n    private final String keyphrase;\n    private final long rowId;\n    private final String serialised;\n    private double score;\n    private boolean exactMatch;\n    private String utterance;\n    private final Regex regex;\n\n    /**\n     * Constructor\n     * <p/>\n     * Container to hold the relevant {@link CustomCommand} data to analyse during matching.\n     *\n     * @param rowId      the {@link ai.saiy.android.database.DBCustomCommand} row\n     * @param keyphrase  the custom phrase\n     * @param regex      the regular expression {@link ai.saiy.android.api.request.Regex}\n     * @param serialised the serialised {@link CustomCommand}\n     */\n    public CustomCommandContainer(final long rowId, @NonNull final String keyphrase, @NonNull final String regex,\n                                  @NonNull final String serialised) {\n        this.keyphrase = keyphrase;\n        this.rowId = rowId;\n        this.serialised = serialised;\n        this.regex = Regex.getRegex(regex);\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    public boolean isExactMatch() {\n        return exactMatch;\n    }\n\n    public void setExactMatch(final boolean exactMatch) {\n        this.exactMatch = exactMatch;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public void setScore(final double score) {\n        this.score = score;\n    }\n\n    public String getKeyphrase() {\n        return keyphrase;\n    }\n\n    public long getRowId() {\n        return rowId;\n    }\n\n    public String getSerialised() {\n        return serialised;\n    }\n\n    public Regex getRegex() {\n        return regex;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomCommandHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Pair;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerHelper;\nimport ai.saiy.android.algorithms.distance.levenshtein.LevenshteinHelper;\nimport ai.saiy.android.algorithms.doublemetaphone.DoubleMetaphoneHelper;\nimport ai.saiy.android.algorithms.fuzzy.FuzzyHelper;\nimport ai.saiy.android.algorithms.metaphone.MetaphoneHelper;\nimport ai.saiy.android.algorithms.mongeelkan.MongeElkanHelper;\nimport ai.saiy.android.algorithms.needlemanwunch.NeedlemanWunschHelper;\nimport ai.saiy.android.algorithms.regex.ContainsHelper;\nimport ai.saiy.android.algorithms.regex.CustomHelper;\nimport ai.saiy.android.algorithms.regex.EndsWithHelper;\nimport ai.saiy.android.algorithms.regex.StartsWithHelper;\nimport ai.saiy.android.algorithms.soundex.SoundexHelper;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.database.DBCustomCommand;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\n\nimport static ai.saiy.android.custom.CCC.CUSTOM_INTENT_SERVICE;\n\n/**\n * Class to extract any custom commands the user has created from {@link DBCustomCommand} and compare\n * with the voice data to see if we have any matches.\n * <p/>\n * Users often create weird and wonderful commands, using 'unnatural' phrases and therefore the\n * successful detection rate by the voice recognition provider can be lower.\n * <p/>\n * Due to this, we need to apply some String matching {@link ai.saiy.android.algorithms} either by\n * their distance or phonetics.\n * <p/>\n * The default threshold each algorithm applies needs to be selected carefully to avoid false positives.\n * Additionally, we have to allow advanced users to alter these thresholds - a necessary evil\n * when false positives are a common occurrence for them. One threshold does unfortunately not\n * suit all...\n * <p/>\n * Before any algorithm is applied, we need to check it is suitable for the {@link SupportedLanguage}\n * <p/>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class CustomCommandHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = CustomCommandHelper.class.getSimpleName();\n\n    private static final long THREADS_TIMEOUT = 500L;\n\n    private static final Object lock = new Object();\n\n    private CustomCommand customCommand = null;\n\n    /**\n     * @param ctx       the application context\n     * @param voiceData the array of recognition results\n     * @param sl        the {@link SupportedLanguage}\n     * @return true if a {@link CustomCommand} is detected. False otherwise\n     */\n    public boolean isCustomCommand(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData,\n                                   @NonNull final SupportedLanguage sl, @NonNull final ArrayList<CustomCommandContainer> cccArray) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n        }\n\n        final long then = System.nanoTime();\n        final Locale loc = sl.getLocale();\n\n        if (!UtilsList.notNaked(cccArray)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no custom commands\");\n                MyLog.getElapsed(CustomCommandHelper.class.getSimpleName(), then);\n            }\n            return false;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have commands: \" + cccArray.size());\n            }\n        }\n\n        final ArrayList<CustomCommandContainer> cccArrayStartWith = new ArrayList<>();\n        final ArrayList<CustomCommandContainer> cccArrayEndsWith = new ArrayList<>();\n        final ArrayList<CustomCommandContainer> cccArrayContains = new ArrayList<>();\n        final ArrayList<CustomCommandContainer> cccArrayCustom = new ArrayList<>();\n\n        final ListIterator<CustomCommandContainer> itr = cccArray.listIterator();\n\n        CustomCommandContainer container;\n        while (itr.hasNext()) {\n            container = itr.next();\n\n            switch (container.getRegex()) {\n\n                case MATCHES:\n                    break;\n                case STARTS_WITH:\n                    cccArrayStartWith.add(container);\n                    itr.remove();\n                    break;\n                case ENDS_WITH:\n                    cccArrayEndsWith.add(container);\n                    itr.remove();\n                    break;\n                case CONTAINS:\n                    cccArrayContains.add(container);\n                    itr.remove();\n                    break;\n                case CUSTOM:\n                    cccArrayCustom.add(container);\n                    itr.remove();\n                    break;\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"algorithmic commands: \" + cccArray.size());\n            MyLog.i(CLS_NAME, \"regex commands: \" + (cccArrayStartWith.size() + cccArrayEndsWith.size()\n                    + cccArrayContains.size() + cccArrayCustom.size()));\n        }\n\n        final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n        final Algorithm[] algorithms = Algorithm.getAlgorithms(ctx, sl);\n\n        final int callableListSize;\n\n        if (UtilsList.notNaked(cccArray)) {\n            callableListSize = algorithms.length + cccArrayStartWith.size() + cccArrayEndsWith.size()\n                    + cccArrayContains.size() + cccArrayCustom.size();\n        } else {\n            callableListSize = cccArrayStartWith.size() + cccArrayEndsWith.size()\n                    + cccArrayContains.size() + cccArrayCustom.size();\n        }\n\n        final List<Callable<Object>> callableList = new ArrayList<>(callableListSize);\n\n        if (UtilsList.notNaked(cccArrayStartWith)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have starts with: \" + cccArrayStartWith.size());\n            }\n\n            callableList.add(new StartsWithHelper(cccArrayStartWith, voiceData, loc));\n        }\n\n        if (UtilsList.notNaked(cccArrayEndsWith)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have ends with: \" + cccArrayEndsWith.size());\n            }\n\n            callableList.add(new EndsWithHelper(cccArrayEndsWith, voiceData, loc));\n        }\n\n        if (UtilsList.notNaked(cccArrayContains)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have contains: \" + cccArrayContains.size());\n            }\n\n            callableList.add(new ContainsHelper(cccArrayContains, voiceData, loc));\n        }\n\n        if (UtilsList.notNaked(cccArrayCustom)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have contains: \" + cccArrayCustom.size());\n            }\n\n            callableList.add(new CustomHelper(cccArrayCustom, voiceData, loc));\n        }\n\n        if (UtilsList.notNaked(cccArray)) {\n\n            for (final Algorithm algorithm : algorithms) {\n\n                switch (algorithm) {\n\n                    case JARO_WINKLER:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: JARO_WINKLER\");\n                        }\n\n                        callableList.add(new JaroWinklerHelper(ctx, cccArray, voiceData, loc));\n                        break;\n                    case LEVENSHTEIN:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: LEVENSHTEIN\");\n                        }\n\n                        callableList.add(new LevenshteinHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case SOUNDEX:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: SOUNDEX\");\n                        }\n\n                        callableList.add(new SoundexHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case METAPHONE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: METAPHONE\");\n                        }\n\n                        callableList.add(new MetaphoneHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case DOUBLE_METAPHONE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: DOUBLE_METAPHONE\");\n                        }\n\n                        callableList.add(new DoubleMetaphoneHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case FUZZY:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: FUZZY\");\n                        }\n\n                        callableList.add(new FuzzyHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case NEEDLEMAN_WUNCH:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: NEEDLEMAN_WUNCH\");\n                        }\n\n                        callableList.add(new NeedlemanWunschHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                    case MONGE_ELKAN:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Running: MONGE_ELKAN\");\n                        }\n\n                        callableList.add(new MongeElkanHelper(ctx, cccArray,\n                                voiceData, loc));\n                        break;\n                }\n            }\n        }\n\n\n        final ArrayList<CustomCommand> customCommandArray = new ArrayList<>();\n\n        try {\n\n            final List<Future<Object>> futures = executorService.invokeAll(callableList,\n                    THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n            for (final Future<Object> future : futures) {\n                customCommandArray.add((CustomCommand) future.get());\n            }\n\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final CancellationException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: CancellationException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                e.printStackTrace();\n            }\n        } finally {\n            executorService.shutdown();\n        }\n\n        if (!customCommandArray.isEmpty()) {\n            customCommandArray.removeAll(Collections.<CustomCommand>singleton(null));\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"algorithms returned \" + customCommandArray.size() + \" matches\");\n                for (final CustomCommand c : customCommandArray) {\n                    MyLog.i(CLS_NAME, \"Potentials: \" + c.getAlgorithm().name() + \" ~ \"\n                            + c.getKeyphrase() + \" ~ \" + c.getUtterance() + \" ~ \"\n                            + c.getScore());\n                }\n            }\n\n            CustomCommand cc;\n            final ListIterator<CustomCommand> itrCC = customCommandArray.listIterator();\n\n            while (itrCC.hasNext()) {\n                cc = itrCC.next();\n                if (cc == null) {\n                    itrCC.remove();\n                } else {\n\n                    if (cc.isExactMatch()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"exact match: \" + cc.getAlgorithm().name() + \" ~ \"\n                                    + cc.getKeyphrase() + \" ~ \" + cc.getUtterance() + \" ~ \"\n                                    + cc.getScore());\n                        }\n                        customCommand = cc;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if (customCommand == null && !customCommandArray.isEmpty()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"No exact match, but have \" + customCommandArray.size() + \" commands\");\n                for (final CustomCommand c : customCommandArray) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(customCommandArray, new Comparator<CustomCommand>() {\n                @Override\n                public int compare(final CustomCommand c1, final CustomCommand c2) {\n                    return Double.compare(c2.getScore(), c1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final CustomCommand c : customCommandArray) {\n                    MyLog.i(CLS_NAME, \"after order: \" + c.getKeyphrase() + \" ~ \" + c.getScore());\n                }\n            }\n\n            customCommand = customCommandArray.get(0);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"match: \" + customCommand.getAlgorithm().name() + \" ~ \"\n                        + customCommand.getKeyphrase() + \" ~ \" + customCommand.getUtterance()\n                        + \" ~ \" + customCommand.getScore());\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CustomCommandHelper.class.getSimpleName(), then);\n        }\n\n        return customCommand != null;\n    }\n\n    /**\n     * Extract all of the user's serialised {@link CustomCommand} from {@link DBCustomCommand} into\n     * an array of {@link CustomCommandContainer}\n     *\n     * @param ctx the application context\n     * @return an array of {@link CustomCommandContainer}\n     */\n    public ArrayList<CustomCommandContainer> getCustomCommands(@NonNull final Context ctx) {\n\n        synchronized (lock) {\n\n            final ArrayList<CustomCommandContainer> customCommandContainerArray;\n            final DBCustomCommand dbCustomCommand = new DBCustomCommand(ctx);\n\n            if (dbCustomCommand.databaseExists()) {\n                customCommandContainerArray = dbCustomCommand.getKeyphrases();\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"databaseExists: true: with \" + customCommandContainerArray.size() + \" commands.\");\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"databaseExists: false\");\n                }\n\n                customCommandContainerArray = new ArrayList<>();\n            }\n\n            return customCommandContainerArray;\n        }\n    }\n\n    /**\n     * Get any resolved {@link CustomCommand}. This object can be null.\n     *\n     * @return the {@link CustomCommand} or the null object\n     */\n\n    public CustomCommand getCommand() {\n        return this.customCommand;\n    }\n\n    public void setCustomCommand(@Nullable final CustomCommand customCommand) {\n        this.customCommand = customCommand;\n    }\n\n    /**\n     * Get the {@link CC} that the custom command dictates\n     *\n     * @return the extracted {@link CC}\n     */\n    public CC getCommandConstant() {\n        return this.customCommand.getCommandConstant();\n    }\n\n\n    /**\n     * Insert a new {@link CustomCommand} in the {@link DBCustomCommand} synchronising with a basic\n     * lock object in a vain attempt to prevent concurrency issues.\n     *\n     * @param ctx           the application context\n     * @param customCommand to be set\n     * @return true if the insertion was successful\n     */\n    public static Pair<Boolean, Long> setCommand(@NonNull final Context ctx, @NonNull final CustomCommand customCommand,\n                                                 final long rowId) {\n\n        synchronized (lock) {\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final String gsonString = gson.toJson(customCommand);\n\n            final DBCustomCommand dbCustomCommand = new DBCustomCommand(ctx);\n\n            final Pair<Boolean, Long> duplicatePair;\n            if (rowId > -1) {\n                duplicatePair = new Pair<>(true, rowId);\n            } else {\n                duplicatePair = commandExists(dbCustomCommand, customCommand);\n            }\n\n            return dbCustomCommand.insertPopulatedRow(customCommand.getKeyphrase(),\n                    customCommand.getRegex(), gsonString, duplicatePair.first, duplicatePair.second);\n        }\n    }\n\n    public static void deleteCustomCommand(@NonNull final Context ctx, final long rowId) {\n\n        synchronized (lock) {\n            final DBCustomCommand dbCustomCommand = new DBCustomCommand(ctx);\n            dbCustomCommand.deleteRow(rowId);\n        }\n\n    }\n\n    /**\n     * Check if the keyphrase for the custom command already exists\n     *\n     * @param dbCustomCommand the {@link DBCustomCommand}\n     * @param customCommand   the prepared {@link CustomCommand}\n     * @return true if the keyphrase exists, false otherwise\n     */\n    private static Pair<Boolean, Long> commandExists(@NonNull final DBCustomCommand dbCustomCommand,\n                                                     @NonNull final CustomCommand customCommand) {\n\n\n        if (dbCustomCommand.databaseExists()) {\n\n            final ArrayList<CustomCommandContainer> customCommandContainerArray = dbCustomCommand.getKeyphrases();\n\n            if (UtilsList.notNaked(customCommandContainerArray)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"have commands\");\n                }\n\n                final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(\n                        UtilsLocale.stringToLocale(customCommand.getVRLocale()));\n\n                final Locale loc = sl.getLocale();\n                for (final CustomCommandContainer ccc : customCommandContainerArray) {\n                    if (ccc.getKeyphrase().toLowerCase(loc).trim().matches(\n                            customCommand.getKeyphrase().toLowerCase(loc).trim())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"keyphrase matched: \" + ccc.getKeyphrase()\n                                    + \" ~ \" + customCommand.getKeyphrase());\n                        }\n                        return new Pair<>(true, ccc.getRowId());\n                    }\n                }\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"command is not a duplicate\");\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"no commands\");\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"databaseExists: false\");\n            }\n        }\n\n        return new Pair<>(false, -1L);\n    }\n\n\n    /**\n     * Check if the keyphrase for the custom command already exists\n     *\n     * @param ctx           the application context\n     * @param customCommand the prepared {@link CustomCommand}\n     * @return true if the keyphrase exists, false otherwise\n     */\n    public boolean commandExists(@NonNull final Context ctx, @NonNull final CustomCommand customCommand) {\n\n        final ArrayList<CustomCommandContainer> customCommandContainerArray = getCustomCommands(ctx);\n\n        if (UtilsList.notNaked(customCommandContainerArray)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"have commands\");\n            }\n\n            final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(\n                    UtilsLocale.stringToLocale(customCommand.getVRLocale()));\n\n            final Locale loc = sl.getLocale();\n            for (final CustomCommandContainer ccc : customCommandContainerArray) {\n                if (ccc.getKeyphrase().toLowerCase(loc).trim().matches(\n                        customCommand.getKeyphrase().toLowerCase(loc).trim())) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"keyphrase matched: \" + ccc.getKeyphrase()\n                                + \" ~ \" + customCommand.getKeyphrase());\n                    }\n                    return true;\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"command is not a duplicate\");\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"no commands\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Delete all of the {@link CustomCommand} in the {@link DBCustomCommand} synchronising with a basic\n     * lock object in a vain attempt to prevent concurrency issues.\n     *\n     * @param ctx the application context\n     * @return true if the process succeeded\n     */\n    public static boolean deleteAllCommands(@NonNull final Context ctx) {\n\n        synchronized (lock) {\n            final DBCustomCommand dbCustomCommand = new DBCustomCommand(ctx);\n            return dbCustomCommand.deleteTable();\n        }\n    }\n\n    /**\n     * Delete all of the {@link CustomCommand} for the given package names.\n     *\n     * @param ctx          the application context\n     * @param packageNames the package names for which commands should be deleted\n     */\n    public static void deleteCommandsForPackage(@NonNull final Context ctx,\n                                                @NonNull final ArrayList<String> packageNames) {\n\n        synchronized (lock) {\n\n            final long then = System.nanoTime();\n\n            final DBCustomCommand dbCustomCommand = new DBCustomCommand(ctx);\n\n            if (dbCustomCommand.databaseExists()) {\n                final ArrayList<CustomCommandContainer> customCommandContainerArray = dbCustomCommand.getKeyphrases();\n\n                final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n                final ArrayList<Long> rowIds = new ArrayList<>();\n                CustomCommand customCommand;\n                for (final CustomCommandContainer container : customCommandContainerArray) {\n\n                    Intent remoteIntent = null;\n\n                    try {\n                        customCommand = gson.fromJson(container.getSerialised(), CustomCommand.class);\n\n                        if (customCommand.getCustomAction() == CUSTOM_INTENT_SERVICE) {\n                            remoteIntent = Intent.parseUri(customCommand.getIntent(), 0);\n                        }\n                    } catch (final URISyntaxException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"remoteIntent.parseUri: URISyntaxException\");\n                            e.printStackTrace();\n                        }\n                    } catch (final JsonSyntaxException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"gson.fromJson: JsonSyntaxException\");\n                            e.printStackTrace();\n                        }\n                    }\n\n                    if (remoteIntent != null && packageNames.contains(remoteIntent.getPackage())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"adding \" + remoteIntent.getPackage() + \" to be deleted\");\n                        }\n\n                        rowIds.add(container.getRowId());\n                    }\n                }\n\n                if (!rowIds.isEmpty()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"deleting \" + rowIds.size() + \" commands\");\n                    }\n                    dbCustomCommand.deleteRows(rowIds);\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"no commands for packages\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"databaseExists: false\");\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.getElapsed(\"deleteCommandsForPackage\", then);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.database.callable.DBCustomCommandCallable;\nimport ai.saiy.android.ui.containers.ContainerCustomisation;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class CustomHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = CustomHelper.class.getSimpleName();\n\n    private static final long THREADS_TIMEOUT = 1000L;\n\n    public static final int CHEVRON_RESOURCE_ID = R.drawable.chevron;\n    public static final int CUSTOM_COMMAND_RESOURCE_ID = R.drawable.ic_account_switch;\n\n    private static final Object lock = new Object();\n\n    public CustomHelperHolder getCustomisationHolder(@NonNull final Context ctx) {\n\n        synchronized (lock) {\n\n            final long then = System.nanoTime();\n\n            final List<Callable<ArrayList<Object>>> callableList = new ArrayList<>();\n\n            callableList.add(new DBCustomCommandCallable(ctx));\n\n            final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n            final ArrayList<Object> objectArray = new ArrayList<>();\n\n            try {\n\n                final List<Future<ArrayList<Object>>> futures = executorService.invokeAll(callableList,\n                        THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n                for (final Future<ArrayList<Object>> future : futures) {\n                    objectArray.add(future.get());\n                }\n\n            } catch (final ExecutionException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                    e.printStackTrace();\n                }\n            } catch (final CancellationException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: CancellationException\");\n                    e.printStackTrace();\n                }\n            } catch (final InterruptedException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                    e.printStackTrace();\n                }\n            } finally {\n                executorService.shutdown();\n            }\n\n            if (DEBUG) {\n                MyLog.getElapsed(CLS_NAME, \"callables\", then);\n            }\n\n            final CustomHelperHolder holder;\n\n            if (UtilsList.notNaked(objectArray)) {\n                holder = completeCustomisationHolder(ctx, objectArray);\n            } else {\n                holder = new CustomHelperHolder();\n            }\n\n            if (DEBUG) {\n                MyLog.getElapsed(CLS_NAME, then);\n            }\n\n            return holder;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private CustomHelperHolder completeCustomisationHolder(@NonNull final Context ctx,\n                                                           @NonNull final ArrayList<Object> objectArray) {\n\n        final CustomHelperHolder holder = new CustomHelperHolder();\n\n        Object object;\n        ArrayList<Object> tempArray;\n        final int size = objectArray.size();\n\n        for (int i = 0; i < size; i++) {\n\n            tempArray = (ArrayList<Object>) objectArray.get(i);\n\n            if (UtilsList.notNaked(tempArray)) {\n\n                object = tempArray.get(0);\n\n                if (object instanceof CustomCommandContainer) {\n                    holder.setCustomCommandArray((ArrayList) tempArray);\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"instanceof default\");\n                    }\n                }\n            }\n        }\n\n        return holder;\n    }\n\n    public ArrayList<ContainerCustomisation> getCustomisations(@NonNull final Context ctx) {\n\n        synchronized (lock) {\n\n            final long then = System.nanoTime();\n\n            final List<Callable<ArrayList<Object>>> callableList = new ArrayList<>();\n            final ArrayList<ContainerCustomisation> containerCustomisationArray = new ArrayList<>();\n\n            callableList.add(new DBCustomCommandCallable(ctx));\n\n            final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n            final ArrayList<Object> objectArray = new ArrayList<>();\n\n            try {\n\n                final List<Future<ArrayList<Object>>> futures = executorService.invokeAll(callableList,\n                        THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n                for (final Future<ArrayList<Object>> future : futures) {\n                    objectArray.addAll(future.get());\n                }\n\n            } catch (final ExecutionException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                    e.printStackTrace();\n                }\n            } catch (final CancellationException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: CancellationException\");\n                    e.printStackTrace();\n                }\n            } catch (final InterruptedException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                    e.printStackTrace();\n                }\n            } finally {\n                executorService.shutdown();\n            }\n\n            if (DEBUG) {\n                MyLog.getElapsed(CLS_NAME, \"callables\", then);\n            }\n\n            if (UtilsList.notNaked(objectArray)) {\n\n                final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n\n                Object object;\n                ContainerCustomisation containerCustomisation;\n                CustomCommandContainer customCommandContainer;\n                CustomCommand customCommand;\n                String serialised;\n                String extra = null;\n                String label;\n                Intent remoteIntent = null;\n\n                final int size = objectArray.size();\n\n                for (int i = 0; i < size; i++) {\n\n                    object = objectArray.get(i);\n\n                    if (object instanceof CustomCommandContainer) {\n\n                        customCommandContainer = (CustomCommandContainer) object;\n\n                        serialised = customCommandContainer.getSerialised();\n                        customCommand = gson.fromJson(serialised, CustomCommand.class);\n\n                        if (customCommand.getCustomAction() == CCC.CUSTOM_INTENT_SERVICE) {\n\n                            try {\n                                remoteIntent = Intent.parseUri(customCommand.getIntent(), 0);\n                            } catch (final URISyntaxException e) {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"remoteIntent.parseUri: URISyntaxException\");\n                                    e.printStackTrace();\n                                }\n                            } catch (final NullPointerException e) {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"remoteIntent.parseUri: NullPointerException\");\n                                    e.printStackTrace();\n                                }\n                            }\n\n                            if (remoteIntent != null) {\n\n                                final Pair<Boolean, String> pair = UtilsApplication.getAppNameFromPackage(ctx,\n                                        remoteIntent.getPackage());\n\n                                if (pair.first) {\n                                    extra = pair.second;\n                                } else {\n                                    extra = remoteIntent.getPackage();\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"remoteIntent null\");\n                                }\n                                extra = ctx.getString(R.string.an_unknown_application);\n                            }\n\n                            label = customCommand.getCustomAction().name();\n\n                        } else {\n                            label = customCommand.getCustomAction().name();\n                        }\n\n                        containerCustomisation = new ContainerCustomisation(Custom.CUSTOM_COMMAND,\n                                serialised,\n                                customCommandContainer.getKeyphrase(),\n                                label,\n                                customCommandContainer.getRowId(),\n                                CUSTOM_COMMAND_RESOURCE_ID, CHEVRON_RESOURCE_ID);\n\n                        containerCustomisationArray.add(containerCustomisation);\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"instanceof default\");\n                        }\n                    }\n                }\n            }\n\n\n            if (DEBUG) {\n                MyLog.getElapsed(CLS_NAME, then);\n            }\n\n            return containerCustomisationArray;\n\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomHelperHolder.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class CustomHelperHolder {\n\n    private ArrayList<CustomCommandContainer> customCommandArray;\n\n    public CustomHelperHolder() {\n    }\n\n    public CustomHelperHolder(@NonNull final ArrayList<CustomCommandContainer> customCommandArray) {\n        this.customCommandArray = customCommandArray;\n    }\n\n    public void setCustomCommandArray(@NonNull final ArrayList<CustomCommandContainer> customCommandArray) {\n        this.customCommandArray = customCommandArray;\n    }\n\n    public ArrayList<CustomCommandContainer> getCustomCommandArray() {\n        return customCommandArray != null ? customCommandArray : new ArrayList<CustomCommandContainer>();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/custom/CustomResolver.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.custom;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class CustomResolver {\n\n    private boolean isCustom;\n    private ArrayList<String> voiceData;\n    private CustomCommandHelper customCommandHelper;\n\n    public CustomResolver() {\n    }\n\n    public CustomResolver(final boolean isCustom, @NonNull final ArrayList<String> voiceData,\n                          final CustomCommandHelper customCommandHelper) {\n        this.isCustom = isCustom;\n        this.voiceData = voiceData;\n        this.customCommandHelper = customCommandHelper;\n    }\n\n\n    public CustomCommandHelper getCustomCommandHelper() {\n        return customCommandHelper;\n    }\n\n    public void setCustomCommandHelper(@NonNull final CustomCommandHelper customCommandHelper) {\n        this.customCommandHelper = customCommandHelper;\n    }\n\n    public boolean isCustom() {\n        return isCustom;\n    }\n\n    public void setCustom(final boolean custom) {\n        isCustom = custom;\n    }\n\n    public ArrayList<String> getVoiceData() {\n        return voiceData;\n    }\n\n    public void setVoiceData(@NonNull final ArrayList<String> voiceData) {\n        this.voiceData = voiceData;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/database/DBCustomCommand.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.database;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.SQLException;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.support.annotation.NonNull;\nimport android.text.TextUtils;\nimport android.util.Pair;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\n\nimport ai.saiy.android.api.request.Regex;\nimport ai.saiy.android.custom.CustomCommand;\nimport ai.saiy.android.custom.CustomCommandContainer;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class DBCustomCommand extends SQLiteOpenHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = DBCustomCommand.class.getSimpleName();\n\n    private static final String DATABASE_NAME = \"customCommands.db\";\n    private final String DATABASE_PATH;\n    public static final String TABLE_CUSTOM_COMMANDS = \"custom_commands\";\n    private static final int DATABASE_VERSION = 1;\n\n    public static final String COLUMN_ID = \"_id\";\n    public static final String COLUMN_KEYPHRASE = \"keyphrase\";\n    public static final String COLUMN_REGEX = \"regex\";\n    public static final String COLUMN_SERIALISED = \"serialised\";\n\n    private static final String[] ALL_COLUMNS = {COLUMN_ID, COLUMN_KEYPHRASE, COLUMN_REGEX, COLUMN_SERIALISED};\n\n    private static final String DATABASE_CREATE = \"create table \" + TABLE_CUSTOM_COMMANDS\n            + \"(\" + COLUMN_ID + \" integer primary key autoincrement, \"\n            + COLUMN_KEYPHRASE + \" text not null, \"\n            + COLUMN_REGEX + \" text not null, \"\n            + COLUMN_SERIALISED + \" text not null);\";\n\n    private SQLiteDatabase database;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public DBCustomCommand(@NonNull final Context mContext) {\n        super(mContext, DATABASE_NAME, null, DATABASE_VERSION);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Constructor\");\n        }\n        DATABASE_PATH = mContext.getDatabasePath(DATABASE_NAME).getPath();\n    }\n\n    /**\n     * Open the database\n     *\n     * @throws SQLException\n     */\n    public void open() throws SQLException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"open\");\n        }\n        database = this.getWritableDatabase();\n    }\n\n    @Override\n    public void close() throws SQLException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"close\");\n        }\n        database.close();\n    }\n\n    @Override\n    public void onCreate(final SQLiteDatabase dataBase) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        dataBase.execSQL(DATABASE_CREATE);\n    }\n\n    @Override\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onUpgrade\");\n            MyLog.w(CLS_NAME, \"Upgrading database from version \" + oldVersion + \" to \" + newVersion\n                    + \", which will destroy all old data\");\n        }\n        db.execSQL(\"DROP TABLE IF EXISTS \" + TABLE_CUSTOM_COMMANDS);\n        onCreate(db);\n    }\n\n    /**\n     * Check if the database exists\n     *\n     * @return true if it exists. False otherwise\n     */\n    public boolean databaseExists() {\n        return new File(DATABASE_PATH).exists();\n    }\n\n    /**\n     * Delete all entries in the current table\n     */\n    public boolean deleteTable() {\n\n        try {\n\n            open();\n\n            if (database.isOpen()) {\n                database.delete(TABLE_CUSTOM_COMMANDS, null, null);\n                return true;\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: database not open\");\n                }\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"deleteTable: finally closing\");\n                    }\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Insert a row, separating the command phrase and serialised class.\n     *\n     * @param keyphrase   the keyphrase\n     * @param regex       the regular expression to be used one of {@link Regex}\n     * @param serialised  the serialised class\n     * @param isDuplicate true if a command is being replaced\n     * @param rowId       the row id of the command to be replaced\n     * @return true if the insertion was successful. False otherwise\n     */\n    public Pair<Boolean, Long> insertPopulatedRow(@NonNull final String keyphrase, @NonNull final Regex regex,\n                                                  @NonNull final String serialised, final boolean isDuplicate,\n                                                  final long rowId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"insertPopulatedRow: duplicate: \" + isDuplicate + \" \" + rowId);\n        }\n\n        boolean success = false;\n        long insertId = -1;\n\n        try {\n\n            open();\n\n            if (database.isOpen()) {\n\n                final ContentValues values = new ContentValues();\n                values.put(COLUMN_KEYPHRASE, keyphrase);\n                values.put(COLUMN_REGEX, regex.name());\n                values.put(COLUMN_SERIALISED, serialised);\n\n                insertId = database.insert(TABLE_CUSTOM_COMMANDS, null, values);\n                final Cursor cursor = database.query(TABLE_CUSTOM_COMMANDS, ALL_COLUMNS,\n                        COLUMN_ID + \" = \" + insertId, null, null,\n                        null, null);\n                cursor.moveToFirst();\n                cursor.close();\n                success = true;\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"insertPopulatedRow: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"insertPopulatedRow: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"insertPopulatedRow: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"insertPopulatedRow: finally closing\");\n                    }\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertPopulatedRow: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertPopulatedRow: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertPopulatedRow: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (isDuplicate) {\n            deleteRow(rowId);\n        }\n\n        return new Pair<>(success, insertId);\n\n    }\n\n    /**\n     * Delete rows from the database\n     *\n     * @param rowIDs the row identifiers\n     */\n    public void deleteRows(final ArrayList<Long> rowIDs) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"deleteRows\");\n        }\n\n        try {\n            open();\n            if (database.isOpen()) {\n\n                final String where = COLUMN_ID + \" IN (\"\n                        + TextUtils.join(\",\", Collections.nCopies(rowIDs.size(), \"?\")) + \")\";\n\n                final String[] rowIdArray = new String[rowIDs.size()];\n\n                for (int i = 0; i < rowIDs.size(); i++) {\n                    rowIdArray[i] = String.valueOf(rowIDs.get(i));\n                }\n\n                final int deleted = database.delete(TABLE_CUSTOM_COMMANDS, where, rowIdArray);\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"deleteRow count: \" + deleted);\n                }\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRows: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRows: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRows: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRows: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRows: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRows: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Delete a row from the database\n     *\n     * @param rowID the row identifier\n     */\n    public void deleteRow(final long rowID) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"deleteRow: \" + rowID);\n        }\n\n        try {\n            open();\n            if (database.isOpen()) {\n                final int deleteResult = database.delete(TABLE_CUSTOM_COMMANDS, COLUMN_ID + \"=?\",\n                        new String[]{String.valueOf(rowID)});\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"deleteResult: \" + deleteResult);\n                }\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRow: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRow: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteRow: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRow: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRow: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteRow: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Get all keyphrases from the database, including the corresponding row identifier and\n     * serialised command data.\n     *\n     * @return the {@link Pair} keyphrase and row identifier\n     */\n    public ArrayList<CustomCommandContainer> getKeyphrases() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getKeyphrases\");\n        }\n\n        final ArrayList<CustomCommandContainer> keyPhrases = new ArrayList<>();\n\n        try {\n\n            open();\n\n            if (database.isOpen()) {\n\n                final Cursor cursor = database.query(TABLE_CUSTOM_COMMANDS,\n                        ALL_COLUMNS, null, null, null, null, null);\n\n                cursor.moveToFirst();\n                while (!cursor.isAfterLast()) {\n                    keyPhrases.add(new CustomCommandContainer(cursor.getLong(0), cursor.getString(1),\n                            cursor.getString(2), cursor.getString(3)));\n                    cursor.moveToNext();\n                }\n\n                cursor.close();\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getKeyphrases: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getKeyphrases: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getKeyphrases: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getKeyphrases: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getKeyphrases: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getKeyphrases: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return keyPhrases;\n    }\n\n    /**\n     * Get the serialisable class\n     *\n     * @param rowId the row identifier\n     * @return the serialised {@link CustomCommand}\n     */\n    public String getSerialisable(final long rowId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSerialisable\");\n        }\n\n        String serialisable = null;\n\n        try {\n            open();\n\n            if (database.isOpen()) {\n                final Cursor cursor = database.query(TABLE_CUSTOM_COMMANDS, new String[]{COLUMN_SERIALISED}, COLUMN_ID + \"=?\",\n                        new String[]{String.valueOf(rowId)}, null, null, null);\n\n                cursor.moveToFirst();\n                if (!cursor.isAfterLast()) {\n                    serialisable = cursor.getString(0);\n                }\n\n                cursor.close();\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSerialisable: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSerialisable: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSerialisable: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            try {\n                if (database.isOpen()) {\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getSerialisable: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getSerialisable: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getSerialisable: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return serialisable;\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/database/DBSpeech.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.database;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.SQLException;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\n\nimport java.io.File;\n\nimport ai.saiy.android.cache.speech.SpeechCachePrepare;\nimport ai.saiy.android.cache.speech.SpeechCacheResult;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Database class to hold compressed audio of Text to Speech Engine utterances. Utterances stored here,\n * will be streamed via {@link android.media.AudioTrack} object, to remove the necessity of fetching\n * network synthesis and the associated latency, along with being faster than initialising a\n * {@link android.speech.tts.TextToSpeech} object.\n * <p>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class DBSpeech extends SQLiteOpenHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = DBSpeech.class.getSimpleName();\n\n    public static final long MAX_CACHE_SIZE = 20000000L;\n    private static final long MAX_UNUSED_THRESHOLD = 2600000000L;\n    private static final long VACUUM_THRESHOLD = 25L;\n\n    private static final String DATABASE_NAME = \"speech.db\";\n    private final String DATABASE_PATH;\n    private static final String TABLE_SPEECH = \"table_speech\";\n    private static final int DATABASE_VERSION = 1;\n\n    private static final String COLUMN_ID = \"_id\";\n    private static final String COLUMN_ENGINE_PACKAGE = \"engine_package\";\n    private static final String COLUMN_VOICE_NAME = \"voice_name\";\n    private static final String COLUMN_VOICE_LOCALE = \"voice_locale\";\n    private static final String COLUMN_UTTERANCE = \"utterance\";\n    private static final String COLUMN_BINARY = \"binary\";\n    private static final String COLUMN_DATE = \"last_used_date\";\n\n    private static final String[] ALL_COLUMNS = {COLUMN_ID, COLUMN_ENGINE_PACKAGE, COLUMN_VOICE_NAME,\n            COLUMN_VOICE_LOCALE, COLUMN_UTTERANCE, COLUMN_BINARY, COLUMN_DATE};\n\n    private static final String DATABASE_CREATE = \"create table \"\n            + TABLE_SPEECH\n            + \"(\" + COLUMN_ID\n            + \" integer primary key autoincrement, \"\n            + COLUMN_ENGINE_PACKAGE\n            + \" text not null, \"\n            + COLUMN_VOICE_NAME\n            + \" text not null, \"\n            + COLUMN_VOICE_LOCALE\n            + \" text not null, \"\n            + COLUMN_UTTERANCE\n            + \" text not null, \"\n            + COLUMN_BINARY\n            + \" blob not null, \"\n            + COLUMN_DATE\n            + \" integer not null);\";\n\n    private SQLiteDatabase database;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public DBSpeech(@NonNull final Context mContext) {\n        super(mContext, DATABASE_NAME, null, DATABASE_VERSION);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Constructor\");\n        }\n        DATABASE_PATH = mContext.getDatabasePath(DATABASE_NAME).getPath();\n    }\n\n    /**\n     * Open the database\n     *\n     * @throws SQLException\n     */\n    public void open() throws SQLException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"open\");\n        }\n        database = this.getWritableDatabase();\n    }\n\n    @Override\n    public void close() throws SQLException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"close\");\n        }\n        database.close();\n    }\n\n    @Override\n    public void onCreate(final SQLiteDatabase dataBase) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        dataBase.execSQL(DATABASE_CREATE);\n    }\n\n    @Override\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onUpgrade\");\n            MyLog.w(CLS_NAME, \"Upgrading database from version \" + oldVersion + \" to \" + newVersion\n                    + \", which will destroy all old data\");\n        }\n        db.execSQL(\"DROP TABLE IF EXISTS \" + TABLE_SPEECH);\n        onCreate(db);\n    }\n\n    /**\n     * Check if the database exists\n     *\n     * @return true if it exists. False otherwise\n     */\n    public boolean databaseExists() {\n        return new File(DATABASE_PATH).exists();\n    }\n\n    /**\n     * Delete all entries in the current table\n     */\n    public boolean deleteTable() {\n\n        try {\n\n            open();\n\n            if (database.isOpen()) {\n                database.delete(TABLE_SPEECH, null, null);\n                return true;\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: database not open\");\n                }\n            }\n\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteTable: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"deleteTable: finally closing\");\n                    }\n                    close();\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteTable: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Delete a given entry\n     *\n     * @param rowId the row identifier\n     * @return true if the deletion was successful\n     */\n    public boolean deleteEntry(final long rowId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"deleteEntry\");\n        }\n\n        final long then = System.nanoTime();\n\n        try {\n            open();\n            if (database.isOpen()) {\n                database.delete(TABLE_SPEECH, COLUMN_ID + \"=?\", new String[]{String.valueOf(rowId)});\n                return true;\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteEntry: database not open\");\n                }\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteEntry: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteEntry: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"deleteEntry: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"deleteEntry: finally closing\");\n                    }\n                    close();\n\n                    if (DEBUG) {\n                        MyLog.getElapsed(CLS_NAME, then);\n                    }\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteEntry: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteEntry: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"deleteEntry: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if an entry exists\n     *\n     * @param initEngine the package name of the Text to Speech Engine\n     * @param voice      the {@link android.speech.tts.Voice}\n     * @param utterance  the utterance\n     * @return true if the entry exits. False otherwise\n     */\n    public boolean entryExists(@NonNull final String initEngine, @NonNull final String voice,\n                               @NonNull final String utterance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"entryExists\");\n        }\n\n        boolean exists = false;\n        final long then = System.nanoTime();\n\n        try {\n\n            open();\n            if (database.isOpen()) {\n\n                final String whereClause = COLUMN_ENGINE_PACKAGE + \"='\" + initEngine\n                        + \"' AND \" + COLUMN_VOICE_NAME + \"='\" + voice\n                        + \"' AND \" + COLUMN_UTTERANCE + \"='\" + utterance.trim().replaceAll(\"[^a-zA-Z0-9]\", \"\") + \"'\";\n\n                final Cursor cursor = database.query(TABLE_SPEECH, new String[]{COLUMN_ID, COLUMN_ENGINE_PACKAGE,\n                                COLUMN_VOICE_NAME, COLUMN_UTTERANCE, COLUMN_BINARY}, whereClause, null, null, null,\n                        null);\n\n                if (cursor != null && cursor.getCount() > 0) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"entryExists: true\");\n                    }\n\n                    if (cursor.moveToFirst()) {\n                        exists = true;\n                    }\n\n                    cursor.close();\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \": entryExists: false\");\n                    }\n                }\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"entryExists: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"entryExists: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"entryExists: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"entryExists: finally closing\");\n                    }\n                    close();\n\n                    if (DEBUG) {\n                        MyLog.getElapsed(CLS_NAME, then);\n                    }\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"entryExists: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"entryExists: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"entryExists: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return exists;\n    }\n\n    /**\n     * Get the compressed audio\n     *\n     * @param initEngine the initialised {@link android.speech.tts.TextToSpeech} package name\n     * @param voice      the {@link android.speech.tts.Voice}\n     * @param utterance  the utterance\n     * @return a populated {@link SpeechCacheResult} or null if no entry exists\n     */\n    public SpeechCacheResult getBytes(@NonNull final String initEngine, @NonNull final String voice,\n                                      @NonNull final String utterance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getBytes\");\n        }\n\n        final long then = System.nanoTime();\n\n        byte[] compressedBytes = null;\n\n        long rowId = -1;\n\n        try {\n\n            open();\n\n            if (database.isOpen()) {\n\n                final String whereClause = COLUMN_ENGINE_PACKAGE + \"='\" + initEngine\n                        + \"' AND \" + COLUMN_VOICE_NAME + \"='\" + voice\n                        + \"' AND \" + COLUMN_UTTERANCE + \"='\" + utterance.trim().replaceAll(\"[^a-zA-Z0-9]\", \"\") + \"'\";\n\n                final Cursor cursor = database.query(TABLE_SPEECH, new String[]{COLUMN_ID, COLUMN_ENGINE_PACKAGE,\n                                COLUMN_VOICE_NAME, COLUMN_UTTERANCE, COLUMN_BINARY}, whereClause, null, null, null,\n                        null);\n\n                if (cursor != null && cursor.getCount() > 0) {\n\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"speechExists: true\");\n                    }\n\n                    if (cursor.moveToFirst()) {\n                        compressedBytes = cursor.getBlob(4);\n\n                        final ContentValues values = new ContentValues();\n                        values.put(COLUMN_DATE, String.valueOf(System.currentTimeMillis()));\n\n                        database.update(TABLE_SPEECH, values, COLUMN_ID + \"=?\",\n                                new String[]{String.valueOf(cursor.getLong(0))});\n\n                        rowId = cursor.getLong(0);\n                    }\n\n                    cursor.close();\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"speechExists: false\");\n                    }\n                }\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getBytes: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getBytes: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getBytes: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getBytes: finally closing\");\n                    }\n                    close();\n\n                    if (DEBUG) {\n                        MyLog.getElapsed(CLS_NAME, then);\n                    }\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getBytes: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getBytes: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getBytes: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (compressedBytes != null && compressedBytes.length > 0) {\n            return new SpeechCacheResult(compressedBytes, rowId, true);\n        } else {\n            return new SpeechCacheResult(null, rowId, false);\n        }\n    }\n\n\n    /**\n     * Check if we should run the database maintenance, so it doesn't exceed a size that a user may\n     * be concerned about.\n     *\n     * @param ctx the application context\n     * @return true if the total size of the database exceeds {@link SPH#getMaxSpeechCacheSize(Context)}\n     */\n    public boolean shouldRunMaintenance(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \": shouldRunMaintenance\");\n        }\n\n        final long then = System.nanoTime();\n        long dbSize = 0;\n\n        try {\n            open();\n            if (database.isOpen()) {\n\n                final String dbPath = database.getPath();\n                if (dbPath != null) {\n\n                    final File dbFile = new File(dbPath);\n                    dbSize = dbFile.length();\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \": Database size: \" + dbSize);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \": Database Path: null\");\n                    }\n                }\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shouldRunMaintenance: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shouldRunMaintenance: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shouldRunMaintenance: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"shouldRunMaintenance: finally closing\");\n                    }\n                    close();\n\n                    if (DEBUG) {\n                        MyLog.getElapsed(CLS_NAME, then);\n                    }\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"shouldRunMaintenance: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"shouldRunMaintenance: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"shouldRunMaintenance: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return dbSize > SPH.getMaxSpeechCacheSize(ctx);\n    }\n\n    /**\n     * Check which entries have remained unused for a period longer than {@link #MAX_UNUSED_THRESHOLD}\n     * and remove them.\n     *\n     * @param ctx the application context\n     */\n    public void runMaintenance(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"runMaintenance\");\n        }\n\n        final long then = System.nanoTime();\n\n        try {\n            open();\n            if (database.isOpen()) {\n                final Cursor cursor = database.query(TABLE_SPEECH,\n                        new String[]{COLUMN_ID, COLUMN_UTTERANCE, COLUMN_DATE}, null, null, null, null, null);\n\n                if (cursor != null && cursor.getCount() > 0) {\n                    cursor.moveToFirst();\n\n                    final long lastUsedApp = SPH.getLastUsed(ctx);\n                    long lastUsedEntry;\n                    long usageGap;\n                    int deleteCount = 0;\n\n                    while (!cursor.isAfterLast()) {\n\n                        lastUsedEntry = cursor.getLong(2);\n                        usageGap = lastUsedApp - lastUsedEntry;\n\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"utterance: \" + cursor.getString(1));\n                            MyLog.d(CLS_NAME, \"lastUsedApp: \" + lastUsedApp);\n                            MyLog.d(CLS_NAME, \"lastUsedEntry: \" + lastUsedEntry);\n                            MyLog.d(CLS_NAME, \"usageGap: \" + usageGap);\n                        }\n\n                        if (usageGap > MAX_UNUSED_THRESHOLD) {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"deleting: true\");\n                            }\n                            database.delete(TABLE_SPEECH, COLUMN_ID + \"=?\",\n                                    new String[]{String.valueOf(cursor.getLong(0))});\n                            deleteCount++;\n                        } else {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"deleting: false\");\n                            }\n                        }\n\n                        cursor.moveToNext();\n                    }\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"deleteCount: \" + deleteCount);\n                    }\n\n                    if (deleteCount > VACUUM_THRESHOLD) {\n                        database.execSQL(\"VACUUM\");\n                    }\n\n                    cursor.close();\n                }\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"runMaintenance: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final SQLException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"runMaintenance: SQLException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"runMaintenance: Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            try {\n                if (database.isOpen()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"runMaintenance: finally closing\");\n                    }\n                    close();\n\n                    if (DEBUG) {\n                        MyLog.getElapsed(CLS_NAME, then);\n                    }\n                }\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runMaintenance: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runMaintenance: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runMaintenance: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Insert a populated row\n     *\n     * @param scp the prepared {@link SpeechCachePrepare} object\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    public void insertRow(@NonNull final SpeechCachePrepare scp) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \": insertRow\");\n        }\n\n        final long then = System.nanoTime();\n\n        if (scp.getCompressedAudio() != null && scp.getCompressedAudio().length > 0) {\n\n            try {\n                open();\n                if (database.isOpen()) {\n\n                    final ContentValues values = new ContentValues();\n\n                    values.put(COLUMN_ENGINE_PACKAGE, scp.getEngine());\n                    values.put(COLUMN_VOICE_NAME, scp.getVoice().getName());\n                    values.put(COLUMN_UTTERANCE, scp.getUtterance().trim().replaceAll(\"[^a-zA-Z0-9]\", \"\"));\n                    values.put(COLUMN_VOICE_LOCALE, scp.getLocale());\n                    values.put(COLUMN_BINARY, scp.getCompressedAudio());\n                    values.put(COLUMN_DATE, String.valueOf(System.currentTimeMillis()));\n\n                    final long insertId = database.insert(TABLE_SPEECH, null, values);\n                    final Cursor cursor = database.query(TABLE_SPEECH, ALL_COLUMNS,\n                            COLUMN_ID + \" = \" + insertId, null, null,\n                            null, null);\n                    cursor.moveToFirst();\n                    cursor.close();\n                }\n\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertRow: IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final SQLException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertRow: SQLException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"insertRow: Exception\");\n                    e.printStackTrace();\n                }\n            } finally {\n                try {\n                    if (database.isOpen()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"insertRow: finally closing\");\n                        }\n                        close();\n\n                        if (DEBUG) {\n                            MyLog.getElapsed(CLS_NAME, then);\n                        }\n                    }\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"insertRow: IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                } catch (final SQLException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"insertRow: SQLException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"insertRow: Exception\");\n                        e.printStackTrace();\n                    }\n                }\n            }\n        } else {\n\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \": insertRow: compression failed\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(\"insertRow took - \", then);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/database/callable/DBCustomCommandCallable.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.database.callable;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Callable;\n\nimport ai.saiy.android.custom.CustomCommandHelper;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class DBCustomCommandCallable implements Callable<ArrayList<Object>> {\n\n    private final Context mContext;\n\n    public DBCustomCommandCallable(@NonNull final Context mContext) {\n        this.mContext = mContext;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public ArrayList<Object> call() throws Exception {\n        return (ArrayList) new CustomCommandHelper().getCustomCommands(mContext);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/defaults/ApplicationDefaults.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.defaults;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionProvider;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic class ApplicationDefaults {\n\n    /**\n     * Check if the user has already set a default song recognition provider\n     *\n     * @param ctx the application context\n     * @return the default {@link SongRecognitionProvider} object or {@link SongRecognitionProvider#UNKNOWN} if\n     * the user has not set a default\n     */\n    public static SongRecognitionProvider getSongRecognitionProvider(@NonNull final Context ctx) {\n        return SPH.getDefaultSongRecognition(ctx);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/defaults/songrecognition/SongRecognitionChooser.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.defaults.songrecognition;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic class SongRecognitionChooser implements Parcelable {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SongRecognitionChooser.class.getSimpleName();\n\n    public static final String PARCEL_KEY = \"song_recognition_chooser\";\n\n    private String applicationName;\n    private String packageName;\n    private boolean installed;\n\n    public static final Creator<SongRecognitionChooser> CREATOR = new\n            Creator<SongRecognitionChooser>() {\n                public SongRecognitionChooser createFromParcel(@NonNull final Parcel in) {\n                    return new SongRecognitionChooser(in);\n                }\n\n                public SongRecognitionChooser[] newArray(int size) {\n                    return new SongRecognitionChooser[size];\n                }\n            };\n\n    public SongRecognitionChooser() {\n    }\n\n    private SongRecognitionChooser(@NonNull final Parcel in) {\n        readFromParcel(in);\n    }\n\n    public SongRecognitionChooser(@NonNull final String applicationName, @NonNull final String packageName,\n                                  final boolean installed) {\n        this.applicationName = applicationName;\n        this.packageName = packageName;\n        this.installed = installed;\n    }\n\n    public void readFromParcel(@NonNull final Parcel in) {\n        this.applicationName = in.readString();\n        this.packageName = in.readString();\n        this.installed = (in.readInt() == 1);\n    }\n\n    @Override\n    public void writeToParcel(final Parcel out, final int flags) {\n        out.writeString(this.applicationName);\n        out.writeString(this.packageName);\n        out.writeInt(installed ? 1 : 0);\n    }\n\n    public String getApplicationName() {\n        return applicationName;\n    }\n\n    public boolean isInstalled() {\n        return installed;\n    }\n\n    public String getPackageName() {\n        return packageName;\n    }\n\n    public void setApplicationName(final String applicationName) {\n        this.applicationName = applicationName;\n    }\n\n    public void setInstalled(final boolean installed) {\n        this.installed = installed;\n    }\n\n    public void setPackageName(final String packageName) {\n        this.packageName = packageName;\n    }\n\n    public static ArrayList<SongRecognitionChooser> prepareChooser(@NonNull final Context ctx,\n                                                                   @NonNull final SupportedLanguage sl) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareChooser\");\n        }\n\n        final ArrayList<SongRecognitionChooser> chooserList = new ArrayList<>();\n\n        SongRecognitionChooser src;\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.SHAZAM),\n                Installed.PACKAGE_SHAZAM, Installed.isPackageInstalled(ctx, Installed.PACKAGE_SHAZAM));\n\n        chooserList.add(src);\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.SHAZAM_ENCORE),\n                Installed.PACKAGE_SHAZAM_ENCORE, Installed.isPackageInstalled(ctx,\n                Installed.PACKAGE_SHAZAM_ENCORE));\n\n        chooserList.add(src);\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.SOUND_HOUND),\n                Installed.PACKAGE_SOUND_HOUND, Installed.isPackageInstalled(ctx,\n                Installed.PACKAGE_SOUND_HOUND));\n\n        chooserList.add(src);\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.SOUND_HOUND_PREMIUM),\n                Installed.PACKAGE_SOUND_HOUND_PREMIUM, Installed.isPackageInstalled(ctx,\n                Installed.PACKAGE_SOUND_HOUND_PREMIUM));\n\n        chooserList.add(src);\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.TRACK_ID),\n                Installed.PACKAGE_TRACK_ID, Installed.isPackageInstalled(ctx, Installed.PACKAGE_TRACK_ID));\n\n        chooserList.add(src);\n\n        src = new SongRecognitionChooser(\n                SongRecognitionProvider.getApplicationName(ctx, sl, SongRecognitionProvider.GOOGLE),\n                Installed.PACKAGE_GOOGLE_SOUND_SEARCH, Installed.isPackageInstalled(ctx,\n                Installed.PACKAGE_GOOGLE_SOUND_SEARCH));\n\n        chooserList.add(src);\n\n        chooserList.addAll(prepareChooserDefault(ctx));\n\n        return removeDuplicates(chooserList);\n    }\n\n    private static ArrayList<SongRecognitionChooser> prepareChooserDefault(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareChooser\");\n        }\n\n        final ArrayList<SongRecognitionChooser> chooserList = new ArrayList<>();\n\n        final Intent recognitionIntent = new Intent();\n        recognitionIntent.setAction(SongRecognitionProvider.MEDIA_RECOGNIZE);\n\n        final PackageManager pm = ctx.getPackageManager();\n        final List<ResolveInfo> list = pm.queryIntentActivities(recognitionIntent, PackageManager.GET_META_DATA);\n\n        final int size = list.size();\n\n        if (size > 0) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"prepareChooser: apps: \" + size);\n            }\n\n            String packageName;\n            String applicationName;\n            SongRecognitionChooser src;\n\n            for (int i = 0; i < size; i++) {\n                src = new SongRecognitionChooser();\n                applicationName = list.get(i).loadLabel(pm).toString();\n\n                if (!UtilsString.notNaked(applicationName)) {\n                    src.setApplicationName(applicationName);\n                    packageName = list.get(i).activityInfo.packageName;\n\n                    if (UtilsString.notNaked(packageName)) {\n                        src.setPackageName(packageName);\n                        src.setInstalled(true);\n                        chooserList.add(src);\n                    }\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"prepareChooser: no apps responded to intent\");\n            }\n        }\n\n        return chooserList;\n    }\n\n    private static ArrayList<SongRecognitionChooser> removeDuplicates(\n            @NonNull final ArrayList<SongRecognitionChooser> chooserList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareChooser\");\n        }\n\n        final ArrayList<String> packageList = new ArrayList<>();\n        final Iterator<SongRecognitionChooser> itr = chooserList.iterator();\n\n        String packageName;\n        SongRecognitionChooser src;\n        while (itr.hasNext()) {\n            src = itr.next();\n            packageName = src.getPackageName();\n\n            if (packageList.contains(packageName)) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"src removed: \" + packageName);\n                }\n\n                itr.remove();\n            } else {\n                packageList.add(packageName);\n            }\n        }\n\n        return chooserList;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/defaults/songrecognition/SongRecognitionProvider.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.defaults.songrecognition;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 10/06/2016.\n */\npublic enum SongRecognitionProvider {\n\n    UNKNOWN,\n    SHAZAM,\n    SHAZAM_ENCORE,\n    SOUND_HOUND,\n    SOUND_HOUND_PREMIUM,\n    TRACK_ID,\n    GOOGLE;\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SongRecognitionProvider.class.getSimpleName();\n\n    public static final String MEDIA_RECOGNIZE = \"saiy.intent.action.MEDIA_RECOGNIZE\";\n\n    public static final String SHAZAM_ACTION = \"com.shazam.android.intent.actions.START_TAGGING\";\n    public static final String SOUND_HOUND_ACTION = \"com.soundhound.android.ID_NOW_EXTERNAL\";\n    public static final String TRACK_ID_ACTION = \"com.sonyericsson.trackid.intent.action.LAUNCH\";\n    public static final String GOOGLE_ACTION = \"com.google.android.googlequicksearchbox.MUSIC_SEARCH\";\n\n    /**\n     * Get the supported application's name\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param provider the {@link SongRecognitionProvider}\n     * @return the application's name\n     */\n    public static String getApplicationName(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                            @NonNull final SongRecognitionProvider provider) {\n\n        switch (provider) {\n\n            case SHAZAM:\n                return ctx.getString(ai.saiy.android.R.string.shazam);\n            case SHAZAM_ENCORE:\n                return ctx.getString(ai.saiy.android.R.string.shazam_encore);\n            case SOUND_HOUND:\n                return ctx.getString(ai.saiy.android.R.string.sound_hound);\n            case SOUND_HOUND_PREMIUM:\n                return ctx.getString(ai.saiy.android.R.string.sound_hound_premium);\n            case TRACK_ID:\n                return ctx.getString(ai.saiy.android.R.string.track_id);\n            case GOOGLE:\n                return ctx.getString(R.string.google_capital) +\n                        SaiyResourcesHelper.getStringResource(ctx, sl, ai.saiy.android.R.string.sound_search);\n        }\n\n        return \"\";\n    }\n\n    public static SongRecognitionProvider getProvider(int provider) {\n\n        final SongRecognitionProvider[] providers = values();\n\n        if (providers.length > provider) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getProvider: \" + providers[provider].toString());\n            }\n            return providers[provider];\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getProvider: out of bounds. Returning UNKNOWN\");\n            }\n            return UNKNOWN;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/device/DeviceInfo.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.device;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.os.Build;\nimport android.provider.Settings;\nimport android.speech.RecognitionService;\nimport android.support.annotation.NonNull;\n\nimport java.util.List;\nimport java.util.Locale;\n\nimport ai.saiy.android.BuildConfig;\nimport ai.saiy.android.R;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Utility class for quick access to device specific settings\n * <p>\n * Created by benrandall76@gmail.com on 25/08/2016.\n */\n\npublic class DeviceInfo {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = DeviceInfo.class.getSimpleName();\n\n    /**\n     * Utility method to prepare certain device information to send along with any feedback.\n     *\n     * @param ctx the application context\n     * @return a formatted string containing the required device info.\n     */\n    public static String getDeviceInfo(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getDeviceInfo\");\n        }\n\n        return \"\\n\\n\\n\" +\n                \"--------------------\" +\n                \"\\n\" +\n                ctx.getString(R.string.app_name) +\n                \" V\" +\n                BuildConfig.VERSION_NAME +\n                \"B\\n\" +\n                ctx.getString(R.string.model) +\n                \": \" +\n                Build.MODEL +\n                \"\\n\" +\n                ctx.getString(R.string.manufacturer) +\n                \": \" +\n                Build.MANUFACTURER +\n                \"\\n\" +\n                ctx.getString(R.string.android) +\n                \": \" +\n                Build.VERSION.SDK_INT +\n                \"\\n\" +\n                ctx.getString(R.string.locale) +\n                \": \" +\n                Locale.getDefault().toString() +\n                \"\\n\" +\n                \"VR \" + ctx.getString(R.string.locale) +\n                \": \" +\n                SPH.getVRLocale(ctx).toString() +\n                \"\\n\" +\n                \"TTS \" + ctx.getString(R.string.locale) +\n                \": \" +\n                SPH.getTTSLocale(ctx).toString() +\n                \"\\n\" +\n                \"TTS \" + ctx.getString(R.string.engine) +\n                \": \" +\n                getDefaultTTSProvider(ctx) +\n                \"\\n\" +\n                \"VR \" + ctx.getString(R.string.engine) +\n                \": \" +\n                getDefaultVRProvider(ctx) +\n                \"\\n\" +\n                \"--------------------\";\n    }\n\n    /**\n     * Get the default Text to Speech provider\n     *\n     * @param ctx the application context\n     * @return the default or an empty string if one is not present\n     */\n    public static String getDefaultTTSProvider(@NonNull final Context ctx) {\n        final String engine = Settings.Secure.getString(ctx.getContentResolver(),\n                Settings.Secure.TTS_DEFAULT_SYNTH);\n        return UtilsString.notNaked(engine) ? engine : \"\";\n    }\n\n    /**\n     * Get the default voice recognition provider\n     *\n     * @param ctx the application context\n     * @return the default or an empty string if one is not present\n     */\n    public static String getDefaultVRProvider(@NonNull final Context ctx) {\n        final List<ResolveInfo> services = ctx.getPackageManager().queryIntentServices(\n                new Intent(RecognitionService.SERVICE_INTERFACE), 0);\n        return UtilsList.notNaked(services) ? services.get(0).serviceInfo.packageName : \"\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/device/UtilsDevice.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.device;\n\nimport android.annotation.TargetApi;\nimport android.app.KeyguardManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.PowerManager;\nimport android.support.annotation.NonNull;\n\n/**\n * A collection of handy device related methods. Static for easy access\n * <p>\n * Created by benrandall76@gmail.com on 06/09/2016.\n */\n\npublic class UtilsDevice {\n\n    /**\n     * Check if the device is currently locked\n     *\n     * @return true if the device is in secure mode, false otherwise\n     */\n    public static boolean isDeviceLocked(@NonNull final Context ctx) {\n        return ((KeyguardManager) ctx.getSystemService(Context.KEYGUARD_SERVICE))\n                .inKeyguardRestrictedInputMode();\n    }\n\n    /**\n     * Check if the device screen is currently turned off\n     *\n     * @param ctx the application context\n     * @return true if the screen is off, false otherwise\n     */\n    public static boolean isScreenOff(@NonNull final Context ctx) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {\n            return isScreenOff20(ctx);\n        } else {\n            return isScreenOffDeprecated(ctx);\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static boolean isScreenOffDeprecated(@NonNull final Context ctx) {\n        return !((PowerManager) ctx.getSystemService(Context.POWER_SERVICE)).isScreenOn();\n    }\n\n    @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n    private static boolean isScreenOff20(@NonNull final Context ctx) {\n        return !((PowerManager) ctx.getSystemService(Context.POWER_SERVICE)).isInteractive();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/error/Issue.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.error;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.ui.activity.ActivityIssue;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to handle issues that the user needs to act upon. The issue is constructed using\n * {@link IssueContent} and forwarded to {@link ActivityIssue}\n * <p/>\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic class Issue {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Issue.class.getSimpleName();\n\n    public static final String ISSUE_CONTENT = \"issue_content\";\n\n    public static final int ISSUE_UNKNOWN = 0;\n    public static final int ISSUE_NO_VR = 1;\n    public static final int ISSUE_NO_TTS_ENGINE = 2;\n    public static final int ISSUE_NO_TTS_LANGUAGE = 3;\n    public static final int ISSUE_VLINGO = 4;\n\n    private final Intent intent;\n    private final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param mContext      the application context\n     * @param issueConstant that identifies the specific issue\n     */\n    public Issue(@NonNull final Context mContext, final int issueConstant) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Constructor\");\n        }\n\n        this.mContext = mContext;\n\n        final IssueContent issueContent = new IssueContent(issueConstant);\n        issueContent.setIssueText(getIssueText(issueConstant));\n\n        intent = new Intent(this.mContext, ActivityIssue.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        Bundle bundle = new Bundle();\n        bundle.putSerializable(ISSUE_CONTENT, issueContent);\n\n        intent.putExtras(bundle);\n\n    }\n\n    /**\n     * Action the intent\n     */\n    public void execute() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n        mContext.startActivity(intent);\n    }\n\n    /**\n     * Get the text that Saiy will announce or display to the user in {@link ActivityIssue}\n     *\n     * @param issueConstant that identifies the specific issue\n     * @return the String that Saiy will announce or display\n     */\n    private String getIssueText(final int issueConstant) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getIssueText\");\n        }\n\n        switch (issueConstant) {\n\n            case ISSUE_NO_VR:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getIssueText: ISSUE_NO_VR\");\n                }\n                return mContext.getString(R.string.issue_vr_text);\n            case ISSUE_NO_TTS_ENGINE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getIssueText: ISSUE_NO_TTS_ENGINE\");\n                }\n                return mContext.getString(R.string.issue_tts_engine_text);\n            case ISSUE_NO_TTS_LANGUAGE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getIssueText: ISSUE_NO_TTS_LANGUAGE\");\n                }\n                return mContext.getString(R.string.issue_tts_language_text);\n            case ISSUE_VLINGO:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getIssueText: ISSUE_VLINGO\");\n                }\n                return mContext.getString(R.string.issue_vlingo_text);\n            case ISSUE_UNKNOWN:\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getIssueText: ISSUE_UNKNOWN\");\n                }\n                return SaiyRequestParams.SILENCE;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/error/IssueContent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.error;\n\nimport android.support.annotation.NonNull;\n\nimport java.io.Serializable;\n\nimport ai.saiy.android.ui.activity.ActivityIssue;\n\n/**\n * Holder to provide information to display in {@link ActivityIssue}\n * <p/>\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic class IssueContent implements Serializable {\n\n    private static final long serialVersionUID = -593683149969450529L;\n\n    private final int issueConstant;\n    private String issueText;\n\n    public IssueContent(final int issueConstant) {\n        this.issueConstant = issueConstant;\n    }\n\n    public void setIssueText(@NonNull final String issueText) {\n        this.issueText = issueText;\n    }\n\n    public int getIssueConstant() {\n        return issueConstant;\n    }\n\n    public String getIssueText() {\n        return issueText;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/files/FileCreator.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.files;\n\nimport android.content.Context;\nimport android.media.MediaMetadataRetriever;\nimport android.support.annotation.NonNull;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\n\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsFile;\n\n/**\n * Class to handle writing audio data to a file whilst it is being recorded. This can be done\n * simultaneously to other audio functions, although performance can take a hit.\n * <p/>\n * Created by benrandall76@gmail.com on 14/02/2016.\n */\npublic class FileCreator {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = FileCreator.class.getSimpleName();\n\n    private volatile int payloadSize;\n    private volatile RandomAccessFile fWriter;\n    private volatile String absolutePath;\n\n    private final Object lock = new Object();\n    private final int samplingRate;\n    private final int nChannels;\n    private final int bSamples;\n    private File defaultFile;\n\n    /**\n     * Constructor\n     *\n     * @param mContext     the application context\n     * @param nChannels    the number of channels\n     * @param samplingRate the sampling rate in hertz\n     * @param bSamples     the sampling rate\n     */\n    public FileCreator(@NonNull final Context mContext, final int nChannels, final int samplingRate,\n                       final int bSamples) {\n        this.nChannels = nChannels;\n        this.samplingRate = samplingRate;\n        this.bSamples = bSamples;\n\n        final File filePath = UtilsFile.getPrivateDir(mContext);\n\n        try {\n\n            defaultFile = File.createTempFile(Constants.DEFAULT_FILE_PREFIX,\n                    Constants.DEFAULT_AUDIO_FILE_SUFFIX, filePath);\n\n            if (defaultFile.exists()) {\n                final boolean deleteSuccess = defaultFile.delete();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"file deleted successfully: \" + deleteSuccess);\n                }\n            }\n\n            this.absolutePath = defaultFile.getAbsolutePath();\n            this.fWriter = getWriter();\n\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Temp file: IOException\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Get the file that has been created\n     *\n     * @return the created file\n     */\n    public File getDefaultFile() {\n        return defaultFile;\n    }\n\n    /**\n     * Pass the audio buffer data\n     *\n     * @param buff the audio buffer\n     */\n    public void passBuffer(@NonNull final byte[] buff) {\n\n        if (fWriter != null) {\n\n            synchronized (lock) {\n\n                new Thread() {\n                    public void run() {\n                        try {\n                            fWriter.write(buff);\n                            payloadSize += buff.length;\n                        } catch (final IOException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"IOException: recording is aborted\");\n                                e.printStackTrace();\n                            }\n                        }\n                    }\n                }.start();\n            }\n        }\n    }\n\n    /**\n     * Finish writing the file\n     */\n    public boolean completeWrite() {\n\n        if (fWriter != null) {\n\n            try {\n\n                fWriter.seek(4);\n                fWriter.writeInt(Integer.reverseBytes(36 + payloadSize));\n                fWriter.seek(40);\n                fWriter.writeInt(Integer.reverseBytes(payloadSize));\n                fWriter.close();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"finished successfully with payload: \" + payloadSize);\n                }\n\n                return true;\n\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"IOException: completeFileWrite\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Constructs the wave header data\n     */\n    private RandomAccessFile getWriter() {\n\n        RandomAccessFile myWriter = null;\n\n        try {\n\n            myWriter = new RandomAccessFile(absolutePath, \"rws\");\n\n            myWriter.setLength(0);\n            myWriter.writeBytes(\"RIFF\");\n            myWriter.writeInt(0);\n            myWriter.writeBytes(\"WAVE\");\n            myWriter.writeBytes(\"fmt \");\n            myWriter.writeInt(Integer.reverseBytes(16));\n            myWriter.writeShort(Short.reverseBytes((short) 1));\n            myWriter.writeShort(Short.reverseBytes((short) nChannels));\n            myWriter.writeInt(Integer.reverseBytes(samplingRate));\n            myWriter.writeInt(Integer.reverseBytes(samplingRate * bSamples * nChannels / 8));\n            myWriter.writeShort(Short.reverseBytes((short) (nChannels * bSamples / 8)));\n            myWriter.writeShort(Short.reverseBytes((short) bSamples));\n            myWriter.writeBytes(\"data\");\n            myWriter.writeInt(0);\n\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"IOException: get writer\");\n                e.printStackTrace();\n            }\n        }\n\n        return myWriter;\n    }\n\n    /**\n     * Used to get information about the written file\n     */\n    private void getFileMeta() {\n\n        if (absolutePath != null) {\n\n            try {\n\n                final MediaMetadataRetriever mmr = new MediaMetadataRetriever();\n                mmr.setDataSource(absolutePath);\n                final String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);\n                mmr.release();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recording duration: \" + duration);\n                }\n\n            } catch (final RuntimeException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"RuntimeException: completeFileWrite\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/intent/ExecuteIntent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.intent;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.SearchManager;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.Settings;\nimport android.speech.RecognizerIntent;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.request.SaiyRequest;\nimport ai.saiy.android.applications.Install;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.device.DeviceInfo;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Utility class collecting the most common intents used throughout the application\n * <p>\n * Created by benrandall76@gmail.com on 13/04/2016.\n */\npublic class ExecuteIntent {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = ExecuteIntent.class.getSimpleName();\n\n    /**\n     * Execute a given intent\n     *\n     * @param ctx    the application context\n     * @param intent the intent to execute\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    public static boolean executeIntent(@NonNull final Context ctx, @NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"executeIntent\");\n        }\n\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"executeIntent: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"executeIntent: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Start a service with a given intent\n     *\n     * @param ctx    the application context\n     * @param intent the intent to execute\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    public static boolean startService(@NonNull final Context ctx, @NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startService\");\n        }\n\n        try {\n            final ComponentName componentName = ctx.startService(intent);\n\n            if (componentName != null) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"startService for: \" + componentName.getPackageName());\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"startService: componentName null\");\n                }\n            }\n            return true;\n        } catch (final SecurityException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"startService: SecurityException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"startService: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Broadcast a given intent\n     *\n     * @param ctx    the application context\n     * @param intent the intent to execute\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    public static boolean sendBroadcast(@NonNull final Context ctx, @NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"sendBroadcast\");\n        }\n\n        try {\n            ctx.sendBroadcast(intent, SaiyRequest.CONTROL_SAIY);\n            return true;\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"sendBroadcast: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Search the Play Store using the name of the desired application. If the user has the\n     * Play Store application installed and defaulted, this URL will open directly in the application.\n     * Otherwise, it will default to a browser search.\n     *\n     * @param ctx     the application context\n     * @param appName to search for\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean playStoreSearch(@NonNull final Context ctx, @NonNull final String appName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"playStoreSearch\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(IntentConstants.PLAY_STORE_SEARCH_URL +\n                appName + IntentConstants.PLAY_STORE_APPS_EXTENSION));\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"playStoreSearch: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"playStoreSearch: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n\n    }\n\n    /**\n     * Perform a web search from the device.\n     *\n     * @param ctx the application context\n     * @param url the url to open\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean webSearch(@NonNull final Context ctx, @NonNull final String url) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"webSearch: \" + url);\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"webSearch: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"webSearch: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Send an uninstall broadcast\n     *\n     * @param ctx         the application context\n     * @param packageName of the app to be uninstalled\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean uninstallApp(@NonNull final Context ctx, @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"uninstallApp\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_DELETE);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.setData(Uri.parse(IntentConstants.PACKAGE + packageName));\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"uninstallApp: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"uninstallApp: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Send an email.\n     *\n     * @param ctx       the application context\n     * @param addresses recipient emails\n     * @param subject   the email subject\n     * @return true if an Activity was available to handle the intent. False otherwise.\n     */\n    public static boolean sendEmail(@NonNull final Context ctx, @NonNull final String[] addresses,\n                                    @Nullable final String subject, @Nullable final String body) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"sendEmail\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_SENDTO);\n        intent.setData(Uri.parse(IntentConstants.MAILTO));\n        intent.putExtra(Intent.EXTRA_EMAIL, addresses);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        if (UtilsString.notNaked(subject)) {\n            intent.putExtra(Intent.EXTRA_SUBJECT, subject);\n        }\n\n        if (UtilsString.notNaked(body)) {\n            intent.putExtra(Intent.EXTRA_TEXT, body);\n        }\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"sendEmail: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"sendEmail: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Launch the Android application settings for a specific application\n     *\n     * @param ctx         the application context\n     * @param packageName of the desired application\n     * @return true if the application settings are correctly opened\n     */\n    public static boolean openApplicationSpecificSettings(@NonNull final Context ctx,\n                                                          @NonNull final String packageName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"openApplicationSpecificSettings\");\n        }\n\n        final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse(\"package:\"\n                + packageName));\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"openApplicationSpecificSettings: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"openApplicationSpecificSettings: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Start Google Now in listening mode\n     *\n     * @param ctx    the application context\n     * @param secure true if the device is in secure mode\n     * @return true if the intent was successful, false otherwise\n     */\n    public static boolean googleNowListen(@NonNull final Context ctx, final boolean secure) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"googleNowListen\");\n        }\n\n        final Intent intent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);\n        intent.setPackage(IntentConstants.PACKAGE_NAME_GOOGLE_NOW);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(RecognizerIntent.EXTRA_SECURE, secure);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"googleNowListen: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"googleNowListen: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n\n    }\n\n    /**\n     * Launch Google Now with a specific search term to resolve\n     *\n     * @param ctx        the application context\n     * @param searchTerm the search term to resolve\n     * @return true if the search term was handled correctly, false otherwise\n     */\n    public static boolean googleNow(@NonNull final Context ctx, @NonNull final String searchTerm) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"googleNow\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);\n        intent.setComponent(new ComponentName(IntentConstants.PACKAGE_NAME_GOOGLE_NOW,\n                IntentConstants.PACKAGE_NAME_GOOGLE_NOW + IntentConstants.ACTIVITY_GOOGLE_NOW_SEARCH));\n\n        intent.putExtra(SearchManager.QUERY, searchTerm);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP\n                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"googleNow: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"googleNow: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n\n    }\n\n    /**\n     * Launch Wolfram Alpha with a specific search term to resolve\n     *\n     * @param ctx        the application context\n     * @param searchTerm the search term to resolve\n     * @return true if the search term was passed correctly, false otherwise\n     */\n    public static boolean wolframAlpha(@NonNull final Context ctx, @NonNull final String searchTerm) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"wolframAlpha\");\n        }\n\n        final Intent intent = new Intent(IntentConstants.INTENT_SEARCH_WOLFRAM_ALPHA);\n        intent.setComponent(new ComponentName(IntentConstants.PACKAGE_NAME_WOLFRAM_ALPHA,\n                IntentConstants.PACKAGE_NAME_WOLFRAM_ALPHA + IntentConstants.ACTIVITY_WOLFRAM_ALPHA_SEARCH));\n        intent.setData(Uri.parse(searchTerm));\n        intent.putExtra(SearchManager.QUERY, searchTerm);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP\n                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"wolframAlpha: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"wolframAlpha: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Standard share intent\n     *\n     * @param ctx the application context\n     * @return true if the intent was successfully processed, false otherwise\n     */\n    public static boolean shareIntent(@NonNull final Context ctx, @NonNull final String content) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shareIntent\");\n        }\n\n        final Intent intent = new Intent(android.content.Intent.ACTION_SEND);\n        intent.setType(IntentConstants.TEXT_PLAIN);\n        intent.putExtra(Intent.EXTRA_TEXT, content);\n\n        final Intent chooserIntent = Intent.createChooser(intent, ctx.getString(R.string.chooser_share_via));\n        chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(chooserIntent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"shareIntent: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"shareIntent: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Intent to prepare and send a feedback email.\n     *\n     * @param ctx the application context\n     * @return true if the intent was successfully processed, false otherwise\n     */\n    public static boolean sendDeveloperEmail(final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"developerEmail\");\n        }\n        return sendEmail(ctx, new String[]{Constants.SAIY_FEEDBACK_EMAIL}, ctx.getString(R.string.feedback),\n                DeviceInfo.getDeviceInfo(ctx));\n    }\n\n    /**\n     * Intent to return to the device home screen/launcher\n     *\n     * @param ctx the application context\n     * @return true if the intent was successfully processed, false otherwise\n     */\n    public static boolean goHome(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"goHome\");\n        }\n\n        final Intent intent = new Intent(Intent.ACTION_MAIN);\n        intent.addCategory(Intent.CATEGORY_HOME);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"goHome: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"goHome: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Launch an Activity of the application\n     *\n     * @param ctx     the application context\n     * @param cls     the class to launch\n     * @param newTask if the corresponding flag should be added\n     * @return true if the activity was launched successfully, false otherwise\n     */\n    public static boolean saiyActivity(@NonNull final Context ctx, @NonNull final Class<?> cls,\n                                       @Nullable final Bundle bundle, final boolean newTask) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"saiyActivity\");\n        }\n\n        final Intent intent = new Intent(ctx, cls);\n\n        if (newTask) {\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n\n        if (UtilsBundle.notNaked(bundle)) {\n            intent.putExtras(bundle);\n        }\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"saiyActivity: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"saiyActivity: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Execute a Settings Intent\n     *\n     * @param ctx            the application context\n     * @param intentConstant the {@link IntentConstants} identifying the intent to execute\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    @SuppressLint(\"InlinedApi\")\n    public static boolean settingsIntent(@NonNull final Context ctx, final int intentConstant) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"settingsIntent\");\n        }\n\n        final Intent intent = new Intent();\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        switch (intentConstant) {\n\n            case IntentConstants.SETTINGS_ACCESSIBILITY:\n                intent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);\n                break;\n            case IntentConstants.SETTINGS_VOICE_SEARCH:\n                return voiceSearchSettings(ctx);\n            case IntentConstants.SETTINGS_INPUT_METHOD:\n                intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);\n                break;\n            case IntentConstants.SETTINGS_USAGE_STATS:\n                intent.setAction(Settings.ACTION_USAGE_ACCESS_SETTINGS);\n                break;\n            case IntentConstants.SETTINGS_VOLUME:\n                intent.setAction(Settings.ACTION_SOUND_SETTINGS);\n                break;\n            case IntentConstants.SETTINGS_TEXT_TO_SPEECH:\n                intent.setAction(IntentConstants.ACTION_TEXT_TO_SPEECH);\n                break;\n            case IntentConstants.SETTINGS_ADD_ACCOUNT:\n                intent.setAction(Settings.ACTION_ADD_ACCOUNT);\n                intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[]{Install.getAccountType()});\n                break;\n            default:\n                break;\n        }\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"settingsIntent: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"settingsIntent: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Execute a Voice Search Settings Intent\n     *\n     * @param ctx the application context\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    private static boolean voiceSearchSettings(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceSearchSettings\");\n        }\n\n        final Intent intent = new Intent();\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        final ArrayList<ComponentName> componentArray = new ArrayList<>();\n        componentArray.add(new ComponentName(Installed.PACKAGE_NAME_GOOGLE_NOW,\n                IntentConstants.COMPONENT_VOICE_SEARCH_PREFERENCES_VELVET));\n        componentArray.add(new ComponentName(Installed.PACKAGE_NAME_GOOGLE_NOW,\n                IntentConstants.COMPONENT_VOICE_SEARCH_PREFERENCES));\n\n        for (final ComponentName componentName : componentArray) {\n\n            try {\n                intent.setComponent(componentName);\n                ctx.startActivity(intent);\n                return true;\n            } catch (final ActivityNotFoundException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"voiceSearchSettings: ActivityNotFoundException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"voiceSearchSettings: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Execute a Voice Assist Settings Intent\n     *\n     * @param ctx the application context\n     * @return true if the intent executed correctly. False otherwise.\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private static boolean voiceAssistSettings21(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceAssistSettings21\");\n        }\n\n        final Intent intent = new Intent();\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.setAction(Settings.ACTION_VOICE_INPUT_SETTINGS);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final ActivityNotFoundException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"voiceAssistSettings21: ActivityNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"voiceAssistSettings21: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return voiceSearchSettings(ctx);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/intent/IntentConstants.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.intent;\n\n/**\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic class IntentConstants {\n\n    public static final int SETTINGS_ACCESSIBILITY = 1;\n    public static final int SETTINGS_VOICE_SEARCH = 2;\n    public static final int SETTINGS_INPUT_METHOD = 3;\n    public static final int SETTINGS_USAGE_STATS = 4;\n    public static final int SETTINGS_VOLUME = 5;\n    public static final int SETTINGS_TEXT_TO_SPEECH = 6;\n    public static final int SETTINGS_ADD_ACCOUNT = 7;\n\n    public static final String COMPONENT_VOICE_SEARCH_PREFERENCES = \"com.google.android.voicesearch.VoiceSearchPreferences\";\n    public static final String COMPONENT_VOICE_SEARCH_PREFERENCES_VELVET = \"com.google.android.apps.gsa.velvet.ui.settings.VoiceSearchPreferences\";\n\n    public static final String ACTION_SAIY_VOICE_DATA = \"ai.saiy.android.action.VOICE_DATA\";\n    public static final String EXTRA_VOICE_DATA = \"voice_data\";\n    public static final String ACTION_TEXT_TO_SPEECH = \"com.android.settings.TTS_SETTINGS\";\n\n    public static final String AMAZON_PACKAGE_URL = \"\";\n    public static final String PLAY_STORE_PACKAGE_URL = \"https://play.google.com/store/apps/details?id=\";\n    public static final String PLAY_STORE_SEARCH_URL = \"https://play.google.com/store/search?q=\";\n    public static final String PLAY_STORE_SEARCH_URL_LEGACY = \"market://search?q=\";\n    public static final String PLAY_STORE_APPS_EXTENSION = \"&c=apps\";\n\n    public static final String PACKAGE_NAME_GOOGLE_NOW = \"com.google.android.googlequicksearchbox\";\n    public static final String ACTIVITY_GOOGLE_NOW_SEARCH = \".SearchActivity\";\n\n    public static final String PACKAGE_NAME_WOLFRAM_ALPHA = \"com.wolfram.android.alpha\";\n    public static final String ACTIVITY_WOLFRAM_ALPHA_SEARCH = \".activity.SearchResultsActivity\";\n    public static final String INTENT_SEARCH_WOLFRAM_ALPHA = \"com.wolfram.android.alpha.intent.action.DO_QUERY_SUGGESTION\";\n\n    public static final String TEXT_PLAIN = \"text/plain\";\n    public static final String MAILTO = \"mailto:\";\n    public static final String PACKAGE = \"package:\";\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/localisation/SaiyResources.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.localisation;\n\nimport android.content.Context;\nimport android.content.res.AssetManager;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.util.DisplayMetrics;\n\nimport java.util.Formatter;\nimport java.util.Locale;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to manage fetching {@link Resources} for a specific {@link Locale}. API levels less\n * than {@link Build.VERSION_CODES#JELLY_BEAN_MR1} require an ugly implementation.\n * <p>\n * Subclass implements {@link Resources} in case of further functionality requirements, such as\n * possible resource not found exception handling.\n * <p>\n * Created by benrandall76@gmail.com on 27/03/2016.\n */\npublic class SaiyResources {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyResources.class.getSimpleName();\n\n    private final Context mContext;\n    private final AssetManager assetManager;\n    private final DisplayMetrics metrics;\n    private final Configuration configuration;\n    private final Locale targetLocale;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage}\n     */\n    public SaiyResources(@NonNull final Context mContext, @NonNull final SupportedLanguage sl) {\n\n        this.mContext = mContext;\n        final Resources resources = this.mContext.getResources();\n        this.assetManager = resources.getAssets();\n        this.metrics = resources.getDisplayMetrics();\n        this.configuration = new Configuration(resources.getConfiguration());\n        this.targetLocale = sl.getLocale();\n    }\n\n    /**\n     * Must be called once no further localised resources are required. If it is not,\n     * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} devices will have their global system local changed.\n     */\n    @SuppressWarnings(\"deprecation\")\n    public void reset() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"reset\");\n        }\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 && checkNotNull()) {\n            configuration.locale = Locale.getDefault(); // reset\n            new ResourceManager(assetManager, metrics, configuration); // reset\n        }\n    }\n\n    /**\n     * Simple check to avoid null pointers prior to resetting.\n     *\n     * @return if the configuration can be safely reset. False otherwise.\n     */\n    private boolean checkNotNull() {\n        return configuration != null && assetManager != null && metrics != null;\n    }\n\n    /**\n     * Get a localised string array\n     *\n     * @param resourceId of the string array\n     * @return the string array\n     */\n    @SuppressWarnings(\"deprecation\")\n    public String[] getStringArray(final int resourceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getStringArray\");\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            configuration.setLocale(targetLocale);\n            return mContext.createConfigurationContext(configuration).getResources().getStringArray(resourceId);\n        } else {\n            configuration.locale = targetLocale;\n            return new ResourceManager(assetManager, metrics, configuration).getStringArray(resourceId);\n        }\n    }\n\n    /**\n     * Get a localised string\n     *\n     * @param resourceId of the string\n     * @return the string\n     */\n    @SuppressWarnings(\"deprecation\")\n    public String getString(final int resourceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getString\");\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            configuration.setLocale(targetLocale);\n            return mContext.createConfigurationContext(configuration).getResources().getString(resourceId);\n        } else {\n            configuration.locale = targetLocale;\n            return new ResourceManager(assetManager, metrics, configuration).getString(resourceId);\n        }\n    }\n\n    /**\n     * Only here in case of future functionality requirements.\n     */\n    private final class ResourceManager extends Resources {\n        public ResourceManager(final AssetManager assets, final DisplayMetrics metrics, final Configuration config) {\n            super(assets, metrics, config);\n        }\n\n        /**\n         * Return the string array associated with a particular resource ID.\n         *\n         * @param id The desired resource identifier, as generated by the aapt\n         *           tool. This integer encodes the package, type, and resource\n         *           entry. The value 0 is an invalid identifier.\n         * @return The string array associated with the resource.\n         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.\n         */\n        @Override\n        public String[] getStringArray(final int id) throws NotFoundException {\n            return super.getStringArray(id);\n        }\n\n        /**\n         * Return the string value associated with a particular resource ID,\n         * substituting the format arguments as defined in {@link Formatter}\n         * and {@link String#format}. It will be stripped of any styled text\n         * information.\n         *\n         * @param id         The desired resource identifier, as generated by the aapt\n         *                   tool. This integer encodes the package, type, and resource\n         *                   entry. The value 0 is an invalid identifier.\n         * @param formatArgs The format arguments that will be used for substitution.\n         * @return String The string data associated with the resource,\n         * stripped of styled text information.\n         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.\n         */\n        @NonNull\n        @Override\n        public String getString(final int id, final Object... formatArgs) throws NotFoundException {\n            return super.getString(id, formatArgs);\n        }\n\n        /**\n         * Return the string value associated with a particular resource ID.  It\n         * will be stripped of any styled text information.\n         *\n         * @param id The desired resource identifier, as generated by the aapt\n         *           tool. This integer encodes the package, type, and resource\n         *           entry. The value 0 is an invalid identifier.\n         * @return String The string data associated with the resource,\n         * stripped of styled text information.\n         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.\n         */\n        @NonNull\n        @Override\n        public String getString(final int id) throws NotFoundException {\n            return super.getString(id);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/localisation/SaiyResourcesHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.localisation;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\n/**\n * Utility Class to help manage speech responses for various locales, handling issues mentioned in\n * the {@link SaiyResources} class.\n * <p/>\n * Created by benrandall76@gmail.com on 25/03/2016.\n */\npublic class SaiyResourcesHelper {\n\n    /**\n     * Get the XML resource array for the {@link SupportedLanguage} which will handle and\n     * necessary variation.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param parentId the id of the XML resource array\n     * @return the XML resource array for the current {@link SupportedLanguage} or default English.\n     */\n    public static String[] getArrayResource(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                            final int parentId) {\n        final SaiyResources sr = new SaiyResources(ctx, sl);\n        final String[] resourceArray = sr.getStringArray(parentId);\n        sr.reset();\n        return resourceArray;\n    }\n\n    /**\n     * Get the XML resource String for the {@link SupportedLanguage} which will handle any\n     * necessary variation.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param parentId the id of the XML resource array\n     * @return the XML resource String for the current {@link SupportedLanguage} or default English.\n     */\n    public static String getStringResource(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                           final int parentId) {\n        final SaiyResources sr = new SaiyResources(ctx, sl);\n        final String resourceString = sr.getString(parentId);\n        sr.reset();\n        return resourceString;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/localisation/SupportedLanguage.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.localisation;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class that stores enum defaults of languages currently supported by the Recognition and Text to Speech\n * systems, as well as a language model. The application may support other languages in terms of XML\n * localisation in the UI, but the user would need to select an alternative {@link Locale} to use\n * the voice and speech features of the application itself.\n * <p>\n * Hard-coding such things is not ideal, however each language the application is translated to is a\n * laborious process and adding in an extra value each time doesn't seem too much effort in comparison...\n * <p>\n * Additional entries will be added, only when there are specific spelling or pronunciation variations\n * that the application will need to handle.\n * <p>\n * The parent locale, such as {@link Locale#ENGLISH} will not replace missing {@link Locale} variations\n * such as eng_IND. Rather, the methods will confirm a supported language and/or country variant and\n * leave any variant intact for use with Text to Speech and Voice Recognition Providers. It is a\n * specific localisation resource that we are concerned with here.\n * <p>\n * Created by benrandall76@gmail.com on 25/03/2016.\n */\npublic enum SupportedLanguage {\n\n    ENGLISH(\"en\", \"\", \"\", \"eng\", \"\", \"\", \"English\", \"English\", Locale.ENGLISH, null),\n    ENGLISH_US(\"en\", \"US\", \"en_US\", \"eng\", \"USA\", \"eng_USA\", \"English\", \"United States\", Locale.US, SupportedLanguage.ENGLISH);\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SupportedLanguage.class.getSimpleName();\n\n    private final String language;\n    private final String country;\n    private final String languageCountry;\n    private final String languageISO;\n    private final String countryISO;\n    private final String languageCountryISO;\n    private final String languageTag;\n    private final String countryTag;\n    private final Locale locale;\n    private final SupportedLanguage parent;\n\n    /**\n     * Constructor\n     *\n     * @param language           string representation\n     * @param country            string representation\n     * @param languageCountry    string representation\n     * @param languageISO        string representation\n     * @param countryISO         string representation\n     * @param languageCountryISO string representation\n     * @param languageTag        string representation\n     * @param countryTag         string representation\n     * @param locale             of the Supported TranslationLanguageBing\n     * @param parent             of the Supported TranslationLanguageBing (the language itself)\n     */\n    SupportedLanguage(final String language, final String country, final String languageCountry,\n                      final String languageISO, final String countryISO, final String languageCountryISO,\n                      final String languageTag, final String countryTag, final Locale locale,\n                      final SupportedLanguage parent) {\n        this.language = language;\n        this.country = country;\n        this.languageCountry = languageCountry;\n        this.languageISO = languageISO;\n        this.countryISO = countryISO;\n        this.languageCountryISO = languageCountryISO;\n        this.languageTag = languageTag;\n        this.countryTag = countryTag;\n        this.locale = locale;\n        this.parent = parent;\n    }\n\n    /**\n     * Get the parent {@link Locale}\n     *\n     * @return the parent Locale\n     */\n    public SupportedLanguage getParent() {\n        return parent;\n    }\n\n    /**\n     * Check if the TranslationLanguageBing has a parent language\n     *\n     * @return true if there is a parent language\n     */\n    public boolean hasParent() {\n        return parent != null;\n    }\n\n    /**\n     * Get the {@link Locale}\n     *\n     * @return the Locale\n     */\n    public Locale getLocale() {\n        return locale;\n    }\n\n    /**\n     * Get the ISO3 language string\n     *\n     * @return the ISO3 language string\n     */\n    public String getLanguageISO() {\n        return this.languageISO;\n    }\n\n    /**\n     * Get the ISO3 Country string\n     *\n     * @return the ISO3 Country string\n     */\n    public String getCountryISO() {\n        return this.countryISO;\n    }\n\n    /**\n     * Get the Country string\n     *\n     * @return the Country string\n     */\n    public String getCountry() {\n        return country;\n    }\n\n    /**\n     * Get the Country tag\n     *\n     * @return the Country tag\n     */\n    public String getCountryTag() {\n        return this.countryTag;\n    }\n\n    /**\n     * Get the language tag\n     *\n     * @return the language tag\n     */\n    public String getLanguageTag() {\n        return this.languageTag;\n    }\n\n    /**\n     * Get the language string\n     *\n     * @return the language string\n     */\n    public String getLanguage() {\n        return this.language;\n    }\n\n    /**\n     * Get the string representation of the {@link Locale}\n     *\n     * @return the string representation\n     */\n    public String getLanguageCountry() {\n        return this.languageCountry;\n    }\n\n    /**\n     * Get the string representation of the ISO3 {@link Locale}\n     *\n     * @return the string representation\n     */\n    public String getLanguageCountryISO() {\n        return this.languageCountryISO;\n    }\n\n    /**\n     * Get all supported languages\n     *\n     * @return a list of supported languages\n     */\n    public static SupportedLanguage[] getLanguages() {\n        return SupportedLanguage.values();\n    }\n\n    /**\n     * Get the supported language of the locale or the parent, to use for localised resources.\n     *\n     * @param userLocale the user's {@link Locale}\n     * @return the Supported TranslationLanguageBing\n     */\n    public static SupportedLanguage getSupportedLanguage(@NonNull final Locale userLocale) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSupportedLanguage: \" + userLocale.toString());\n        }\n\n        for (final SupportedLanguage sl : getLanguages()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getSupportedLanguage: comparing: \" + sl.getLocale().toString() + \" ~ \" + userLocale.toString());\n            }\n\n            if (sl.getLocale().equals(userLocale)) {\n                return sl;\n            }\n        }\n\n        final ArrayList<SupportedLanguage> matches = new ArrayList<>();\n\n        final String language = userLocale.getLanguage();\n        final String country = userLocale.getCountry();\n\n        if (UtilsString.notNaked(language)) {\n\n            for (final SupportedLanguage sl : getLanguages()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSupportedLanguage: comparing: \" + language + \" ~ \" + sl.getLanguage());\n                }\n\n                if (language.equalsIgnoreCase(sl.getLanguage())) {\n                    matches.add(sl);\n                }\n            }\n\n            if (!matches.isEmpty()) {\n                for (final SupportedLanguage sl : matches) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getSupportedLanguage: comparing: \" + country + \" ~ \" + sl.getCountry());\n                    }\n\n                    if (country.equalsIgnoreCase(sl.getCountry())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getSupportedLanguage: full match: \" + sl.getLanguageCountry());\n                        }\n                        return sl;\n                    }\n                }\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSupportedLanguage: no country match returning first: \" + matches.get(0).getLocale().toString());\n                }\n\n                if (matches.get(0).hasParent()) {\n                    return matches.get(0).getParent();\n                }\n\n                return matches.get(0);\n\n            }\n\n            for (final SupportedLanguage sl : getLanguages()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSupportedLanguage: comparing: \" + language + \" ~ \" + sl.getLanguageISO());\n                }\n\n                if (language.equalsIgnoreCase(sl.getLanguageISO())) {\n                    matches.add(sl);\n                }\n            }\n\n            if (!matches.isEmpty()) {\n                for (final SupportedLanguage sl : matches) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getSupportedLanguage: comparing: \" + country + \" ~ \" + sl.getCountryISO());\n                    }\n\n                    if (country.equalsIgnoreCase(sl.getCountryISO())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getSupportedLanguage: full match: \" + sl.getLanguageCountryISO());\n                        }\n                        return sl;\n                    }\n                }\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getSupportedLanguage: no countryISO match returning first: \" + matches.get(0).getLocale().toString());\n                }\n\n                if (matches.get(0).hasParent()) {\n                    return matches.get(0).getParent();\n                }\n\n                return matches.get(0);\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSupportedLanguage: language naked\" + userLocale.toString());\n            }\n        }\n\n        return ENGLISH;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/memory/Memory.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.memory;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\n\n/**\n * Class to structure the data of the previous request that Saiy processed. Such information will be\n * used in the {@link CC#COMMAND_PARDON} or if a voice recognition or text to speech error occurs\n * and we need to pick up from where we left off.\n * <p/>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class Memory {\n\n    private final String vrLanguage;\n    private final String ttsLanguage;\n    private final String utterance;\n    private final ArrayList<String> utteranceArray;\n    private final int action;\n    private final CC command;\n    private final int condition;\n    private final SupportedLanguage sl;\n\n    /**\n     * Constructor\n     *\n     * @param action         one of {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     *                       or {@link LocalRequest#ACTION_SPEAK_ONLY}\n     * @param vrLanguage     the recognition language\n     * @param ttsLanguage    the text to speech language\n     * @param utterance      the utterance that Saiy spoke\n     * @param utteranceArray the utterance array that Saiy spoke\n     * @param command        the {@link CC} command that was performed\n     * @param condition      the {@link Condition} that was applied\n     * @param sl             the {@link SupportedLanguage}\n     */\n    public Memory(final int action, @NonNull final String vrLanguage, @NonNull final String ttsLanguage,\n                  @NonNull final String utterance, @NonNull final ArrayList<String> utteranceArray,\n                  @NonNull final CC command, final int condition, @NonNull final SupportedLanguage sl) {\n        this.action = action;\n        this.vrLanguage = vrLanguage;\n        this.ttsLanguage = ttsLanguage;\n        this.utterance = utterance;\n        this.utteranceArray = utteranceArray;\n        this.command = command;\n        this.condition = condition;\n        this.sl = sl;\n    }\n\n    public int getAction() {\n        return action;\n    }\n\n    public CC getCommand() {\n        return command;\n    }\n\n    public int getCondition() {\n        return condition;\n    }\n\n    public String getVRLanguage() {\n        return vrLanguage;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public String getTTSLanguage() {\n        return ttsLanguage;\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public ArrayList<String> getUtteranceArray() {\n        return utteranceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/memory/MemoryHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.memory;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Helper class to handle retrieving {@link Memory} objects\n * <p/>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class MemoryHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = MemoryHelper.class.getSimpleName();\n\n    /**\n     * Check if we have a memory of a previous action or utterance\n     *\n     * @param ctx the application context\n     * @return true if a {@link Memory} is stored\n     */\n    public static boolean hasMemory(@NonNull final Context ctx) {\n        return SPH.getMemory(ctx) != null;\n    }\n\n    /**\n     * Get the {@link Memory} we have stored\n     *\n     * @param ctx the application context\n     * @return the {@link Memory} object\n     */\n    public static Memory getMemory(@NonNull final Context ctx) {\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final Memory memory;\n\n        if (hasMemory(ctx)) {\n\n            try {\n                memory = gson.fromJson(SPH.getMemory(ctx), Memory.class);\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"memory: \" + gson.toJson(memory));\n                }\n                return memory;\n            } catch (final JsonSyntaxException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"memory: JsonSyntaxException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"memory: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"memory: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return getUnknown(ctx);\n    }\n\n    /**\n     * No {@link Memory} was stored, so we need to let the user know. We get the utterance we will\n     * announce from the {@link PersonalityResponse} class.\n     *\n     * @param ctx the application context\n     * @return a constructed {@link Memory} object\n     */\n    private static Memory getUnknown(@NonNull final Context ctx) {\n\n        final String vrLanguage = SPH.getVRLocale(ctx).toString();\n        final String ttsLanguage = SPH.getTTSLocale(ctx).toString();\n        final ArrayList<String> utteranceArray = new ArrayList<>();\n        final int action = LocalRequest.ACTION_SPEAK_ONLY;\n        final CC command = CC.COMMAND_UNKNOWN;\n        final int condition = Condition.CONDITION_NONE;\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(SPH.getVRLocale(ctx));\n        final String utterance = PersonalityResponse.getNoMemory(ctx, sl);\n\n        return new Memory(action, vrLanguage, ttsLanguage, utterance, utteranceArray,\n                command, condition, sl);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/memory/MemoryPrepare.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.memory;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Helper class to prepare the {@link Memory} data\n * <p/>\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class MemoryPrepare {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = MemoryPrepare.class.getSimpleName();\n\n    private final String vrLanguage;\n    private final String ttsLanguage;\n    private final String utterance;\n    private final ArrayList<String> utteranceArray;\n    private final int action;\n    private final CC command;\n    private final int condition;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param bundle   the bundle of data containing all necessary actions and parameters\n     */\n    public MemoryPrepare(@NonNull final Context mContext, @NonNull final Bundle bundle) {\n        this.mContext = mContext;\n\n        vrLanguage = bundle.getString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, SPH.getVRLocale(mContext).toString());\n        ttsLanguage = bundle.getString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(mContext).toString());\n        utterance = bundle.getString(LocalRequest.EXTRA_UTTERANCE, SaiyRequestParams.SILENCE);\n\n        if (bundle.containsKey(LocalRequest.EXTRA_UTTERANCE_ARRAY)) {\n            utteranceArray = bundle.getStringArrayList(LocalRequest.EXTRA_UTTERANCE_ARRAY);\n        } else {\n            utteranceArray = new ArrayList<>();\n        }\n\n        action = bundle.getInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_UNKNOWN);\n        command = (CC) bundle.getSerializable(LocalRequest.EXTRA_COMMAND);\n        condition = bundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE);\n        sl = (SupportedLanguage) bundle.getSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE);\n    }\n\n    /**\n     * Save the memory into the the user's shared preferences, once it has been serialised.\n     */\n    public void save() {\n\n        final Memory memory = new Memory(action, vrLanguage, ttsLanguage, utterance, utteranceArray,\n                command, condition, sl);\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final String gsonString = gson.toJson(memory);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"save: gsonString: \" + gsonString);\n        }\n\n        SPH.setMemory(mContext, gsonString);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/NLUCoerce.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.apiai.NLUAPIAI;\nimport ai.saiy.android.nlu.apiai.NLUAPIAIHelper;\nimport ai.saiy.android.nlu.bluemix.NLUBluemix;\nimport ai.saiy.android.nlu.microsoft.Entity;\nimport ai.saiy.android.nlu.microsoft.Intent;\nimport ai.saiy.android.nlu.microsoft.NLUMicrosoft;\nimport ai.saiy.android.nlu.microsoft.NLUMicrosoftHelper;\nimport ai.saiy.android.nlu.nuance.Concept;\nimport ai.saiy.android.nlu.nuance.Interpretation;\nimport ai.saiy.android.nlu.nuance.NLUNuance;\nimport ai.saiy.android.nlu.nuance.NLUNuanceHelper;\nimport ai.saiy.android.nlu.saiy.ContextValue;\nimport ai.saiy.android.nlu.saiy.NLUSaiy;\nimport ai.saiy.android.nlu.saiy.NLUSaiyHelper;\nimport ai.saiy.android.nlu.wit.NLUWit;\nimport ai.saiy.android.processing.Quantum;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsMap;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to validate remote NLP models and coerce them into a generic {@link CommandRequest} object\n * for {@link Quantum} to process.\n * <p>\n * Created by benrandall76@gmail.com on 31/05/2016.\n */\npublic class NLUCoerce {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NLUCoerce.class.getSimpleName();\n\n    private CommandRequest commandRequest;\n    private final Object nluProvider;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param nluProvider     one of the remote NLP providers\n     * @param mContext        the application context\n     * @param sl              the {@link SupportedLanguage}\n     * @param vrLocale        the voice recognition {@link Locale}\n     * @param ttsLocale       the Text to Speech {@link Locale}\n     * @param confidenceArray float array of confidence scores\n     * @param resultsArray    ArrayList of recognition results\n     */\n    public NLUCoerce(@NonNull final Object nluProvider, @NonNull final Context mContext,\n                     @NonNull final SupportedLanguage sl, @NonNull final Locale vrLocale,\n                     @NonNull final Locale ttsLocale, @NonNull final float[] confidenceArray,\n                     @NonNull final ArrayList<String> resultsArray) {\n\n        this.nluProvider = nluProvider;\n        this.mContext = mContext;\n        this.sl = sl;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n\n        this.commandRequest = new CommandRequest(getVRLocale(), getTTSLocale(), getSupportedLanguage());\n        commandRequest.setResultsArray(getResultsArray());\n        commandRequest.setConfidenceArray(getConfidenceArray());\n    }\n\n    /**\n     * Coerce the NLP results into a generic {@link CommandRequest} object, validating the minimal\n     * requirements for each implementation.\n     */\n    public void coerce() {\n\n        if (nluProvider instanceof NLUMicrosoft) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUMicrosoft\");\n            }\n\n            if (validateNLUNLUMicrosoft((NLUMicrosoft) nluProvider)) {\n\n                for (final Intent i : ((NLUMicrosoft) nluProvider).getIntents()) {\n\n                    if (i.getScore() > NLUMicrosoft.MIN_THRESHOLD) {\n\n                        commandRequest.setCC(NLUConstants.intentToCC(i.getIntent()));\n\n                        if (!commandRequest.getCC().equals(CC.COMMAND_UNKNOWN)) {\n                            final NLUMicrosoftHelper microsoftHelper = new NLUMicrosoftHelper();\n                            commandRequest = microsoftHelper.prepareCommand(mContext, commandRequest,\n                                    getSupportedLanguage(), ((NLUMicrosoft) nluProvider).getEntities());\n                            if (commandRequest.isResolved()) {\n                                break;\n                            }\n                            break;\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"coerce: COMMAND_UNKNOWN\");\n                            }\n\n                            commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                            commandRequest.setResolved(false);\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"coerce: below threshold: \" + i.getScore());\n                        }\n\n                        commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                        commandRequest.setResolved(false);\n                    }\n                }\n\n                if (!commandRequest.isResolved()) {\n                    commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                    commandRequest.setResolved(false);\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"coerce: NLUMicrosoft validation failed\");\n                }\n\n                commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                commandRequest.setResolved(false);\n            }\n\n        } else if (nluProvider instanceof NLUNuance) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUNuance\");\n            }\n\n            if (validateNLUNuance((NLUNuance) nluProvider)) {\n\n                for (final Interpretation interpretation : ((NLUNuance) nluProvider).getInterpretations()) {\n\n                    if (interpretation.getAction().getIntent().getConfidence() > NLUNuance.MIN_THRESHOLD) {\n\n                        commandRequest.setCC(NLUConstants.intentToCC(\n                                interpretation.getAction().getIntent().getValue()));\n\n                        if (!commandRequest.getCC().equals(CC.COMMAND_UNKNOWN)) {\n                            final NLUNuanceHelper nuanceHelper = new NLUNuanceHelper();\n                            commandRequest = nuanceHelper.prepareCommand(mContext, commandRequest, getSupportedLanguage(),\n                                    interpretation.getConcept());\n                            if (commandRequest.isResolved()) {\n                                break;\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"coerce: COMMAND_UNKNOWN\");\n                            }\n\n                            commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                            commandRequest.setResolved(false);\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"coerce: below threshold: \" + interpretation.getAction()\n                                    .getIntent().getConfidence());\n                        }\n\n                        commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                        commandRequest.setResolved(false);\n                    }\n                }\n\n                if (!commandRequest.isResolved()) {\n                    commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                    commandRequest.setResolved(false);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"coerce: NLUNuance validation failed\");\n                }\n\n                commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                commandRequest.setResolved(false);\n            }\n        } else if (nluProvider instanceof NLUAPIAI) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUAPIAI\");\n            }\n\n            if (validateNLUAPIAI((NLUAPIAI) nluProvider)) {\n\n                commandRequest.setCC(NLUConstants.intentToCC(((NLUAPIAI) nluProvider).getIntent()));\n\n                if (!commandRequest.getCC().equals(CC.COMMAND_UNKNOWN)) {\n\n                    final NLUAPIAIHelper apiaiHelper = new NLUAPIAIHelper();\n                    commandRequest = apiaiHelper.prepareCommand(mContext, commandRequest,\n                            getSupportedLanguage(), ((NLUAPIAI) nluProvider).getParameters());\n\n                    if (!commandRequest.isResolved()) {\n                        commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                        commandRequest.setResolved(false);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"coerce: COMMAND_UNKNOWN\");\n                    }\n                    commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                    commandRequest.setResolved(false);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"coerce: NLUAPIAI validation failed\");\n                }\n\n                commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                commandRequest.setResolved(false);\n            }\n        } else if (nluProvider instanceof NLUWit) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUWit\");\n            }\n            // TODO\n        } else if (nluProvider instanceof NLUBluemix) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUBluemix\");\n            }\n            // TODO\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"coerce: instanceof NLUSaiy\");\n            }\n\n            if (validateNLUSaiy((NLUSaiy) nluProvider)) {\n\n                for (final ai.saiy.android.nlu.saiy.Intent intent : ((NLUSaiy) nluProvider).getIntents()) {\n                    commandRequest.setCC(NLUConstants.intentToCC(intent.getIntent()));\n\n                    if (!commandRequest.getCC().equals(CC.COMMAND_UNKNOWN)) {\n                        final NLUSaiyHelper saiyHelper = new NLUSaiyHelper();\n                        commandRequest = saiyHelper.prepareCommand(mContext, commandRequest,\n                                getSupportedLanguage(), intent.getEntities());\n\n                        if (commandRequest.isResolved()) {\n                            break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"coerce: COMMAND_UNKNOWN\");\n                        }\n\n                        commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                        commandRequest.setResolved(false);\n                    }\n                }\n\n                if (!commandRequest.isResolved()) {\n                    commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                    commandRequest.setResolved(false);\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"coerce: NLUSaiy validation failed\");\n                }\n\n                commandRequest.setCC(CC.COMMAND_UNKNOWN);\n                commandRequest.setResolved(false);\n            }\n        }\n\n        commandRequest.setResultsArray(getResultsArray());\n\n        new Quantum(mContext).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, commandRequest);\n    }\n\n    /**\n     * Validate the parameters prior to use.\n     *\n     * @param nluAPIAI the {@link NLUAPIAI} response object\n     * @return true if the minimum parameters are present, false otherwise.\n     */\n    private boolean validateNLUAPIAI(@NonNull final NLUAPIAI nluAPIAI) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateNLUAPIAI\");\n        }\n\n        if (UtilsString.notNaked(nluAPIAI.getIntent())) {\n            if (UtilsMap.notNaked(nluAPIAI.getParameters())) {\n                if (UtilsList.notNaked(nluAPIAI.getResults())) {\n                    if (UtilsList.notNaked(nluAPIAI.getConfidence())) {\n                        return true;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"validateNLUAPIAI: confidence naked\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"validateNLUAPIAI: results naked\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"validateNLUAPIAI: parameters naked: allowing\");\n                }\n\n                return true;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"validateNLUAPIAI: intent naked\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Validate the parameters prior to use.\n     *\n     * @param nluNuance the {@link NLUNuance} response object\n     * @return true if the minimum parameters are present, false otherwise.\n     */\n    private boolean validateNLUNuance(@NonNull final NLUNuance nluNuance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateNLUNuance\");\n        }\n\n        final List<Interpretation> interpretations = nluNuance.getInterpretations();\n\n        if (interpretations != null) {\n\n            for (final Interpretation interpretation : interpretations) {\n                if (UtilsString.notNaked(interpretation.getLiteral())) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"interpretation: getLiteral: \" + interpretation.getLiteral());\n                    }\n\n                    if (interpretation.getAction() != null) {\n                        if (interpretation.getAction().getIntent() != null) {\n                            if (UtilsString.notNaked(interpretation.getAction().getIntent().getValue())) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"interpretation: getValue: \" + interpretation.getAction()\n                                            .getIntent().getValue());\n                                    MyLog.i(CLS_NAME, \"interpretation: getConfidence: \" + interpretation.getAction()\n                                            .getIntent().getConfidence());\n                                }\n\n                                final Map<String, List<Concept>> map = interpretation.getConcept();\n\n                                if (UtilsMap.notNaked(map)) {\n\n                                    for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"key: \" + entry.getKey() + \" ~ \" + entry.getValue());\n                                        }\n\n                                        final List<Concept> concepts = entry.getValue();\n                                        if (UtilsList.notNaked(concepts)) {\n\n                                            final int conceptSize = concepts.size();\n                                            for (int i = 0; i < conceptSize; i++) {\n                                                if (UtilsString.notNaked(concepts.get(i).getLiteral())) {\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"concept: getLiteral: \"\n                                                                + concepts.get(i).getLiteral());\n                                                        MyLog.i(CLS_NAME, \"concept: getValue: \"\n                                                                + concepts.get(i).getValue());\n                                                        MyLog.i(CLS_NAME, \"concept: getRanges: \"\n                                                                + Arrays.deepToString(concepts.get(i)\n                                                                .getRanges()));\n                                                    }\n\n                                                    if (i == (conceptSize - 1)) {\n                                                        return true;\n                                                    }\n                                                } else {\n                                                    if (DEBUG) {\n                                                        MyLog.w(CLS_NAME, \"validateNLUNuance: literal naked\");\n                                                    }\n                                                    break;\n                                                }\n                                            }\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.w(CLS_NAME, \"validateNLUNuance: concepts naked\");\n                                            }\n                                            break;\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"validateNLUNuance: no concepts to examine\");\n                                    }\n                                    return true;\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"validateNLUNuance: value naked\");\n                                }\n                                break;\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"validateNLUNuance: intent null\");\n                            }\n                            break;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"validateNLUNuance: action null\");\n                        }\n                        break;\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"validateNLUNuance: literal naked\");\n                    }\n                    break;\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"validateNLUNuance: interpretations null\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Validate the parameters prior to use.\n     *\n     * @param nluMicrosoft the {@link NLUMicrosoft} response object\n     * @return true if the minimum parameters are present, false otherwise.\n     */\n    private boolean validateNLUNLUMicrosoft(@NonNull final NLUMicrosoft nluMicrosoft) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateNLUNLUMicrosoft\");\n        }\n\n        if (UtilsString.notNaked(nluMicrosoft.getQuery())) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"query: \" + nluMicrosoft.getQuery());\n            }\n\n            final List<Intent> intents = nluMicrosoft.getIntents();\n            if (UtilsList.notNaked(intents)) {\n\n                for (final Intent i : intents) {\n\n                    if (UtilsString.notNaked(i.getIntent())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getIntent: \" + i.getIntent());\n                            MyLog.i(CLS_NAME, \"getScore: \" + i.getScore());\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"validateNLUNLUMicrosoft: intent naked\");\n                        }\n                        return false;\n                    }\n                }\n\n                final List<Entity> entities = nluMicrosoft.getEntities();\n                if (entities != null) {\n\n                    final int entitiesSize = entities.size();\n\n                    if (entitiesSize > 0) {\n\n                        for (int i = 0; i < entitiesSize; i++) {\n\n                            if (UtilsString.notNaked(entities.get(i).getEntity())\n                                    && UtilsString.notNaked(entities.get(i).getType())) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"getEntity: \" + entities.get(i).getEntity());\n                                    MyLog.i(CLS_NAME, \"getType: \" + entities.get(i).getType());\n                                    MyLog.i(CLS_NAME, \"getStartIndex: \" + entities.get(i).getStartIndex());\n                                    MyLog.i(CLS_NAME, \"getEndIndex: \" + entities.get(i).getEndIndex());\n                                    MyLog.i(CLS_NAME, \"getScore: \" + entities.get(i).getScore());\n                                }\n\n                                if (i == (entitiesSize - 1)) {\n                                    return true;\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"validateNLUNLUMicrosoft: entity/type naked\");\n                                }\n                                break;\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"validateNLUNLUMicrosoft: no entities to examine\");\n                        }\n\n                        return true;\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"validateNLUNLUMicrosoft: entities null\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"validateNLUNLUMicrosoft: intents naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"validateNLUNLUMicrosoft: query naked\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Validate the parameters prior to use.\n     *\n     * @param nluSaiy the {@link NLUSaiy} response object\n     * @return true if the minimum parameters are present, false otherwise.\n     */\n    private boolean validateNLUSaiy(@NonNull final NLUSaiy nluSaiy) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateNLUSaiy\");\n        }\n\n        final List<String> results = nluSaiy.getResults();\n        if (UtilsList.notNaked(results)) {\n\n            final float[] confidence = nluSaiy.getConfidence();\n            if (UtilsList.notNaked(confidence)) {\n\n                final List<ai.saiy.android.nlu.saiy.Intent> intents = nluSaiy.getIntents();\n                if (UtilsList.notNaked(intents)) {\n\n                    for (final ai.saiy.android.nlu.saiy.Intent intent : intents) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getIntent: \" + intent.getIntent());\n                            MyLog.i(CLS_NAME, \"getType: \" + intent.getConfidence());\n                        }\n\n                        final List<ai.saiy.android.nlu.saiy.Entity> entities = intent.getEntities();\n                        if (UtilsList.notNaked(entities)) {\n                            final int entitiesSize = entities.size();\n\n                            for (int i = 0; i < entitiesSize; i++) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"getVoiceName: \" + entities.get(i).getName());\n                                    MyLog.i(CLS_NAME, \"getVoiceName: \" + entities.get(i).getValue());\n                                    MyLog.i(CLS_NAME, \"getVoiceName: \" + entities.get(i).getConfidence());\n                                    MyLog.i(CLS_NAME, \"getContextual: \" + entities.get(i).getContextual());\n                                    MyLog.i(CLS_NAME, \"getVoiceName: \" + Arrays.toString(entities.get(i).getIndex()));\n                                }\n\n                                final List<ai.saiy.android.nlu.saiy.Context> contexts = entities.get(i).getContextual();\n                                for (final ai.saiy.android.nlu.saiy.Context context : contexts) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"getContext: \" + context.getContext());\n                                        MyLog.i(CLS_NAME, \"getConfidence: \" + context.getConfidence());\n                                    }\n\n                                    final List<ContextValue> contextValues = context.getContextValues();\n                                    for (final ContextValue contextValue : contextValues) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"getIdentifier: \" + contextValue.getIdentifier());\n                                        }\n                                    }\n                                }\n\n                                if (i == (entitiesSize - 1)) {\n                                    return true;\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"validateNLUSaiy: entities naked\");\n                            }\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"validateNLUSaiy: intents naked\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"validateNLUSaiy: confidence naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"validateNLUSaiy: results naked\");\n            }\n        }\n\n        return false;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    private float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/NLUConstants.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Constants used for Nuance NLU\n * <p/>\n * Created by benrandall76@gmail.com on 15/02/2016.\n */\npublic class NLUConstants {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = NLUConstants.class.getSimpleName();\n\n    // Translate command\n    public static final String TRANSLATE = \"translate\";\n    public static final String TEXT_TO_TRANSLATE = \"text_to_translate\";\n    public static final String TRANSLATE_LANGUAGE = \"translate_language\";\n    public static final String LANGUAGE = \"language\";\n\n    // User Name command\n    public static final String USER_NAME = \"user_name\";\n    public static final String NAME_USER = \"name_user\";\n\n    // Unknown command\n    public static final String UNKNOWN = \"unknown\";\n    public static final String NO_MATCH = \"NO_MATCH\";\n    public static final String NONE = \"None\";\n\n    // Battery command\n    public static final String BATTERY = \"battery\";\n    public static final String BATTERY_TYPE = \"battery_type\";\n\n    // Spell command\n    public static final String SPELL = \"spell\";\n    public static final String TEXT_TO_SPELL = \"text_to_spell\";\n\n    // Music recognition command\n    public static final String SONG_RECOGNITION = \"music_recognition\";\n\n    // Pardon command\n    public static final String PARDON = \"pardon\";\n\n    // Vocal identification command\n    public static final String VOCAL_IDENTITY = \"vocal_identity\";\n\n    // Tasker command\n    public static final String TASKER_TASK = \"tasker_task\";\n    public static final String TASKER_TASK_NAME = \"tasker_task_name\";\n\n    // Wolfram Alpha command\n    public static final String WOLFRAM_ALPHA = \"wolfram_alpha\";\n    public static final String QUESTION_CONTENT = \"question_content\";\n\n    // Emotion command\n    public static final String EMOTION = \"emotion\";\n\n    // Emotion command\n    public static final String HOTWORD = \"hotword\";\n\n    public static final String LITERAL = \"literal\";\n\n    /**\n     * Compare the literal intent String name and match it to one of the {@link CC} constants\n     *\n     * @param name of the intent\n     * @return the corresponding {@link CC} value\n     */\n    public static CC intentToCC(@NonNull final String name) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"intentToCC\");\n        }\n        if (name.matches(TRANSLATE)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_TRANSLATE\");\n            }\n            return CC.COMMAND_TRANSLATE;\n        } else if (name.matches(USER_NAME)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_USER_NAME\");\n            }\n            return CC.COMMAND_USER_NAME;\n        } else if (name.matches(PARDON)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_PARDON\");\n            }\n            return CC.COMMAND_PARDON;\n        } else if (name.matches(BATTERY)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_BATTERY\");\n            }\n            return CC.COMMAND_BATTERY;\n        } else if (name.matches(SPELL)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_SPELL\");\n            }\n            return CC.COMMAND_SPELL;\n        } else if (name.matches(SONG_RECOGNITION)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_SONG_RECOGNITION\");\n            }\n            return CC.COMMAND_SONG_RECOGNITION;\n        } else if (name.matches(VOCAL_IDENTITY)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_VOICE_IDENTIFY\");\n            }\n            return CC.COMMAND_VOICE_IDENTIFY;\n        } else if (name.matches(TASKER_TASK)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_TASKER\");\n            }\n            return CC.COMMAND_TASKER;\n        } else if (name.matches(WOLFRAM_ALPHA)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_WOLFRAM_ALPHA\");\n            }\n            return CC.COMMAND_WOLFRAM_ALPHA;\n        } else if (name.matches(EMOTION)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_EMOTION\");\n            }\n            return CC.COMMAND_EMOTION;\n        } else if (name.matches(HOTWORD)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: COMMAND_HOTWORD\");\n            }\n            return CC.COMMAND_HOTWORD;\n        } else if (name.matches(UNKNOWN) || name.matches(NO_MATCH) || name.matches(NONE)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"intentToCC: UNKNOWN/NO_MATCH\");\n            }\n            return CC.COMMAND_UNKNOWN;\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"intentToCC: none\");\n            }\n            return CC.COMMAND_UNKNOWN;\n        }\n    }\n\n    /**\n     * Compare the literal intent String names and match them to one of the {@link CC} constants\n     *\n     * @param nameArray of intent names\n     * @return an {@code ArrayList<String>} of the {@link CC} values\n     */\n    public static ArrayList<CC> intentToCC(@NonNull final ArrayList<String> nameArray) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"intentToCC\");\n        }\n\n        final ArrayList<CC> ccArray = new ArrayList<>();\n\n        for (final String name : nameArray) {\n\n            if (name.matches(TRANSLATE)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_TRANSLATE\");\n                }\n                ccArray.add(CC.COMMAND_TRANSLATE);\n            } else if (name.matches(USER_NAME)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_USER_NAME\");\n                }\n                ccArray.add(CC.COMMAND_USER_NAME);\n            } else if (name.matches(SPELL)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_SPELL\");\n                }\n                ccArray.add(CC.COMMAND_SPELL);\n            } else if (name.matches(BATTERY)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_BATTERY\");\n                }\n                ccArray.add(CC.COMMAND_BATTERY);\n            } else if (name.matches(PARDON)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_PARDON\");\n                }\n                ccArray.add(CC.COMMAND_PARDON);\n            } else if (name.matches(SONG_RECOGNITION)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: COMMAND_SONG_RECOGNITION\");\n                }\n                ccArray.add(CC.COMMAND_SONG_RECOGNITION);\n            } else if (name.matches(UNKNOWN) || name.matches(NO_MATCH)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"intentToCC: UNKNOWN/NO_MATCH\");\n                }\n                ccArray.add(CC.COMMAND_UNKNOWN);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"intentToCC: none\");\n                }\n                ccArray.add(CC.COMMAND_UNKNOWN);\n            }\n        }\n\n        return ccArray;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/apiai/NLUAPIAI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.apiai;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.JsonElement;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class NLUAPIAI {\n\n    private final ArrayList<String> results;\n    private final float[] confidence;\n    private final String intent;\n    private final HashMap<String, JsonElement> parameters;\n\n    public NLUAPIAI(@NonNull final float[] confidence, @NonNull final ArrayList<String> results,\n                    @NonNull final String intent, @NonNull final HashMap<String, JsonElement> parameters) {\n        this.confidence = confidence;\n        this.results = results;\n        this.intent = intent;\n        this.parameters = parameters;\n    }\n\n    public float[] getConfidence() {\n        return confidence;\n    }\n\n    public ArrayList<String> getResults() {\n        return results;\n    }\n\n    public String getIntent() {\n        return intent;\n    }\n\n    public HashMap<String, JsonElement> getParameters() {\n        return parameters;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/apiai/NLUAPIAIHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.apiai;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.JsonElement;\n\nimport java.util.HashMap;\n\nimport ai.saiy.android.command.battery.CommandBatteryValues;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.spell.CommandSpellValues;\nimport ai.saiy.android.command.tasker.CommandTaskerValues;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.username.CommandUserNameValues;\nimport ai.saiy.android.command.wolframalpha.CommandWolframAlphaValues;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUConstants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsMap;\n\n/**\n * Created by benrandall76@gmail.com on 05/06/2016.\n */\npublic class NLUAPIAIHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NLUAPIAIHelper.class.getSimpleName();\n\n    /**\n     * Coerce the parameters into a {@link CommandRequest} object\n     *\n     * @param ctx            the application context\n     * @param commandRequest the {@link CommandRequest}\n     * @param sl             the {@link SupportedLanguage}\n     * @param parameters     the parameters unique to the NLP provider\n     * @return the populated {@link CommandRequest} object\n     */\n    public CommandRequest prepareCommand(@NonNull final Context ctx,\n                                         @NonNull final CommandRequest commandRequest,\n                                         @NonNull final SupportedLanguage sl,\n                                         @NonNull final HashMap<String, JsonElement> parameters) {\n\n        switch (commandRequest.getCC()) {\n\n            case COMMAND_UNKNOWN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_UNKNOWN\");\n                }\n                break;\n            case COMMAND_CANCEL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_CANCEL\");\n                }\n                break;\n            case COMMAND_SPELL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL\");\n                }\n\n                final CommandSpellValues csv = new CommandSpellValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.TEXT_TO_SPELL)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL: extraction complete\");\n                        }\n\n                        csv.setText(parameters.get(NLUConstants.TEXT_TO_SPELL).getAsString());\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(csv);\n\n                break;\n            case COMMAND_TRANSLATE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE\");\n                }\n\n                final CommandTranslateValues ctv = new CommandTranslateValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.LANGUAGE)\n                            && parameters.containsKey(NLUConstants.TEXT_TO_TRANSLATE)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE: extraction complete\");\n                        }\n\n                        ctv.setText(parameters.get(NLUConstants.TEXT_TO_TRANSLATE).getAsString());\n                        ctv.setLanguage(parameters.get(NLUConstants.LANGUAGE).getAsString());\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(ctv);\n\n                break;\n            case COMMAND_SONG_RECOGNITION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SONG_RECOGNITION: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_PARDON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_PARDON: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_USER_NAME:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME\");\n                }\n\n                final CommandUserNameValues cunv = new CommandUserNameValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.NAME_USER)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME: extraction complete\");\n                        }\n\n                        cunv.setName(parameters.get(NLUConstants.NAME_USER).getAsString());\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(cunv);\n\n                break;\n            case COMMAND_BATTERY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY\");\n                }\n\n                final CommandBatteryValues cbv = new CommandBatteryValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.BATTERY_TYPE)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY: extraction complete\");\n                        }\n\n                        cbv.setTypeString(parameters.get(NLUConstants.BATTERY_TYPE).getAsString());\n                        cbv.setType(cbv.stringToType(ctx, sl, parameters.get(NLUConstants.BATTERY_TYPE)\n                                .getAsString()));\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(cbv);\n\n                break;\n            case COMMAND_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_HOTWORD\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMOTION\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_VOICE_IDENTIFY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_VOICE_IDENTIFY\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_TASKER:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER\");\n                }\n\n                final CommandTaskerValues taskerValues = new CommandTaskerValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.TASKER_TASK_NAME)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER: extraction complete\");\n                        }\n\n                        taskerValues.setTaskName(parameters.get(NLUConstants.TASKER_TASK_NAME).getAsString());\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(taskerValues);\n\n                break;\n            case COMMAND_WOLFRAM_ALPHA:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA\");\n                }\n\n                final CommandWolframAlphaValues cwav = new CommandWolframAlphaValues();\n\n                if (UtilsMap.notNaked(parameters)) {\n                    if (parameters.containsKey(NLUConstants.QUESTION_CONTENT)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA: extraction complete\");\n                        }\n\n                        cwav.setQuestion(parameters.get(NLUConstants.QUESTION_CONTENT).getAsString());\n                        commandRequest.setResolved(true);\n                    }\n                }\n\n                commandRequest.setVariableData(cwav);\n\n                break;\n            case COMMAND_EMPTY_ARRAY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMPTY_ARRAY\");\n                }\n                break;\n            case COMMAND_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_CUSTOM\");\n                }\n                break;\n            case COMMAND_SOMETHING_WEIRD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SOMETHING_WEIRD\");\n                }\n                break;\n        }\n\n        return commandRequest;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/apiai/RemoteAPIAI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.apiai;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.google.gson.GsonBuilder;\n\nimport ai.api.AIConfiguration;\nimport ai.api.AIDataService;\nimport ai.api.AIServiceException;\nimport ai.api.model.AIRequest;\nimport ai.api.model.AIResponse;\nimport ai.saiy.android.api.language.nlu.NLULanguageAPIAI;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class RemoteAPIAI {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RemoteAPIAI.class.getSimpleName();\n\n    private final String utterance;\n    private final AIDataService aiDataService;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param apiKey   the API AI api key\n     * @param vrLocale the {@link NLULanguageAPIAI}\n     */\n    public RemoteAPIAI(@NonNull final Context mContext,\n                       @NonNull final String utterance,\n                       @NonNull final String apiKey,\n                       @NonNull final NLULanguageAPIAI vrLocale) {\n        this.utterance = utterance;\n\n        final AIConfiguration config = new AIConfiguration(apiKey,\n                AIConfiguration.SupportedLanguages.fromLanguageTag(vrLocale.getLocaleString()),\n                AIConfiguration.RecognitionEngine.System);\n\n        aiDataService = new AIDataService(mContext, config);\n    }\n\n    public Pair<Boolean, String> fetch() {\n\n        final AIRequest aiRequest = new AIRequest();\n        aiRequest.setQuery(utterance);\n\n        try {\n\n            final AIResponse response = aiDataService.request(aiRequest);\n\n            if (response != null) {\n\n                final String gsonString = new GsonBuilder().disableHtmlEscaping().create().toJson(response);\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"gsonString: \" + response.toString());\n                }\n\n                return new Pair<>(true, gsonString);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"response null\");\n                }\n            }\n\n        } catch (final AIServiceException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"AIResponse AIServiceException\");\n                e.printStackTrace();\n            }\n        }\n\n        return new Pair<>(false, null);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/apiai/ResolveAPIAI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.apiai;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.api.model.AIResponse;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class ResolveAPIAI {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveAPIAI.class.getSimpleName();\n\n    private NLUAPIAI nluAPIAI;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveAPIAI(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                        @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                        @NonNull final float[] confidenceArray,\n                        @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final String gsonResponse) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        final AIResponse response = gson.fromJson(gsonResponse, new TypeToken<AIResponse>() {\n        }.getType());\n\n        nluAPIAI = new NLUAPIAI(confidenceArray, resultsArray,\n                response.getResult().getMetadata().getIntentName(), response.getResult().getParameters());\n\n        new NLUCoerce(getNLUAPIAI(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    public NLUAPIAI getNLUAPIAI() {\n        return nluAPIAI;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/bluemix/Alternative.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.bluemix;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class Alternative {\n\n    @SerializedName(\"transcript\")\n    private final String transcript;\n\n    @SerializedName(\"confidence\")\n    private final float confidence;\n\n    public Alternative(final float confidence, @NonNull final String transcript) {\n        this.confidence = confidence;\n        this.transcript = transcript;\n    }\n\n    public float getConfidence() {\n        return confidence;\n    }\n\n    public String getTranscript() {\n        return transcript;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/bluemix/NLUBluemix.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.bluemix;\n\nimport android.support.annotation.Nullable;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class NLUBluemix {\n\n    @SerializedName(\"state\")\n    private final String state;\n\n    @SerializedName(\"result_index\")\n    private final long resultIndex;\n\n    @SerializedName(\"results\")\n    private final List<Result> results;\n\n    public NLUBluemix(final long resultIndex, @Nullable final String state, @Nullable final List<Result> results) {\n        this.resultIndex = resultIndex;\n        this.state = state;\n        this.results = results;\n    }\n\n    public long getResultIndex() {\n        return resultIndex;\n    }\n\n    public List<Result> getResults() {\n        return results;\n    }\n\n    public String getState() {\n        return state;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/bluemix/ResolveBluemix.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.bluemix;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class ResolveBluemix {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveBluemix.class.getSimpleName();\n\n    private NLUBluemix nluBluemix;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveBluemix(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                          @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                          @NonNull final float[] confidenceArray,\n                          @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final NLUBluemix nluBluemix) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        this.nluBluemix = nluBluemix;\n\n        new NLUCoerce(getNLUBluemix(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    public NLUBluemix getNLUBluemix() {\n        return nluBluemix;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/bluemix/Result.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.bluemix;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class Result {\n\n    @SerializedName(\"alternatives\")\n    private final List<Alternative> alternatives;\n\n    @SerializedName(\"final\")\n    private final boolean isFinal;\n\n    public Result(@NonNull final List<Alternative> alternatives, final boolean isFinal) {\n        this.alternatives = alternatives;\n        this.isFinal = isFinal;\n    }\n\n    public List<Alternative> getAlternatives() {\n        return alternatives;\n    }\n\n    public boolean isFinal() {\n        return isFinal;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/AlgorithmicContainer.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.algorithms.Algorithm;\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class AlgorithmicContainer {\n\n    private boolean exactMatch;\n    private double score;\n    private String input;\n    private String genericMatch;\n    private Algorithm algorithm;\n    private Object variableData;\n    private int parentPosition;\n\n    public int getParentPosition() {\n        return parentPosition;\n    }\n\n    public void setParentPosition(final int parentPosition) {\n        this.parentPosition = parentPosition;\n    }\n\n    public String getInput() {\n        return input;\n    }\n\n    public void setInput(@NonNull final String input) {\n        this.input = input;\n    }\n\n    public Algorithm getAlgorithm() {\n        return algorithm;\n    }\n\n    public void setAlgorithm(@NonNull final Algorithm algorithm) {\n        this.algorithm = algorithm;\n    }\n\n    public boolean isExactMatch() {\n        return exactMatch;\n    }\n\n    public void setExactMatch(final boolean exactMatch) {\n        this.exactMatch = exactMatch;\n    }\n\n    public String getGenericMatch() {\n        return genericMatch;\n    }\n\n    public void setGenericMatch(@NonNull final String genericMatch) {\n        this.genericMatch = genericMatch;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public void setScore(final double score) {\n        this.score = score;\n    }\n\n    public Object getVariableData() {\n        return variableData;\n    }\n\n    public void setVariableData(@NonNull final Object variableData) {\n        this.variableData = variableData;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/AlgorithmicResolver.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerHelper;\nimport ai.saiy.android.algorithms.distance.levenshtein.LevenshteinHelper;\nimport ai.saiy.android.algorithms.doublemetaphone.DoubleMetaphoneHelper;\nimport ai.saiy.android.algorithms.fuzzy.FuzzyHelper;\nimport ai.saiy.android.algorithms.metaphone.MetaphoneHelper;\nimport ai.saiy.android.algorithms.mongeelkan.MongeElkanHelper;\nimport ai.saiy.android.algorithms.needlemanwunch.NeedlemanWunschHelper;\nimport ai.saiy.android.algorithms.soundex.SoundexHelper;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class AlgorithmicResolver {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = AlgorithmicResolver.class.getSimpleName();\n\n    private final long THREADS_TIMEOUT;\n    public static final long THREADS_TIMEOUT_500 = 500L;\n    public static final long THREADS_TIMEOUT_2000 = 2000L;\n\n    private final Context mContext;\n    private final ArrayList<String> inputData;\n    private final ArrayList<?> genericData;\n    private final Algorithm[] algorithms;\n    private final Locale loc;\n    private final ExecutorService executorService;\n    private final List<Callable<Object>> callableList;\n    private AlgorithmicContainer algorithmicContainer = null;\n    private final boolean precision;\n\n    public AlgorithmicResolver(@NonNull final Context mContext, @NonNull final Algorithm[] algorithms,\n                               @NonNull final Locale loc, @NonNull final ArrayList<String> inputData,\n                               @NonNull final ArrayList<?> genericData, final long timeout, final boolean precision) {\n        this.mContext = mContext;\n        this.genericData = genericData;\n        this.inputData = inputData;\n        this.algorithms = algorithms;\n        this.loc = loc;\n        this.THREADS_TIMEOUT = timeout;\n        this.precision = precision;\n\n        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n        callableList = new ArrayList<>(this.algorithms.length);\n    }\n\n    public AlgorithmicContainer resolve() {\n\n        final long then = System.nanoTime();\n\n        for (final Algorithm algorithm : algorithms) {\n\n            switch (algorithm) {\n\n                case JARO_WINKLER:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: JARO_WINKLER\");\n                    }\n\n                    callableList.add(new JaroWinklerHelper(mContext, genericData, inputData, loc));\n                    break;\n                case LEVENSHTEIN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: LEVENSHTEIN\");\n                    }\n\n                    callableList.add(new LevenshteinHelper(mContext, genericData, inputData, loc));\n                    break;\n                case SOUNDEX:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: SOUNDEX\");\n                    }\n\n                    callableList.add(new SoundexHelper(mContext, genericData, inputData, loc));\n                    break;\n                case METAPHONE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: METAPHONE\");\n                    }\n\n                    callableList.add(new MetaphoneHelper(mContext, genericData, inputData, loc));\n                    break;\n                case DOUBLE_METAPHONE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: DOUBLE_METAPHONE\");\n                    }\n\n                    callableList.add(new DoubleMetaphoneHelper(mContext, genericData, inputData, loc));\n                    break;\n                case FUZZY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: FUZZY\");\n                    }\n\n                    callableList.add(new FuzzyHelper(mContext, genericData, inputData, loc));\n                    break;\n                case NEEDLEMAN_WUNCH:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: NEEDLEMAN_WUNCH\");\n                    }\n\n                    callableList.add(new NeedlemanWunschHelper(mContext, genericData, inputData, loc));\n                    break;\n                case MONGE_ELKAN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Running: MONGE_ELKAN\");\n                    }\n\n                    callableList.add(new MongeElkanHelper(mContext, genericData, inputData, loc));\n                    break;\n\n            }\n        }\n\n        final ArrayList<AlgorithmicContainer> algorithmicContainerArray = new ArrayList<>();\n\n        try {\n\n            final List<Future<Object>> futures = executorService.invokeAll(callableList,\n                    THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n            for (final Future<Object> future : futures) {\n                algorithmicContainerArray.add((AlgorithmicContainer) future.get());\n            }\n\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final CancellationException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: CancellationException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                e.printStackTrace();\n            }\n        } finally {\n            executorService.shutdown();\n        }\n\n        if (!algorithmicContainerArray.isEmpty()) {\n            algorithmicContainerArray.removeAll(Collections.<AlgorithmicContainer>singleton(null));\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"algorithms returned \" + algorithmicContainerArray.size() + \" matches\");\n                for (final AlgorithmicContainer a : algorithmicContainerArray) {\n                    MyLog.i(CLS_NAME, \"Potentials: \" + a.getAlgorithm().name() + \" ~ \"\n                            + a.getGenericMatch() + \" ~ \" + a.getInput() + \" ~ \"\n                            + a.getScore());\n                }\n            }\n\n            AlgorithmicContainer ac;\n            final ListIterator<AlgorithmicContainer> itr = algorithmicContainerArray.listIterator();\n\n            while (itr.hasNext()) {\n                ac = itr.next();\n                if (ac == null) {\n                    itr.remove();\n                } else {\n\n                    if (ac.isExactMatch()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"exact match: \" + ac.getAlgorithm().name() + \" ~ \"\n                                    + ac.getGenericMatch() + \" ~ \" + ac.getInput() + \" ~ \"\n                                    + ac.getScore());\n                        }\n                        algorithmicContainer = ac;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if (algorithmicContainer == null && !algorithmicContainerArray.isEmpty()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"No exact match, but have \" + algorithmicContainerArray.size() + \" commands\");\n                for (final AlgorithmicContainer c : algorithmicContainerArray) {\n                    MyLog.i(CLS_NAME, \"before order: \" + c.getGenericMatch() + \" ~ \" + c.getScore());\n                }\n            }\n\n            Collections.sort(algorithmicContainerArray, new Comparator<AlgorithmicContainer>() {\n                @Override\n                public int compare(final AlgorithmicContainer a1, final AlgorithmicContainer a2) {\n                    return Double.compare(a2.getScore(), a1.getScore());\n                }\n            });\n\n            if (DEBUG) {\n                for (final AlgorithmicContainer a : algorithmicContainerArray) {\n                    MyLog.i(CLS_NAME, \"after order: \" + a.getGenericMatch() + \" ~ \" + a.getScore());\n                }\n            }\n\n            algorithmicContainer = algorithmicContainerArray.get(0);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"match: \" + algorithmicContainer.getAlgorithm().name() + \" ~ \"\n                        + algorithmicContainer.getGenericMatch() + \" ~ \" + algorithmicContainer.getInput()\n                        + \" ~ \" + algorithmicContainer.getScore());\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return algorithmicContainer;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/FrequencyAnalysis.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport org.apache.commons.collections4.CollectionUtils;\n\nimport java.util.ArrayList;\nimport java.util.Map;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 09/02/2016.\n * <p/>\n * This class analyses the frequency of detected commands and weights them based on quantity\n * and corresponding confidence scores.\n * <p/>\n * If the command detected to be the most frequent is not above the threshold of the sum of the\n * first n commands (in this deliberate case, n=2) then further analysis is done.\n * <p/>\n * NOTE - based on studying many recognition providers results, which use association models to\n * return results, the global speech data becomes diluted in favour of related possibilities. If\n * you say quite clearly to Google Voice Search 'turn on Bluetooth', it will return 'turn off\n * Bluetooth' and 'turn on WiFi' in the results, albeit with a low(er) confidence score.\n * <p/>\n * Additionally, it is possible that amalgamating the speech data could result in the positive\n * detection of a command. Consider the response of 'What's the weather like in blue stork' as well as\n * 'What's the feather spike in new york', combined would identify a weather request and the relevant\n * location detail. This may seem like an usual case, but if you wonder why I have so much logging\n * in the app, then now you know.............\n * <p/>\n * Not using a deliberate language model will negate the above, but I mention now to allow you\n * to learn from my greyhound (=grey hair (Nuance recognition joke (to prove a point))).\n */\npublic final class FrequencyAnalysis {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FrequencyAnalysis.class.getSimpleName();\n\n    private static final int THRESHOLD = 65;\n\n    private final ArrayList<Pair<CC, Float>> commandArray;\n\n    /**\n     * Constructor\n     *\n     * @param commandArray containing the command frequency\n     */\n    public FrequencyAnalysis(@NonNull final ArrayList<Pair<CC, Float>> commandArray) {\n        this.commandArray = commandArray;\n    }\n\n    /**\n     * Sort the ArrayList<Integer> to decide on the most probable command.\n     *\n     * @return the constant command integer {@link CC}\n     */\n    public CC analyse() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"analyse\");\n        }\n\n        long then = System.nanoTime();\n        final CC commandInt;\n\n        switch (commandArray.size()) {\n\n            case 0:\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"empty commandArray\");\n                }\n                commandInt = CC.COMMAND_UNKNOWN;\n                break;\n            case 1:\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"Only one - unanimous\");\n                }\n                commandInt = commandArray.get(0).first;\n                break;\n            default:\n\n                final ArrayList<CC> ccArray = new ArrayList<>();\n                for (final Pair<CC, Float> pair : commandArray) {\n                    ccArray.add(pair.first);\n                }\n\n                if (ccArray.size() > 1) {\n\n                    final Map<CC, Integer> cardinalityMap = CollectionUtils.getCardinalityMap(ccArray);\n\n                    CC firstCommand = CC.COMMAND_UNKNOWN;\n                    int firstQuantity = 0;\n                    CC secondCommand = CC.COMMAND_UNKNOWN;\n                    int secondQuantity = 0;\n\n                    int count = 0;\n\n                    for (final Map.Entry<CC, Integer> e : cardinalityMap.entrySet()) {\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"cardinalityMap: Quantity: \"\n                                    + e.getValue() + \" Command: \" + e.getKey());\n                        }\n\n                        if (count == 0) {\n                            firstCommand = e.getKey();\n                            firstQuantity = e.getValue();\n                        } else if (count == 1) {\n                            secondCommand = e.getKey();\n                            secondQuantity = e.getValue();\n                            break;\n                        }\n\n                        count++;\n                    }\n\n                    if (DEBUG) {\n                        MyLog.d(CLS_NAME, \"firstCommand: \" + firstCommand.name());\n                        MyLog.d(CLS_NAME, \"firstQuantity: \" + firstQuantity);\n                        MyLog.d(CLS_NAME, \"secondCommand: \" + secondCommand.name());\n                        MyLog.d(CLS_NAME, \"secondQuantity: \" + secondQuantity);\n                    }\n\n                    final double total = firstQuantity + secondQuantity;\n\n                    final double percentage = ((firstQuantity / total) * 100);\n\n                    if (percentage > THRESHOLD) {\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"Percentage: \" + percentage + \"%\");\n                        }\n\n                        commandInt = firstCommand;\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"Percentage: \" + percentage + \"%\");\n                        }\n\n                        final float firstConfidence = commandArray.get(ccArray.indexOf(firstCommand)).second;\n                        final float secondConfidence = commandArray.get(ccArray.indexOf(secondCommand)).second;\n\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"firstConfidence: \" + String.valueOf(firstConfidence));\n                            MyLog.v(CLS_NAME, \"secondConfidence: \" + String.valueOf(secondConfidence));\n                        }\n\n                        if (firstConfidence > secondConfidence) {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"firstConfidence: higher confidence\");\n                            }\n                            commandInt = firstCommand;\n                        } else if (secondConfidence > firstConfidence) {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"secondConfidence: higher confidence\");\n                            }\n                            commandInt = secondCommand;\n                        } else {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"equal confidence\");\n                            }\n                            if (firstQuantity > secondQuantity) {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"first: had more entries\");\n                                }\n                                commandInt = firstCommand;\n                            } else if (secondQuantity > firstQuantity) {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"second: had more entries\");\n                                }\n                                commandInt = secondCommand;\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"dead-heat: Selecting priority commands or first-come-first-serve\");\n                                }\n\n                                if (secondCommand == CC.COMMAND_USER_NAME) {\n                                    commandInt = secondCommand;\n                                } else {\n                                    commandInt = firstCommand;\n                                }\n                            }\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"unanimous\");\n                    }\n                    commandInt = commandArray.get(0).first;\n                }\n\n                break;\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"returning command ~ \" + commandInt);\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return commandInt;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/InitStrings.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.battery.Battery;\nimport ai.saiy.android.command.cancel.Cancel;\nimport ai.saiy.android.command.emotion.Emotion;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.hotword.Hotword;\nimport ai.saiy.android.command.pardon.Pardon;\nimport ai.saiy.android.command.songrecognition.SongRecognition;\nimport ai.saiy.android.command.spell.Spell;\nimport ai.saiy.android.command.tasker.Tasker;\nimport ai.saiy.android.command.translate.Translate;\nimport ai.saiy.android.command.username.UserName;\nimport ai.saiy.android.command.vocalrecognition.VocalRecognition;\nimport ai.saiy.android.command.wolframalpha.WolframAlpha;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\npublic final class InitStrings {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = InitStrings.class.getSimpleName();\n\n    private static final long THREADS_TIMEOUT = 1000L;\n\n    private final List<Callable<ArrayList<Pair<CC, Float>>>> callableList;\n\n    private static String testString;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public InitStrings(@NonNull final Context mContext) {\n\n        callableList = new ArrayList<>();\n\n        if (testString != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"test string initialised. Returning\");\n            }\n            return;\n        }\n\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(SPH.getVRLocale(mContext));\n        final SaiyResources sr = new SaiyResources(mContext, sl);\n\n        testString = sr.getString(R.string.test_string);\n\n        final ArrayList<String> voiceData = new ArrayList<>();\n        final float[] confidence = new float[]{};\n\n        callableList.add(new Cancel(sr, sl, voiceData, confidence));\n        callableList.add(new Spell(sr, sl, voiceData, confidence));\n        callableList.add(new Translate(sr, sl, voiceData, confidence));\n        callableList.add(new Pardon(sr, sl, voiceData, confidence));\n        callableList.add(new UserName(sr, sl, voiceData, confidence));\n        callableList.add(new SongRecognition(sr, sl, voiceData, confidence));\n        callableList.add(new Battery(sr, sl, voiceData, confidence));\n        callableList.add(new WolframAlpha(sr, sl, voiceData, confidence));\n        callableList.add(new Tasker(sr, sl, voiceData, confidence));\n        callableList.add(new Emotion(sr, sl, voiceData, confidence));\n        callableList.add(new Hotword(sr, sl, voiceData, confidence));\n        callableList.add(new VocalRecognition(sr, sl, voiceData, confidence));\n        sr.reset();\n    }\n\n    public void init() {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"init: availableProcessors: \" + Runtime.getRuntime().availableProcessors());\n        }\n\n        final long then = System.nanoTime();\n\n        final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n\n        try {\n\n            final List<Future<ArrayList<Pair<CC, Float>>>> futures = executorService.invokeAll(callableList,\n                    THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n            for (final Future<ArrayList<Pair<CC, Float>>> future : futures) {\n                future.get();\n            }\n\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final CancellationException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: CancellationException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                e.printStackTrace();\n            }\n        } finally {\n            executorService.shutdown();\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/Profanity.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 09/02/2016.\n * <p/>\n * Voice recognition providers profanity filters report multiple *'s\n * to obscure the troublesome bad word....\n * <p/>\n * This helper removes entries that contain them in order\n * to avoid regex crashes when String matching later in our processing.\n * <p/>\n * The exception to the rule is when a 'calculate' command\n * may have been detected, in which case the * may denote a multiplication operation\n * and will be handled carefully elsewhere.\n * <p/>\n * Encouraging users to turn off their profanity filters isn't ideal for users who have tender ears,\n * but literal string matching across the application is so much bl**dy effort...\n */\npublic final class Profanity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Profanity.class.getSimpleName();\n\n    private final ArrayList<String> voiceData;\n    private static Pattern pCalculate;\n    private static final Pattern pProfanity = Pattern.compile(\"[\\\\*]+\");\n\n    private static String calculate;\n\n    /**\n     * Constructor\n     *\n     * @param mContext  the application context\n     * @param voiceData the array of voice data\n     * @param sl        the {@link SupportedLanguage}\n     */\n    public Profanity(@NonNull final Context mContext, @NonNull final ArrayList<String> voiceData,\n                     @NonNull final SupportedLanguage sl) {\n        this.voiceData = voiceData;\n\n        if (calculate == null || pCalculate == null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialising strings\");\n            }\n            final SaiyResources sr = new SaiyResources(mContext, sl);\n            initStrings(sr);\n            sr.reset();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"strings initialised\");\n            }\n        }\n    }\n\n    private static void initStrings(@NonNull final SaiyResources sr) {\n        calculate = sr.getString(ai.saiy.android.R.string.calculate);\n        pCalculate = Pattern.compile(\".*\\\\b\" + calculate + \"\\\\b.*\");\n    }\n\n    /**\n     * Loop through the voice data, removing any occurrences that contain the filtered profanity.\n     *\n     * @return the remaining entries\n     */\n    public ArrayList<String> remove() {\n\n        final long then = System.nanoTime();\n        final Iterator<String> itr = voiceData.iterator();\n\n        String vd;\n        while (itr.hasNext()) {\n            vd = itr.next();\n\n            if (!pCalculate.matcher(vd).find() && pProfanity.matcher(vd).find()) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"vd removed: \" + vd);\n                }\n                itr.remove();\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return voiceData;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/local/Resolve.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.local;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.command.battery.Battery;\nimport ai.saiy.android.command.cancel.Cancel;\nimport ai.saiy.android.command.emotion.Emotion;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.hotword.Hotword;\nimport ai.saiy.android.command.pardon.Pardon;\nimport ai.saiy.android.command.songrecognition.SongRecognition;\nimport ai.saiy.android.command.spell.Spell;\nimport ai.saiy.android.command.tasker.Tasker;\nimport ai.saiy.android.command.translate.Translate;\nimport ai.saiy.android.command.username.UserName;\nimport ai.saiy.android.command.vocalrecognition.VocalRecognition;\nimport ai.saiy.android.command.wolframalpha.WolframAlpha;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * The beginnings of a local and very deliberate language model to detect commands. This won't scale.\n * <p/>\n * We loop through every speech occurrence as just relying on the first may not be sufficient,\n * regardless of any associated confidence score.\n * <p/>\n * A thread pool is used so we can concurrently analyse the results with each of our local 'language\n * models'. Each of these return an ArrayList, which for every detection will contain a Pair with\n * the corresponding command constant {@link CC} and confidence score.\n * <p/>\n * Once all threads have finished, the results are combined into a single Array List and ordered by\n * their associated confidence score.\n * <p/>\n * This array list is then examined in {@link FrequencyAnalysis} for frequency occurrences and outliers\n * in an attempt to establish the command and weighted by both frequency and confidence, in order to\n * establish the most likely command.\n * <p/>\n * Created by benrandall76@gmail.com on 09/02/2016.\n */\npublic final class Resolve {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Resolve.class.getSimpleName();\n\n    private static final long THREADS_TIMEOUT = 1000L;\n\n    private final ArrayList<String> voiceData;\n    private final List<Callable<ArrayList<Pair<CC, Float>>>> callableList;\n\n    /**\n     * Constructor\n     *\n     * @param mContext   the application context\n     * @param voiceData  ArrayList<String> containing the voice data\n     * @param confidence float array of confidence scores\n     * @param sl         the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                   This is not necessarily the Locale of the device, as the user may be\n     *                   multi-lingual and have set a custom recognition language in a launcher short-cut.\n     */\n    public Resolve(@NonNull final Context mContext, @NonNull final ArrayList<String> voiceData,\n                   @NonNull final float[] confidence, @NonNull final SupportedLanguage sl) {\n        this.voiceData = voiceData;\n\n        final SaiyResources sr = new SaiyResources(mContext, sl);\n        callableList = new ArrayList<>();\n        callableList.add(new Cancel(sr, sl, voiceData, confidence));\n        callableList.add(new Spell(sr, sl, voiceData, confidence));\n        callableList.add(new Translate(sr, sl, voiceData, confidence));\n        callableList.add(new Pardon(sr, sl, voiceData, confidence));\n        callableList.add(new UserName(sr, sl, voiceData, confidence));\n        callableList.add(new SongRecognition(sr, sl, voiceData, confidence));\n        callableList.add(new Battery(sr, sl, voiceData, confidence));\n        callableList.add(new WolframAlpha(sr, sl, voiceData, confidence));\n        callableList.add(new Tasker(sr, sl, voiceData, confidence));\n        callableList.add(new Emotion(sr, sl, voiceData, confidence));\n        callableList.add(new Hotword(sr, sl, voiceData, confidence));\n        callableList.add(new VocalRecognition(sr, sl, voiceData, confidence));\n        sr.reset();\n    }\n\n    /**\n     * Constructor\n     * <p>\n     * Used only for intercepting Google Now commands, as we exclude some due to the output being identical\n     *\n     * @param mContext   the application context\n     * @param voiceData  ArrayList<String> containing the voice data\n     * @param confidence float array of confidence scores\n     * @param sl         the {@link SupportedLanguage} we are using to analyse the voice data.\n     *                   This is not necessarily the Locale of the device, as the user may be\n     *                   multi-lingual and have set a custom recognition language in a launcher short-cut.\n     * @param interim    true to only include commands with a 'finite' length, false otherwise\n     */\n    public Resolve(@NonNull final Context mContext, @NonNull final ArrayList<String> voiceData,\n                   @NonNull final float[] confidence, @NonNull final SupportedLanguage sl, final boolean interim) {\n        this.voiceData = voiceData;\n\n        final SaiyResources sr = new SaiyResources(mContext, sl);\n        callableList = new ArrayList<>();\n        callableList.add(new Cancel(sr, sl, voiceData, confidence));\n        callableList.add(new Pardon(sr, sl, voiceData, confidence));\n        callableList.add(new SongRecognition(sr, sl, voiceData, confidence));\n        callableList.add(new Battery(sr, sl, voiceData, confidence));\n        callableList.add(new Emotion(sr, sl, voiceData, confidence));\n        callableList.add(new Hotword(sr, sl, voiceData, confidence));\n        callableList.add(new VocalRecognition(sr, sl, voiceData, confidence));\n\n        if (!interim) {\n            callableList.add(new Spell(sr, sl, voiceData, confidence));\n            callableList.add(new UserName(sr, sl, voiceData, confidence));\n            callableList.add(new Tasker(sr, sl, voiceData, confidence));\n            callableList.add(new WolframAlpha(sr, sl, voiceData, confidence));\n        }\n\n        // callableList.add(new Translate(sr, sl, voiceData, confidence));\n\n        sr.reset();\n    }\n\n    /**\n     * Create an ArrayList<Integer> containing the frequency of commands.\n     *\n     * @return an ArrayList<Integer> of all recognised commands\n     */\n    public ArrayList<Pair<CC, Float>> resolve() {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"analyse: voiceData: \" + voiceData.size() + \" : \" + voiceData.toString());\n            MyLog.d(CLS_NAME, \"analyse: availableProcessors: \" + Runtime.getRuntime().availableProcessors());\n        }\n\n        final long then = System.nanoTime();\n\n        final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n        final ArrayList<ArrayList<Pair<CC, Float>>> pairList = new ArrayList<>();\n\n        try {\n\n            final List<Future<ArrayList<Pair<CC, Float>>>> futures = executorService.invokeAll(callableList,\n                    THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n            for (final Future<ArrayList<Pair<CC, Float>>> future : futures) {\n                pairList.add(future.get());\n            }\n\n        } catch (final ExecutionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                e.printStackTrace();\n            }\n        } catch (final CancellationException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: CancellationException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                e.printStackTrace();\n            }\n        } finally {\n            executorService.shutdown();\n        }\n\n        final ArrayList<Pair<CC, Float>> toReturn = new ArrayList<>();\n\n        if (!pairList.isEmpty()) {\n\n            for (final ArrayList<Pair<CC, Float>> pairs : pairList) {\n                toReturn.addAll(pairs);\n            }\n\n            if (!toReturn.isEmpty()) {\n                Collections.sort(toReturn, new Comparator<Pair<CC, Float>>() {\n                    @Override\n                    public int compare(final Pair<CC, Float> p1, final Pair<CC, Float> p2) {\n                        return Float.compare(p2.second, p1.second);\n                    }\n                });\n            }\n\n            if (DEBUG) {\n                for (final Pair<CC, Float> pairs : toReturn) {\n                    MyLog.i(CLS_NAME, \"command: \" + pairs.first.name() + \" ~ \" + String.valueOf(pairs.second));\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return toReturn;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/microsoft/Entity.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.microsoft;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Entity {\n\n    @SerializedName(\"entity\")\n    private final String entity;\n\n    @SerializedName(\"type\")\n    private final String type;\n\n    @SerializedName(\"startIndex\")\n    private final long startIndex;\n\n    @SerializedName(\"endIndex\")\n    private final long endIndex;\n\n    @SerializedName(\"score\")\n    private final double score;\n\n    public Entity(final long endIndex, @NonNull final String entity, @NonNull final String type,\n                  final long startIndex, final double score) {\n        this.endIndex = endIndex;\n        this.entity = entity;\n        this.type = type;\n        this.startIndex = startIndex;\n        this.score = score;\n    }\n\n    public long getEndIndex() {\n        return endIndex;\n    }\n\n    public String getEntity() {\n        return entity;\n    }\n\n    public double getScore() {\n        return score;\n    }\n\n    public long getStartIndex() {\n        return startIndex;\n    }\n\n    public String getType() {\n        return type;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/microsoft/Intent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.microsoft;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Intent {\n\n    @SerializedName(\"score\")\n    private final float score;\n\n    @SerializedName(\"intent\")\n    private final String intent;\n\n\n    public Intent(@NonNull final String intent, final float score) {\n        this.intent = intent;\n        this.score = score;\n    }\n\n    public String getIntent() {\n        return intent;\n    }\n\n    public float getScore() {\n        return score;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/microsoft/NLUMicrosoft.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.microsoft;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class NLUMicrosoft {\n\n    public static final float MIN_THRESHOLD = 0.75f;\n\n    @SerializedName(\"query\")\n    private final String query;\n\n    @SerializedName(\"entities\")\n    private final List<Entity> entities;\n\n    @SerializedName(\"intents\")\n    private final List<Intent> intents;\n\n    public NLUMicrosoft(@NonNull final List<Entity> entities, @NonNull final String query,\n                        @NonNull final List<Intent> intents) {\n        this.entities = entities;\n        this.query = query;\n        this.intents = intents;\n    }\n\n    public List<Entity> getEntities() {\n        return entities;\n    }\n\n    public List<Intent> getIntents() {\n        return intents;\n    }\n\n    public String getQuery() {\n        return query;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/microsoft/NLUMicrosoftHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.microsoft;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.List;\n\nimport ai.saiy.android.command.battery.CommandBatteryValues;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.spell.CommandSpellValues;\nimport ai.saiy.android.command.tasker.CommandTaskerValues;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.username.CommandUserNameValues;\nimport ai.saiy.android.command.wolframalpha.CommandWolframAlphaValues;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUConstants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 02/06/2016.\n */\npublic class NLUMicrosoftHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NLUMicrosoftHelper.class.getSimpleName();\n\n    /**\n     * Coerce the parameters into a {@link CommandRequest} object\n     *\n     * @param ctx            the application context\n     * @param commandRequest the {@link CommandRequest}\n     * @param sl             the {@link SupportedLanguage}\n     * @param entities       the parameters unique to the NLP provider\n     * @return the populated {@link CommandRequest} object\n     */\n    public CommandRequest prepareCommand(@NonNull final Context ctx,\n                                         @NonNull final CommandRequest commandRequest,\n                                         @NonNull final SupportedLanguage sl,\n                                         @NonNull final List<Entity> entities) {\n\n        switch (commandRequest.getCC()) {\n\n            case COMMAND_UNKNOWN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_UNKNOWN\");\n                }\n                break;\n            case COMMAND_CANCEL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_CANCEL\");\n                }\n                break;\n            case COMMAND_SONG_RECOGNITION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SONG_RECOGNITION: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_SPELL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL\");\n                }\n\n                final CommandSpellValues csv = new CommandSpellValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.TEXT_TO_SPELL)) {\n                        csv.setText(e.getEntity());\n                        csv.setStartIndex(e.getStartIndex());\n                        csv.setEndIndex(e.getEndIndex());\n                        commandRequest.setResolved(true);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL: extraction complete\");\n                        }\n\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(csv);\n\n                break;\n            case COMMAND_TRANSLATE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE\");\n                }\n\n                final CommandTranslateValues ctv = new CommandTranslateValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.TRANSLATE_LANGUAGE)) {\n                        ctv.setLanguage(e.getEntity());\n                        ctv.setLanguageStartIndex(e.getStartIndex());\n                        ctv.setLanguageEndIndex(e.getEndIndex());\n                    } else if (e.getType().matches(NLUConstants.TEXT_TO_TRANSLATE)) {\n                        ctv.setText(e.getEntity());\n                        ctv.setTextStartIndex(e.getStartIndex());\n                        ctv.setTextEndIndex(e.getEndIndex());\n                    }\n\n                    if (UtilsString.notNaked(ctv.getLanguage()) && UtilsString.notNaked(ctv.getText())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE: extraction complete\");\n                        }\n\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(ctv);\n\n                break;\n            case COMMAND_PARDON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_PARDON: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_USER_NAME:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME\");\n                }\n\n                final CommandUserNameValues cunv = new CommandUserNameValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.NAME_USER)) {\n                        cunv.setName(e.getEntity());\n                        cunv.setStartIndex(e.getStartIndex());\n                        cunv.setEndIndex(e.getEndIndex());\n                        commandRequest.setResolved(true);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME: extraction complete\");\n                        }\n\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cunv);\n\n                break;\n            case COMMAND_BATTERY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY\");\n                }\n\n                final CommandBatteryValues cbv = new CommandBatteryValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.BATTERY_TYPE)) {\n                        cbv.setType(cbv.stringToType(ctx, sl, e.getEntity()));\n                        cbv.setTypeString(e.getEntity());\n                        cbv.setStartIndex(e.getStartIndex());\n                        cbv.setEndIndex(e.getEndIndex());\n                        commandRequest.setResolved(true);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY: extraction complete\");\n                        }\n\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cbv);\n\n                break;\n            case COMMAND_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_HOTWORD\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMOTION\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_VOICE_IDENTIFY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_VOICE_IDENTIFY\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_TASKER:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER\");\n                }\n\n                final CommandTaskerValues taskerValues = new CommandTaskerValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.TASKER_TASK_NAME)) {\n                        taskerValues.setTaskName(e.getEntity());\n                        taskerValues.setStartIndex(e.getStartIndex());\n                        taskerValues.setEndIndex(e.getEndIndex());\n                        commandRequest.setResolved(true);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER: extraction complete\");\n                        }\n\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(taskerValues);\n\n                break;\n            case COMMAND_WOLFRAM_ALPHA:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA\");\n                }\n\n                final CommandWolframAlphaValues cwav = new CommandWolframAlphaValues();\n\n                for (final Entity e : entities) {\n                    if (e.getType().matches(NLUConstants.QUESTION_CONTENT)) {\n                        cwav.setQuestion(e.getEntity());\n                        cwav.setStartIndex(e.getStartIndex());\n                        cwav.setEndIndex(e.getEndIndex());\n                        commandRequest.setResolved(true);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA: extraction complete\");\n                        }\n\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cwav);\n\n                break;\n            case COMMAND_EMPTY_ARRAY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMPTY_ARRAY\");\n                }\n                break;\n            case COMMAND_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_CUSTOM\");\n                }\n                break;\n            case COMMAND_SOMETHING_WEIRD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SOMETHING_WEIRD\");\n                }\n                break;\n        }\n\n        return commandRequest;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/microsoft/ResolveMicrosoft.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.microsoft;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class ResolveMicrosoft {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveMicrosoft.class.getSimpleName();\n\n    private NLUMicrosoft nluMicrosoft;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveMicrosoft(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                            @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                            @NonNull final float[] confidenceArray,\n                            @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final String payload) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        nluMicrosoft = gson.fromJson(payload, NLUMicrosoft.class);\n\n        new NLUCoerce(getNLUMicrosoft(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    public NLUMicrosoft getNLUMicrosoft() {\n        return nluMicrosoft;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/Action.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Action {\n\n    @SerializedName(\"intent\")\n    private final Intent intent;\n\n    public Action(@NonNull final Intent intent) {\n        this.intent = intent;\n    }\n\n    public Intent getIntent() {\n        return intent;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/Concept.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Concept {\n\n    @SerializedName(\"ranges\")\n    private final int[][] ranges;\n\n    @SerializedName(\"literal\")\n    private final String literal;\n\n    @SerializedName(\"value\")\n    private final String value;\n\n    public Concept(@NonNull final String literal, @NonNull final int[][] ranges,\n                   @NonNull final String value) {\n        this.literal = literal;\n        this.ranges = ranges;\n        this.value = value;\n    }\n\n    public String getLiteral() {\n        return literal;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public int[][] getRanges() {\n        return ranges;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/Intent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Intent {\n\n    @SerializedName(\"value\")\n    private final String value;\n\n    @SerializedName(\"confidence\")\n    private final float confidence;\n\n    public Intent(final float confidence, @NonNull final String value) {\n        this.confidence = confidence;\n        this.value = value;\n    }\n\n    public float getConfidence() {\n        return confidence;\n    }\n\n    public String getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/Interpretation.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Created by benrandall76@gmail.com on 20/05/2016.\n */\npublic class Interpretation {\n\n    @SerializedName(\"literal\")\n    private final String literal;\n\n    @SerializedName(\"action\")\n    private final Action action;\n\n    @SerializedName(\"concepts\")\n    private final Map<String, List<Concept>> concept;\n\n    public Interpretation(@NonNull final Action action, @NonNull final Map<String, List<Concept>> concept,\n                          @NonNull final String literal) {\n        this.action = action;\n        this.concept = concept;\n        this.literal = literal;\n    }\n\n    public Action getAction() {\n        return action;\n    }\n\n    public Map<String, List<Concept>> getConcept() {\n        return concept;\n    }\n\n    public String getLiteral() {\n        return literal;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/NLUNuance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 24/05/2016.\n */\npublic class NLUNuance {\n\n    public static final float MIN_THRESHOLD = 0.75f;\n\n    @SerializedName(\"interpretations\")\n    private final List<Interpretation> interpretations;\n\n    public NLUNuance(@NonNull final List<Interpretation> interpretations) {\n        this.interpretations = interpretations;\n    }\n\n    public List<Interpretation> getInterpretations() {\n        return interpretations;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/NLUNuanceHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport ai.saiy.android.command.battery.CommandBatteryValues;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.spell.CommandSpellValues;\nimport ai.saiy.android.command.tasker.CommandTaskerValues;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.username.CommandUserNameValues;\nimport ai.saiy.android.command.wolframalpha.CommandWolframAlphaValues;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUConstants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 02/06/2016.\n */\npublic class NLUNuanceHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NLUNuanceHelper.class.getSimpleName();\n\n    /**\n     * Coerce the parameters into a {@link CommandRequest} object\n     *\n     * @param ctx            the application context\n     * @param commandRequest the {@link CommandRequest}\n     * @param sl             the {@link SupportedLanguage}\n     * @param map            the parameters unique to the NLP provider\n     * @return the populated {@link CommandRequest} object\n     */\n    public CommandRequest prepareCommand(@NonNull final Context ctx,\n                                         @NonNull final CommandRequest commandRequest,\n                                         @NonNull final SupportedLanguage sl,\n                                         @NonNull final Map<String, List<Concept>> map) {\n\n        switch (commandRequest.getCC()) {\n\n            case COMMAND_UNKNOWN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_UNKNOWN\");\n                }\n                break;\n            case COMMAND_CANCEL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_CANCEL\");\n                }\n                break;\n            case COMMAND_SPELL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL\");\n                }\n\n                final CommandSpellValues csv = new CommandSpellValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.TEXT_TO_SPELL)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL: extraction complete\");\n                            }\n\n                            csv.setText(concept.getLiteral());\n                            csv.setRanges(concept.getRanges());\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(csv);\n\n                break;\n            case COMMAND_TRANSLATE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE\");\n                }\n\n                final CommandTranslateValues ctv = new CommandTranslateValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.TEXT_TO_TRANSLATE)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: TEXT_TO_TRANSLATE\");\n                                MyLog.i(CLS_NAME, \"prepareCommand: getLiteral: \" + concept.getLiteral());\n                                MyLog.i(CLS_NAME, \"prepareCommand: getRanges: \" + Arrays.deepToString(concept.getRanges()));\n                            }\n\n                            ctv.setText(concept.getLiteral());\n                            ctv.setTextRanges(concept.getRanges());\n\n                        } else if (entry.getKey().matches(NLUConstants.LANGUAGE)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: TRANSLATE_LANGUAGE\");\n                                MyLog.i(CLS_NAME, \"prepareCommand: getLiteral: \" + concept.getLiteral());\n                                MyLog.i(CLS_NAME, \"prepareCommand: getRanges: \" + Arrays.deepToString(concept.getRanges()));\n                            }\n\n                            ctv.setLanguage(concept.getLiteral().toLowerCase(sl.getLocale()));\n                            ctv.setLanguageRanges(concept.getRanges());\n                        }\n\n                        if (UtilsString.notNaked(ctv.getLanguage()) && UtilsString.notNaked(ctv.getText())) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE: extraction complete\");\n                            }\n\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(ctv);\n                break;\n            case COMMAND_SONG_RECOGNITION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SONG_RECOGNITION: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_PARDON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_PARDON: extraction complete\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_USER_NAME:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME\");\n                }\n\n                final CommandUserNameValues cunv = new CommandUserNameValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.NAME_USER)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME: extraction complete\");\n                            }\n\n                            cunv.setName(concept.getLiteral());\n                            cunv.setRanges(concept.getRanges());\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(cunv);\n                break;\n            case COMMAND_BATTERY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY\");\n                }\n\n                final CommandBatteryValues cbv = new CommandBatteryValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.BATTERY_TYPE)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY: extraction complete\");\n                            }\n\n                            cbv.setType(cbv.stringToType(ctx, sl, concept.getLiteral()));\n                            cbv.setTypeString(concept.getLiteral());\n                            cbv.setRanges(concept.getRanges());\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(cbv);\n                break;\n            case COMMAND_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_HOTWORD\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMOTION\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_VOICE_IDENTIFY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_VOICE_IDENTIFY\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_TASKER:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER\");\n                }\n\n                final CommandTaskerValues taskerValues = new CommandTaskerValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.TASKER_TASK_NAME)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER: extraction complete\");\n                            }\n\n                            taskerValues.setTaskName(concept.getLiteral());\n                            taskerValues.setRanges(concept.getRanges());\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(taskerValues);\n\n                break;\n            case COMMAND_WOLFRAM_ALPHA:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA\");\n                }\n\n                final CommandWolframAlphaValues cwav = new CommandWolframAlphaValues();\n\n                for (final Map.Entry<String, List<Concept>> entry : map.entrySet()) {\n\n                    final List<Concept> concepts = entry.getValue();\n                    for (final Concept concept : concepts) {\n\n                        if (entry.getKey().matches(NLUConstants.QUESTION_CONTENT)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA: extraction complete\");\n                            }\n\n                            cwav.setQuestion(concept.getLiteral());\n                            cwav.setRanges(concept.getRanges());\n                            commandRequest.setResolved(true);\n                            break;\n                        }\n                    }\n                }\n\n                commandRequest.setVariableData(cwav);\n\n                break;\n            case COMMAND_EMPTY_ARRAY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMPTY_ARRAY\");\n                }\n                break;\n            case COMMAND_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_CUSTOM\");\n                }\n                break;\n            case COMMAND_SOMETHING_WEIRD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SOMETHING_WEIRD\");\n                }\n                break;\n        }\n\n        return commandRequest;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/nuance/ResolveNuance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.nuance;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.FieldNamingPolicy;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 24/05/2016.\n */\npublic class ResolveNuance {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveNuance.class.getSimpleName();\n\n    private NLUNuance nluNuance;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveNuance(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                         @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                         @NonNull final float[] confidenceArray,\n                         @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final JSONObject payload) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        final GsonBuilder builder = new GsonBuilder();\n        builder.disableHtmlEscaping();\n        builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);\n\n        final Gson gson = builder.create();\n        nluNuance = gson.fromJson(payload.toString(), new TypeToken<NLUNuance>() {\n        }.getType());\n\n        new NLUCoerce(getNLUNuance(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    private NLUNuance getNLUNuance() {\n        return nluNuance;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    private float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/Context.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class Context {\n\n    @SerializedName(\"context\")\n    private final String context;\n\n    @SerializedName(\"confidence\")\n    private final double confidence;\n\n    @SerializedName(\"values\")\n    private final List<ContextValue> contextValues;\n\n    public Context(final double confidence, @NonNull final String context,\n                   @NonNull final List<ContextValue> contextValues) {\n        this.confidence = confidence;\n        this.context = context;\n        this.contextValues = contextValues;\n    }\n\n    public double getConfidence() {\n        return confidence;\n    }\n\n    public String getContext() {\n        return context;\n    }\n\n    public List<ContextValue> getContextValues() {\n        return contextValues;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/ContextValue.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class ContextValue {\n\n    @SerializedName(\"identifier\")\n    private final String identifier;\n\n    public ContextValue(@NonNull final String identifier) {\n        this.identifier = identifier;\n    }\n\n    public String getIdentifier() {\n        return identifier;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/Entity.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class Entity {\n\n    @SerializedName(\"name\")\n    private final String name;\n\n    @SerializedName(\"value\")\n    private final String value;\n\n    @SerializedName(\"contextual\")\n    private final List<Context> contextual;\n\n    @SerializedName(\"index\")\n    private final int[] index;\n\n    @SerializedName(\"confidence\")\n    private final double confidence;\n\n    public Entity(final double confidence, @NonNull final String name, @NonNull final String value,\n                  @NonNull final List<Context> contextual, @NonNull final int[] index) {\n        this.confidence = confidence;\n        this.name = name;\n        this.value = value;\n        this.contextual = contextual;\n        this.index = index;\n    }\n\n    public double getConfidence() {\n        return confidence;\n    }\n\n    public List<Context> getContextual() {\n        return contextual;\n    }\n\n    public int[] getIndex() {\n        return index;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/Intent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class Intent {\n\n    @SerializedName(\"intent\")\n    private final String intent;\n\n\n    @SerializedName(\"confidence\")\n    private final double confidence;\n\n    @SerializedName(\"entities\")\n    private final List<Entity> entities;\n\n    public Intent(final double confidence, @NonNull final String intent, @NonNull final List<Entity> entities) {\n        this.confidence = confidence;\n        this.intent = intent;\n        this.entities = entities;\n    }\n\n    public List<Entity> getEntities() {\n        return entities;\n    }\n\n    public double getConfidence() {\n        return confidence;\n    }\n\n    public String getIntent() {\n        return intent;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/NLUSaiy.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 31/05/2016.\n */\npublic class NLUSaiy {\n\n    @SerializedName(\"results\")\n    private final List<String> results;\n\n    @SerializedName(\"confidence\")\n    private final float[] confidence;\n\n    @SerializedName(\"intents\")\n    private final List<Intent> intents;\n\n    public NLUSaiy(@NonNull final float[] confidence, @NonNull final List<String> results,\n                   @NonNull final List<Intent> intents) {\n        this.confidence = confidence;\n        this.results = results;\n        this.intents = intents;\n    }\n\n    public float[] getConfidence() {\n        return confidence;\n    }\n\n    public List<Intent> getIntents() {\n        return intents;\n    }\n\n    public List<String> getResults() {\n        return results;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/NLUSaiyHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.List;\n\nimport ai.saiy.android.command.battery.CommandBatteryValues;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.spell.CommandSpellValues;\nimport ai.saiy.android.command.tasker.CommandTaskerValues;\nimport ai.saiy.android.command.translate.CommandTranslateValues;\nimport ai.saiy.android.command.username.CommandUserNameValues;\nimport ai.saiy.android.command.wolframalpha.CommandWolframAlphaValues;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUConstants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class NLUSaiyHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NLUSaiyHelper.class.getSimpleName();\n\n    /**\n     * Coerce the parameters into a {@link CommandRequest} object\n     *\n     * @param ctx            the application context\n     * @param commandRequest the {@link CommandRequest}\n     * @param sl             the {@link SupportedLanguage}\n     * @param entities       the parameters unique to the NLP provider\n     * @return the populated {@link CommandRequest} object\n     */\n    public CommandRequest prepareCommand(@NonNull final android.content.Context ctx,\n                                         @NonNull final CommandRequest commandRequest,\n                                         @NonNull final SupportedLanguage sl,\n                                         @NonNull final List<Entity> entities) {\n\n        switch (commandRequest.getCC()) {\n\n            case COMMAND_UNKNOWN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_UNKNOWN\");\n                }\n                break;\n            case COMMAND_CANCEL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_CANCEL\");\n                }\n                break;\n            case COMMAND_SPELL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SPELL\");\n                }\n\n                final CommandSpellValues csv = new CommandSpellValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.NAME_USER)) {\n                        csv.setText(e.getValue());\n                        csv.setStartIndex(e.getIndex()[0]);\n                        csv.setEndIndex(e.getIndex()[1]);\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(csv);\n\n                break;\n            case COMMAND_TRANSLATE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE\");\n                }\n\n                final CommandTranslateValues ctv = new CommandTranslateValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.TRANSLATE_LANGUAGE)) {\n                        ctv.setLanguage(e.getValue());\n                        ctv.setLanguageStartIndex(e.getIndex()[0]);\n                        ctv.setLanguageEndIndex(e.getIndex()[1]);\n                    } else if (e.getName().matches(NLUConstants.TEXT_TO_TRANSLATE)) {\n                        ctv.setText(e.getValue());\n                        ctv.setTextStartIndex(e.getIndex()[0]);\n                        ctv.setTextEndIndex(e.getIndex()[1]);\n                    }\n\n                    if (UtilsString.notNaked(ctv.getLanguage()) && UtilsString.notNaked(ctv.getText())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TRANSLATE: extraction complete\");\n                        }\n\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(ctv);\n\n                break;\n            case COMMAND_SONG_RECOGNITION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SONG_RECOGNITION\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_PARDON:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_PARDON\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_USER_NAME:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_NAME\");\n                }\n\n                final CommandUserNameValues cunv = new CommandUserNameValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.NAME_USER)) {\n                        cunv.setName(e.getValue());\n                        cunv.setStartIndex(e.getIndex()[0]);\n                        cunv.setEndIndex(e.getIndex()[1]);\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cunv);\n\n                break;\n            case COMMAND_BATTERY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_BATTERY\");\n                }\n\n                final CommandBatteryValues cbv = new CommandBatteryValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.BATTERY_TYPE)) {\n                        cbv.setTypeString(e.getValue());\n                        cbv.setType(cbv.stringToType(ctx, sl, e.getValue()));\n                        cbv.setStartIndex(e.getIndex()[0]);\n                        cbv.setEndIndex(e.getIndex()[1]);\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cbv);\n\n                break;\n            case COMMAND_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_HOTWORD\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMOTION\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_VOICE_IDENTIFY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_VOICE_IDENTIFY\");\n                }\n                commandRequest.setResolved(true);\n                break;\n            case COMMAND_TASKER:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_TASKER\");\n                }\n\n                final CommandTaskerValues taskerValues = new CommandTaskerValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.TASKER_TASK_NAME)) {\n                        taskerValues.setTaskName(e.getValue());\n                        taskerValues.setStartIndex(e.getIndex()[0]);\n                        taskerValues.setEndIndex(e.getIndex()[1]);\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(taskerValues);\n\n                break;\n            case COMMAND_WOLFRAM_ALPHA:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_WOLFRAM_ALPHA\");\n                }\n\n                final CommandWolframAlphaValues cwav = new CommandWolframAlphaValues();\n\n                for (final Entity e : entities) {\n                    if (e.getName().matches(NLUConstants.QUESTION_CONTENT)) {\n                        cwav.setQuestion(e.getValue());\n                        cwav.setStartIndex(e.getIndex()[0]);\n                        cwav.setEndIndex(e.getIndex()[1]);\n                        commandRequest.setResolved(true);\n                        break;\n                    }\n                }\n\n                commandRequest.setVariableData(cwav);\n\n                break;\n            case COMMAND_EMPTY_ARRAY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_EMPTY_ARRAY\");\n                }\n                break;\n            case COMMAND_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_USER_CUSTOM\");\n                }\n                break;\n            case COMMAND_SOMETHING_WEIRD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"prepareCommand: COMMAND_SOMETHING_WEIRD\");\n                }\n                break;\n        }\n\n        return commandRequest;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/saiy/ResolveSaiy.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.saiy;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.FieldNamingPolicy;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class ResolveSaiy {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveSaiy.class.getSimpleName();\n\n    private NLUSaiy nluSaiy;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveSaiy(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                       @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                       @NonNull final float[] confidenceArray,\n                       @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final JSONObject payload) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        final GsonBuilder builder = new GsonBuilder();\n        builder.disableHtmlEscaping();\n        builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);\n\n        final Gson gson = builder.create();\n        nluSaiy = gson.fromJson(payload.toString(), new TypeToken<NLUSaiy>() {\n        }.getType());\n\n        new NLUCoerce(getNLUSaiy(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    public NLUSaiy getNLUSaiy() {\n        return nluSaiy;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/wit/Entity.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.wit;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class Entity {\n\n    @SerializedName(\"message_body\")\n    private final List<MessageBody> results;\n\n    public Entity(@NonNull final List<MessageBody> results) {\n        this.results = results;\n    }\n\n    public List<MessageBody> getResults() {\n        return results;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/wit/MessageBody.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.wit;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class MessageBody {\n\n    @SerializedName(\"type\")\n    private final String type;\n\n    @SerializedName(\"value\")\n    private final String value;\n\n    @SerializedName(\"confidence\")\n    private final float confidence;\n\n    @SerializedName(\"suggested\")\n    private final boolean suggested;\n\n    public MessageBody(final float confidence, @NonNull final String type, @NonNull final String value,\n                       final boolean suggested) {\n        this.confidence = confidence;\n        this.type = type;\n        this.value = value;\n        this.suggested = suggested;\n    }\n\n    public float getConfidence() {\n        return confidence;\n    }\n\n    public boolean isSuggested() {\n        return suggested;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/wit/NLUWit.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.wit;\n\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class NLUWit {\n\n    @SerializedName(\"_text\")\n    private final String text;\n\n    @SerializedName(\"msg_id\")\n    private final String messageId;\n\n    @SerializedName(\"confidence\")\n    private final float confidence;\n\n    @SerializedName(\"entities\")\n    private final Entity entities;\n\n    public NLUWit(final float confidence, @NonNull final String text, @NonNull final String messageId,\n                  @NonNull final Entity entities) {\n        this.confidence = confidence;\n        this.text = text;\n        this.messageId = messageId;\n        this.entities = entities;\n    }\n\n    public float getConfidence() {\n        return confidence;\n    }\n\n    public Entity getEntities() {\n        return entities;\n    }\n\n    public String getMessageId() {\n        return messageId;\n    }\n\n    public String getText() {\n        return text;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/nlu/wit/ResolveWit.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.nlu.wit;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.NLUCoerce;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic class ResolveWit {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ResolveWit.class.getSimpleName();\n\n    private NLUWit nluWit;\n\n    private final ArrayList<String> resultsArray;\n    private final float[] confidenceArray;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Context mContext;\n\n    public ResolveWit(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                      @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                      @NonNull final float[] confidenceArray,\n                      @NonNull final ArrayList<String> resultsArray) {\n\n        this.confidenceArray = confidenceArray;\n        this.resultsArray = resultsArray;\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n        this.mContext = mContext;\n    }\n\n    public void unpack(@NonNull final NLUWit nluWit) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"unpacking\");\n        }\n\n        this.nluWit = nluWit;\n\n        new NLUCoerce(getNLUWit(), getContext(), getSupportedLanguage(), getVRLocale(), getTTSLocale(),\n                getConfidenceArray(), getResultsArray()).coerce();\n    }\n\n    public NLUWit getNLUWit() {\n        return nluWit;\n    }\n\n    public Locale getVRLocale() {\n        return vrLocale;\n    }\n\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    public SupportedLanguage getSupportedLanguage() {\n        return sl;\n    }\n\n    public ArrayList<String> getResultsArray() {\n        return resultsArray;\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public float[] getConfidenceArray() {\n        return confidenceArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/partial/IPartial.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.partial;\n\n/**\n * Created by benrandall76@gmail.com on 24/04/2016.\n */\npublic interface IPartial {\n\n    void onCancelDetected();\n\n    void onTranslateDetected();\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/partial/Partial.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.partial;\n\n/**\n * Class to hold Partial Results constants\n * <p>\n * Created by benrandall76@gmail.com on 26/04/2016.\n */\npublic class Partial {\n\n    public static final int CANCEL = 1;\n    public static final int TRANSLATE = 2;\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/partial/PartialHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.partial;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.os.Process;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\nimport ai.saiy.android.command.cancel.CancelPartial;\nimport ai.saiy.android.command.translate.TranslatePartial;\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to detect commands within the partial voice results from the recognition provider. We can\n * use any early detection to initialise and/or prefetch resources, so should the full command be\n * resolved later in the final results, it should, in theory, execute more quickly.\n * <p/>\n * Created by benrandall76@gmail.com on 23/04/2016.\n */\npublic class PartialHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = PartialHelper.class.getSimpleName();\n\n    private static final long THREADS_TIMEOUT = 100L;\n\n    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());\n    private final List<Callable<Pair<Boolean, Integer>>> callableList = new ArrayList<>();\n    private final CancelPartial cancelPartial;\n    private volatile TranslatePartial translatePartial;\n    private final IPartial iPartial;\n\n    /**\n     * Constructor\n     * <p/>\n     * Initialises the Strings used to analyse the partial results for triggers we need to react to.\n     * The {@link SaiyResources} are released here.\n     *\n     * @param mContext the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param iPartial the {@link IPartial} listener\n     */\n    public PartialHelper(@NonNull final Context mContext, @NonNull final SupportedLanguage sl,\n                         @NonNull final IPartial iPartial) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Constructor\");\n        }\n\n        this.iPartial = iPartial;\n\n        final SaiyResources sr = new SaiyResources(mContext, sl);\n        cancelPartial = new CancelPartial(sr, sl);\n        callableList.add(cancelPartial);\n\n        switch (SPH.getDefaultTranslationProvider(mContext)) {\n            case TranslationProvider.TRANSLATION_PROVIDER_BING:\n                translatePartial = new TranslatePartial(sr, sl);\n                callableList.add(translatePartial);\n                break;\n        }\n\n        sr.reset();\n    }\n\n    /**\n     * Utility method to detect the phrase during a recognition loop. Handling the initialisation\n     * of localised resources can be slow, so we need to do this only once.\n     *\n     * @param partialResults the bundle of partial results\n     */\n    public void isPartial(@NonNull final Bundle partialResults) {\n\n        new Thread() {\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                final List<Pair<Boolean, Integer>> resultList = new ArrayList<>(callableList.size());\n                cancelPartial.setPartialData(partialResults);\n\n                if (translatePartial != null) {\n                    translatePartial.setPartialData(partialResults);\n                }\n\n                try {\n\n                    final List<Future<Pair<Boolean, Integer>>> futures = executorService.invokeAll(callableList,\n                            THREADS_TIMEOUT, TimeUnit.MILLISECONDS);\n\n                    for (final Future<Pair<Boolean, Integer>> future : futures) {\n                        resultList.add(future.get());\n                    }\n\n                } catch (final ExecutionException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"future: ExecutionException\");\n                        e.printStackTrace();\n                    }\n                } catch (final CancellationException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"future: CancellationException\");\n                        e.printStackTrace();\n                    }\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"future: InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                for (final Pair<Boolean, Integer> result : resultList) {\n                    if (result.first) {\n                        switch (result.second) {\n                            case Partial.CANCEL:\n                                iPartial.onCancelDetected();\n                                break;\n                            case Partial.TRANSLATE:\n                                iPartial.onTranslateDetected();\n                                break;\n                        }\n                    }\n                }\n            }\n        }.start();\n    }\n\n    public void shutdown() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shutdown\");\n        }\n\n        try {\n\n            executorService.shutdown();\n\n            if (!executorService.awaitTermination(THREADS_TIMEOUT, TimeUnit.MILLISECONDS)) {\n                executorService.shutdownNow();\n            }\n        } catch (final CancellationException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shutdownNow: CancellationException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shutdownNow: InterruptedException\");\n                e.printStackTrace();\n            }\n        } finally {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"shutdown: complete\");\n            }\n        }\n    }\n\n    public boolean isShutdown() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isShutdown\");\n        }\n        return executorService.isShutdown();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/permissions/PermissionHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.permissions;\n\nimport android.app.AppOpsManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.RequiresApi;\nimport android.support.v4.content.ContextCompat;\n\nimport ai.saiy.android.Manifest;\nimport ai.saiy.android.ui.activity.ActivityPermissionDialog;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 12/04/2016.\n */\npublic class PermissionHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = PermissionHelper.class.getSimpleName();\n\n    public static final String REQUESTED_PERMISSION = \"requested_permission\";\n    public static final String REQUESTED_PERMISSION_ID = \"requested_permission_id\";\n\n    public static final int REQUEST_UNKNOWN = 0;\n    public static final int REQUEST_AUDIO = 1;\n    public static final int REQUEST_FILE = 2;\n    public static final int REQUEST_GROUP_CONTACTS = 3;\n\n    /**\n     * Check if the calling application has the correct permission to control Saiy.\n     *\n     * @param ctx the application context\n     * @return true if the permission has been granted.\n     */\n    public static boolean checkSaiyRemotePermission(@NonNull final Context ctx) {\n\n        switch (ctx.checkCallingPermission(Manifest.permission.CONTROL_SAIY)) {\n\n            case PackageManager.PERMISSION_GRANTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkSaiyRemotePermission: PERMISSION_GRANTED\");\n                }\n                return true;\n            case PackageManager.PERMISSION_DENIED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkSaiyRemotePermission: PERMISSION_DENIED\");\n                }\n            default:\n                return false;\n        }\n    }\n\n    /**\n     * Check if the calling application has the correct permission to control Saiy.\n     *\n     * @param ctx        the application context\n     * @param callingUid of the remote request\n     * @return true if the permission has been granted.\n     */\n    public static boolean checkSaiyPermission(@NonNull final Context ctx, final int callingUid) {\n\n        final String packageName = ctx.getPackageManager().getNameForUid(callingUid);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkSaiyPermission: \" + packageName);\n        }\n\n        if (UtilsString.notNaked(packageName) && callingUid > 0) {\n\n            if (!packageName.matches(ctx.getPackageName())) {\n\n                switch (ctx.checkCallingPermission(Manifest.permission.CONTROL_SAIY)) {\n\n                    case PackageManager.PERMISSION_GRANTED:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkSaiyPermission: PERMISSION_GRANTED\");\n                        }\n                        return true;\n                    case PackageManager.PERMISSION_DENIED:\n                    default:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkSaiyPermission: PERMISSION_DENIED\");\n                        }\n                        return false;\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkSaiyPermission: self\");\n                }\n                return true;\n            }\n        } else {\n            MyLog.e(CLS_NAME, ctx.getString(ai.saiy.android.R.string.error_package_name_null));\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if the user has granted microphone permissions\n     *\n     * @param ctx the application context\n     * @return true if the permissions have been granted. False if they have been denied or are\n     * required to be requested.\n     */\n    public static boolean checkAudioPermissions(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkAudioPermissions\");\n        }\n\n        switch (ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.RECORD_AUDIO)) {\n\n            case PackageManager.PERMISSION_GRANTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkAudioPermissions: PERMISSION_GRANTED\");\n                }\n                return true;\n            case PackageManager.PERMISSION_DENIED:\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"checkAudioPermissions: PERMISSION_DENIED\");\n                }\n\n                final Intent intent = new Intent(ctx, ActivityPermissionDialog.class);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n                final Bundle bundle = new Bundle();\n                bundle.putStringArray(REQUESTED_PERMISSION, new String[]{android.Manifest.permission.RECORD_AUDIO});\n                bundle.putInt(REQUESTED_PERMISSION_ID, REQUEST_AUDIO);\n\n                intent.putExtras(bundle);\n\n                ctx.startActivity(intent);\n                return false;\n        }\n    }\n\n    /**\n     * Check if the user has granted write files permissions\n     *\n     * @param ctx the application context\n     * @return true if the permissions have been granted. False if they have been denied or are\n     * required to be requested.\n     */\n    public static boolean checkFilePermissions(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkFilePermissions\");\n        }\n\n        switch (ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n\n            case PackageManager.PERMISSION_GRANTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkFilePermissions: PERMISSION_GRANTED\");\n                }\n                return true;\n            case PackageManager.PERMISSION_DENIED:\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"checkFilePermissions: PERMISSION_DENIED\");\n                }\n\n                final Intent intent = new Intent(ctx, ActivityPermissionDialog.class);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n                final Bundle bundle = new Bundle();\n                bundle.putStringArray(REQUESTED_PERMISSION, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE});\n                bundle.putInt(REQUESTED_PERMISSION_ID, REQUEST_FILE);\n\n                intent.putExtras(bundle);\n\n                ctx.startActivity(intent);\n                return false;\n        }\n    }\n\n    /**\n     * Check if the user has granted the contacts group permission\n     *\n     * @param ctx the application context\n     * @return true if the permissions have been granted. False if they have been denied or are\n     * required to be requested.\n     */\n    public static boolean checkContactGroupPermissions(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkContactGroupPermissions\");\n        }\n\n        switch (ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.GET_ACCOUNTS)) {\n\n            case PackageManager.PERMISSION_GRANTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkContactGroupPermissions: PERMISSION_GRANTED\");\n                }\n                return true;\n            case PackageManager.PERMISSION_DENIED:\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"checkContactGroupPermissions: PERMISSION_DENIED\");\n                }\n\n                final Intent intent = new Intent(ctx, ActivityPermissionDialog.class);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n                final Bundle bundle = new Bundle();\n                bundle.putStringArray(REQUESTED_PERMISSION, new String[]{android.Manifest.permission.GET_ACCOUNTS});\n                bundle.putInt(REQUESTED_PERMISSION_ID, REQUEST_GROUP_CONTACTS);\n\n                intent.putExtras(bundle);\n\n                ctx.startActivity(intent);\n                return false;\n        }\n    }\n\n    /**\n     * Check if the user has granted the usage stats permission, which must be manually complete via the\n     * settings.\n     *\n     * @param ctx the application context\n     * @return true if the permissions have been granted, false otherwise\n     */\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    public static boolean checkUsageStatsPermission(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkUsageStatsPermission\");\n        }\n\n        final AppOpsManager appOps = (AppOpsManager) ctx.getSystemService(Context.APP_OPS_SERVICE);\n\n        return appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,\n                android.os.Process.myUid(), ctx.getPackageName()) == AppOpsManager.MODE_ALLOWED;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/personality/AI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.personality;\n\n/**\n * The more features of the application that are interacted with, the more the 'AI Value' increases,\n * which is shown to the user in the permanent notification.\n * <p>\n * The AI Level is based on very little that is Artificially Intelligent....\n * <p>\n * Created by benrandall76@gmail.com on 22/03/2016.\n */\npublic class AI {\n\n    // Placeholder value\n    public static double AI_LEVEL = 0.25;\n\n    /**\n     * Get the current AI Level to display\n     *\n     * @return the AI Level\n     */\n    public static String getAILevel() {\n        return String.valueOf(calculateAI());\n    }\n\n    /**\n     * Check how much of the application's functionality the user has used and is using. More = higher.\n     *\n     * @return the AI Level\n     */\n    private static double calculateAI() {\n        // TODO\n        return AI_LEVEL;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/personality/PersonalityHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.personality;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to separate the personality of Saiy from standard speech responses. However, they may\n * directly call {@link PersonalityResponse}, but for the sake of separation...\n * <p>\n * Created by benrandall76@gmail.com on 08/02/2016.\n */\npublic final class PersonalityHelper {\n\n    /**\n     * Prevent instantiation\n     */\n    public PersonalityHelper() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * Used to determine whether the user making this call is subject to\n     * teleportations.\n     * <p/>\n     * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method can\n     * now automatically identify goats using advanced goat recognition technology.</p>\n     *\n     * @return Returns true if the user making this call is a goat.\n     */\n    public static boolean isUserAGoat() {\n        return Math.random() < 0.5;\n    }\n\n    /**\n     * Gets a random or user defined intro\n     *\n     * @param ctx the application context\n     * @return intro utterance\n     */\n    public static String getIntro(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        return PersonalityResponse.getIntro(ctx, sl);\n    }\n\n    /**\n     * Gets the name of the user to include in speech 50% of the time.\n     *\n     * @param ctx the application context\n     * @return the name of the user or an empty string\n     */\n    public static String getUserNameOrNot(@NonNull final Context ctx) {\n        if (isUserAGoat()) {\n            return SPH.getUserName(ctx);\n        } else {\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/personality/PersonalityResponse.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.personality;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Random;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Helper Class to get standard speech responses. Responses that are user defined,\n * or more personal should be handled in {@link PersonalityHelper}\n * <p>\n * Created by benrandall76@gmail.com on 13/02/2016.\n */\npublic final class PersonalityResponse {\n\n    /**\n     * Prevent instantiation\n     */\n    public PersonalityResponse() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * Get the Beyond Verbal intro response\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the introduction\n     */\n    public static String getBeyondVerbalIntroResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_beyond_verbal);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the Beyond Verbal error response\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the introduction\n     */\n    public static String getBeyondVerbalErrorResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_beyond_verbal_error);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the Beyond Verbal verbose introduction.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBeyondVerbalVerboseResponse(@NonNull final Context ctx,\n                                                        @NonNull final SupportedLanguage sl) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.beyond_verbal_verbose), PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the Beyond Verbal extra verbose introduction.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBeyondVerbalExtraVerboseResponse(@NonNull final Context ctx,\n                                                             @NonNull final SupportedLanguage sl) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.beyond_verbal_extra_verbose), PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the Beyond Verbal connection error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBeyondVerbalServerErrorResponse(@NonNull final Context ctx,\n                                                            @NonNull final SupportedLanguage sl) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_beyond_verbal_connection), PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard Tasker task executed response.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param taskName the task name\n     * @return the required response\n     */\n    public static String getTaskerTaskExecutedResponse(@NonNull final Context ctx,\n                                                       @NonNull final SupportedLanguage sl,\n                                                       @NonNull final String taskName) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_tasker);\n        return String.format(stringArray[new Random().nextInt(stringArray.length)], taskName);\n    }\n\n    /**\n     * Get the standard Tasker task executed response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerTaskNotMatchedResponse(@NonNull final Context ctx,\n                                                         @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_error_tasker_match);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n\n    /**\n     * Get the standard Task task failed response.\n     *\n     * @param ctx      the application context\n     * @param sl       the {@link SupportedLanguage}\n     * @param taskName the task name\n     * @return the required response\n     */\n    public static String getTaskerTaskFailedResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                                     @NonNull final String taskName) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_tasker_execute), taskName);\n    }\n\n    /**\n     * Get the standard no Tasker tasks response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerNoTasksResponse(@NonNull final Context ctx,\n                                                  @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_tasker_no_tasks);\n    }\n\n    /**\n     * Get the standard no Tasker external access response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerExternalAccessResponse(@NonNull final Context ctx,\n                                                         @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_tasker_external_access);\n    }\n\n    /**\n     * Get the standard no Tasker disabled response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerDisabledResponse(@NonNull final Context ctx,\n                                                   @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_tasker_enable);\n    }\n\n    /**\n     * Get the standard no Tasker not installed response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerInstallResponse(@NonNull final Context ctx,\n                                                  @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_tasker_install);\n    }\n\n    /**\n     * Get the standard no Tasker install order issue response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getTaskerInstallOrderResponse(@NonNull final Context ctx,\n                                                       @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_tasker_permissions);\n    }\n\n    /**\n     * Get the speech introduction, either a user defined one or an inbuilt intro, adding the user's\n     * name if known.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the introduction\n     */\n    public static String getIntro(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n\n        final String userIntro = SPH.getCustomIntro(ctx);\n\n        if (userIntro != null) {\n\n            if (userIntro.isEmpty()) {\n                return SaiyRequestParams.SILENCE;\n            } else {\n\n                if (SPH.getCustomIntroRandom(ctx)) {\n\n                    final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                            R.array.array_intro);\n\n                    final ArrayList<String> list = new ArrayList<>(stringArray.length);\n                    Collections.addAll(list, stringArray);\n                    list.add(userIntro);\n\n                    return UtilsString.stripNameSpace(String.format(list.get(new Random()\n                            .nextInt(list.size())), PersonalityHelper.getUserNameOrNot(ctx)));\n                } else {\n                    return userIntro;\n                }\n            }\n        } else {\n            final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                    R.array.array_intro);\n            return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                    PersonalityHelper.getUserNameOrNot(ctx)));\n        }\n    }\n\n    /**\n     * Get the standard profanity filter response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getErrorProfanityFilter(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_empty_profanity);\n    }\n\n    /**\n     * Get the standard empty voice data response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getErrorEmptyVoiceData(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_empty_voice_data);\n    }\n\n    /**\n     * Get the standard action unknown response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getErrorActionUnknown(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_unknown_action);\n    }\n\n    /**\n     * Get the standard remote command failed response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getErrorRemoteFailed(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                              @NonNull final String appName) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_remote), appName);\n    }\n\n    /**\n     * Get the standard remote command failed unknown response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getErrorRemoteFailedUnknown(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_remote_unknown);\n    }\n\n    /**\n     * Get the standard remote command registration failure response.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the remote application name\n     * @return the required response\n     */\n    public static String getErrorRemoteCommandRegister(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                                       @NonNull final String appName) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_remote_command_register), appName);\n    }\n\n    /**\n     * Get the standard remote command registration success response.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the remote application name\n     * @return the required response\n     */\n    public static String getRemoteCommandRegisterSuccess(@NonNull final Context ctx,\n                                                         @NonNull final SupportedLanguage sl,\n                                                         @NonNull final String appName,\n                                                         @NonNull final String keyphrase) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.remote_command_register_response), appName, keyphrase);\n    }\n\n    /**\n     * Get the standard remote command success response.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the remote application name\n     * @return the required response\n     */\n    public static String getRemoteSuccess(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                          @NonNull final String appName) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl, R.string.remote_success), appName);\n    }\n\n    /**\n     * Get the standard no network connection response, adding the user's name if defined.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getNoNetwork(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_no_network);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard no comprende response, adding the user's name if defined.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getNoComprende(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_no_comprende);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard repeat command response, adding the user's name if defined.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getRepeatCommand(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_repeat_command);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard cancel response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getCancelled(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_cancel);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard user name response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getUserName(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_user_name);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard song recognition response.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the default provider\n     * @return the required response\n     */\n    public static String getSongRecognitionResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                                    @NonNull final String appName) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_song_recognition);\n        return String.format(stringArray[new Random().nextInt(stringArray.length)], appName);\n    }\n\n    /**\n     * Get the secure error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getSecureErrorResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_error_secure);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard battery information response.\n     *\n     * @param ctx   the application context\n     * @param sl    the {@link SupportedLanguage}\n     * @param type  the requested battery information type\n     * @param value the value of the requested type\n     * @return the required response\n     */\n    public static String getBatteryResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                            @NonNull final String type, @NonNull final String value) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_battery);\n        return String.format(stringArray[new Random().nextInt(stringArray.length)], type, value);\n    }\n\n    /**\n     * Get the battery error unknown request response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBatteryErrorUnknownResponse(@NonNull final Context ctx,\n                                                        @NonNull final SupportedLanguage sl) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_battery_unknown), PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the battery error access request response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBatteryErrorAccessResponse(@NonNull final Context ctx,\n                                                       @NonNull final SupportedLanguage sl) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_battery_access), PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the song recognition app opening error response.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the default provider\n     * @return the required response\n     */\n    public static String getSongRecognitionErrorAppResponse(@NonNull final Context ctx,\n                                                            @NonNull final SupportedLanguage sl,\n                                                            @NonNull final String appName) {\n        return UtilsString.stripNameSpace(String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_song_recognition_app_failed), PersonalityHelper.getUserNameOrNot(ctx), appName));\n    }\n\n    /**\n     * Get the song recognition app no longer installed error.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getSongRecognitionErrorNoApp(@NonNull final Context ctx,\n                                                      @NonNull final SupportedLanguage sl) {\n        return SaiyResourcesHelper.getStringResource(ctx, sl, R.string.error_song_recognition_chooser);\n    }\n\n    /**\n     * Get the song recognition app no longer installed error.\n     *\n     * @param ctx     the application context\n     * @param sl      the {@link SupportedLanguage}\n     * @param appName the default provider\n     * @return the required response\n     */\n    public static String getSongRecognitionErrorAppUninstalled(@NonNull final Context ctx,\n                                                               @NonNull final SupportedLanguage sl,\n                                                               @NonNull final String appName) {\n        return String.format(SaiyResourcesHelper.getStringResource(ctx, sl,\n                R.string.error_song_recognition_default_app), appName);\n    }\n\n    /**\n     * Get the standard user name response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getUserNameRepeat(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_user_name_repeat);\n        return UtilsString.stripNameSpace(stringArray[new Random().nextInt(stringArray.length)]);\n    }\n\n    /**\n     * Get the standard user name error response, adding the user's name if defined.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getUserNameError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_user_name_error);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the standard Wolfram Alpha error response, adding the user's name if defined.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getWolframAlphaError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_error_wolfram_alpha_unknown);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the Wolfram Alpha response intro.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getWolframAlphaIntro(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_wolfram_alpha_intro);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard no memory response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getNoMemory(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] responseArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_no_memory);\n        final String[] extraArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_no_memory_extra);\n        final String[] extraUnknownArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_no_memory_extra_unknown);\n\n        String extra = extraArray[new Random().nextInt(extraArray.length)];\n\n        if (extra.matches(ctx.getString(R.string.memory_extra_facebook)) &&\n                !Installed.isPackageInstalled(ctx, Installed.PACKAGE_FACEBOOK)) {\n            extra = extraUnknownArray[new Random().nextInt(extraUnknownArray.length)];\n        } else if (extra.matches(ctx.getString(R.string.memory_extra_twitter)) &&\n                !Installed.isPackageInstalled(ctx, Installed.PACKAGE_TWITTER)) {\n            extra = extraUnknownArray[new Random().nextInt(extraUnknownArray.length)];\n        } else if (extra.matches(ctx.getString(R.string.memory_extra_tinder)) &&\n                !Installed.isPackageInstalled(ctx, Installed.PACKAGE_TINDER)) {\n            extra = extraUnknownArray[new Random().nextInt(extraUnknownArray.length)];\n        } else if (extra.matches(ctx.getString(R.string.memory_extra_whatsapp)) &&\n                !Installed.isPackageInstalled(ctx, Installed.PACKAGE_WHATSAPP)) {\n            extra = extraUnknownArray[new Random().nextInt(extraUnknownArray.length)];\n        } else if (extra.matches(ctx.getString(R.string.memory_extra_snapchat)) &&\n                !Installed.isPackageInstalled(ctx, Installed.PACKAGE_SNAPCHAT)) {\n            extra = extraUnknownArray[new Random().nextInt(extraUnknownArray.length)];\n        }\n\n        return UtilsString.stripNameSpace(String.format(responseArray[new Random().nextInt(responseArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx), extra));\n    }\n\n    /**\n     * Get the standard clipboard response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getClipboardSpell(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_clipboard_copy);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard clipboard error data response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getClipboardDataError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_error_clipboard_data);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard clipboard error access response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getClipboardAccessError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_error_clipboard_access);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the standard spell error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getSpellError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_error_spell);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the vocal enrollment error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getEnrollmentError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_error_enrollment);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the vocal id error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getVocalIDError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_error_vocal_id);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the vocal id high response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getVocalIDHigh(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_vocal_id_high);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the vocal id medium response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getVocalIDMedium(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_vocal_id_medium);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the vocal id low response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getVocalIDLow(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_vocal_id_low);\n        return stringArray[new Random().nextInt(stringArray.length)];\n    }\n\n    /**\n     * Get the vocal enrollment error response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getEnrollmentAPIError(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl,\n                R.array.array_error_enrollment_api);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n\n    /**\n     * Get the BV analysis complete response.\n     *\n     * @param ctx the application context\n     * @param sl  the {@link SupportedLanguage}\n     * @return the required response\n     */\n    public static String getBVAnalysisCompleteResponse(@NonNull final Context ctx, @NonNull final SupportedLanguage sl) {\n        final String[] stringArray = SaiyResourcesHelper.getArrayResource(ctx, sl, R.array.array_bv_analysis_complete);\n        return UtilsString.stripNameSpace(String.format(stringArray[new Random().nextInt(stringArray.length)],\n                PersonalityHelper.getUserNameOrNot(ctx)));\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Condition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\n/**\n * Class of constants to identify how Saiy should process the voice data. This isn't always to\n * resolve a command, as the application could be in conversation mode or expecting the user to\n * acknowledge interaction from a previous command.\n * <p/>\n * The danger with constants such as these, would be the failure to reset/remove the Condition under\n * weird and wonderful circumstances. This could potentially leave the application stuck sending the voice\n * data to the wrong part of the application. Great care is needed to make sure this doesn't happen.\n * <p/>\n * Created by benrandall76@gmail.com on 01/04/2016.\n */\npublic final class Condition {\n\n    public static final int CONDITION_NONE = 0;\n    public static final int CONDITION_CONVERSATION = 1;\n    public static final int CONDITION_ROOT = 2;\n    public static final int CONDITION_TRANSLATION = 3;\n    public static final int CONDITION_USER_CUSTOM = 4;\n    public static final int CONDITION_EMOTION = 5;\n    public static final int CONDITION_IDENTITY = 6;\n    public static final int CONDITION_IDENTIFY = 7;\n    public static final int CONDITION_GOOGLE_NOW = 8;\n    public static final int CONDITION_SECURE = 9;\n    public static final int CONDITION_SELF_AWARE = 17;\n    public static final int CONDITION_IGNORE = 99;\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/EntangledPair.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.command.helper.CC;\n\n/**\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic class EntangledPair {\n\n    private final Position position;\n    private final CC cc;\n    private String toastContent;\n    private String utterance;\n\n    public EntangledPair(@NonNull final Position position, @NonNull final CC cc) {\n        this.position = position;\n        this.cc = cc;\n    }\n\n    public Position getPosition() {\n        return position;\n    }\n\n    public CC getCC() {\n        return cc;\n    }\n\n    public String getUtterance() {\n        return utterance;\n    }\n\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    public String getToastContent() {\n        return toastContent;\n    }\n\n    public void setToastContent(@NonNull final String toastContent) {\n        this.toastContent = toastContent;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Outcome.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.Locale;\n\nimport ai.saiy.android.service.helper.LocalRequest;\n\n/**\n * Class that provides all possible outcomes that could be needed in {@link Quantum#onProgressUpdate(EntangledPair...)}\n * or {@link Quantum#onPostExecute(Qubit)}\n * <p>\n * Created by benrandall76@gmail.com on 09/02/2016.\n */\npublic class Outcome {\n\n    public static final int SUCCESS = 0;\n    public static final int FAILURE = 1;\n\n    private String utterance;\n    private int outcome = SUCCESS;\n    private EntangledPair entangledPair;\n    private Qubit qubit;\n    private int action;\n    private Locale ttsLocale;\n    private Object extra;\n    private int condition;\n\n    /**\n     * Get any condition that may have been applied\n     *\n     * @return the {@link Condition} constant\n     */\n    public int getCondition() {\n        return condition;\n    }\n\n    /**\n     * Set any required additional condition\n     *\n     * @param condition the {@link Condition} constant\n     */\n    public void setCondition(final int condition) {\n        this.condition = condition;\n    }\n\n    /**\n     * Get the action the outcome needs to apply\n     *\n     * @return one of {@link LocalRequest#ACTION_SPEAK_ONLY} or\n     * {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     */\n    public int getAction() {\n        return action;\n    }\n\n    /**\n     * Set the action the outcome needs to apply\n     *\n     * @param action one of {@link LocalRequest#ACTION_SPEAK_ONLY} or\n     *               {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     */\n    public void setAction(final int action) {\n        this.action = action;\n    }\n\n    /**\n     * Set the outcome of the command processing\n     *\n     * @param outcome one of {@link #SUCCESS} or {@link #FAILURE}\n     */\n    public void setOutcome(int outcome) {\n        this.outcome = outcome;\n    }\n\n    /**\n     * Get the outcome\n     *\n     * @return either {@link #SUCCESS} or {@link #FAILURE}\n     */\n    public int getOutcome() {\n        return this.outcome;\n    }\n\n    /**\n     * Set the values to be used in {@link Quantum#onProgressUpdate(EntangledPair...)}\n     *\n     * @param entangledPair to be used\n     */\n    public void setEntangledPair(@NonNull final EntangledPair entangledPair) {\n        this.entangledPair = entangledPair;\n    }\n\n    /**\n     * Get the values to be used in {@link Quantum#onProgressUpdate(EntangledPair...)}\n     *\n     * @return the values\n     */\n    protected EntangledPair getEntangledPair() {\n        return this.entangledPair;\n    }\n\n    /**\n     * Set the values to be used in {@link Quantum#onPostExecute(Qubit)}\n     *\n     * @param qubit to be used\n     */\n    public void setQubit(@NonNull final Qubit qubit) {\n        this.qubit = qubit;\n    }\n\n    /**\n     * Get the values to be used in {@link Quantum#onPostExecute(Qubit)}\n     *\n     * @return the values\n     */\n    public Qubit getQubit() {\n        return this.qubit;\n    }\n\n    /**\n     * Set the utterance to be spoken\n     *\n     * @param utterance to be spoken\n     */\n    public void setUtterance(@NonNull final String utterance) {\n        this.utterance = utterance;\n    }\n\n    /**\n     * Get the utterance to be spoken\n     *\n     * @return the string utterance\n     */\n    public String getUtterance() {\n        return this.utterance;\n    }\n\n    /**\n     * Set the custom Text to Speech {@link Locale}\n     *\n     * @return the custom {@link Locale}\n     */\n    public Locale getTTSLocale() {\n        return ttsLocale;\n    }\n\n    /**\n     * Get the custom Text to Speech {@link Locale}\n     *\n     * @param locale to set\n     */\n    public void setTTSLocale(@NonNull final Locale locale) {\n        this.ttsLocale = locale;\n    }\n\n    /**\n     * Get an object extra which can be cast to the expected type\n     *\n     * @return the object extra\n     */\n    public Object getExtra() {\n        return extra;\n    }\n\n    /**\n     * Set the object extra which will be cast to the expected type\n     *\n     * @param extra the object extra\n     */\n    public void setExtra(final Object extra) {\n        this.extra = extra;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Position.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\n/**\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic enum Position {\n\n    TOAST_SHORT,\n    TOAST_LONG,\n    SPEAK,\n    CLIPBOARD,\n    SHOW_COMPUTING\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Quantum.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.command.battery.CommandBattery;\nimport ai.saiy.android.command.clipboard.ClipboardHelper;\nimport ai.saiy.android.command.custom.CommandCustom;\nimport ai.saiy.android.command.emotion.CommandEmotion;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.command.songrecognition.CommandSongRecognition;\nimport ai.saiy.android.command.spell.CommandSpell;\nimport ai.saiy.android.command.tasker.CommandTasker;\nimport ai.saiy.android.command.translate.CommandTranslate;\nimport ai.saiy.android.command.unknown.Unknown;\nimport ai.saiy.android.command.username.CommandUserName;\nimport ai.saiy.android.command.vocalrecognition.CommandVocalRecognition;\nimport ai.saiy.android.command.wolframalpha.CommandWolframAlpha;\nimport ai.saiy.android.custom.CustomResolver;\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionChooser;\nimport ai.saiy.android.device.UtilsDevice;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.memory.Memory;\nimport ai.saiy.android.memory.MemoryHelper;\nimport ai.saiy.android.nlu.local.FrequencyAnalysis;\nimport ai.saiy.android.nlu.local.Resolve;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.helper.QuantumHelper;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.thirdparty.tasker.TaskerHelper;\nimport ai.saiy.android.ui.activity.ActivityChooserDialog;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.Conditions.Network;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\n\n/**\n * This is the main class for resolving and actioning a spoken command. Regardless or not as to whether\n * the request has been processed locally or remotely, there is still no guarantee that the request is\n * sensical, to say that any given parameters can be resolved specifically to the destination command,\n * or perhaps the current context of the user.\n * <p>\n * Therefore, before we attempt to set the device brightness to 1984, create a reminder to get milk for\n * a week last Wednesday, translate 'hello' into helicopter, or call a contact with no number, we may\n * need to intervene....\n * <p>\n * Additionally, the request many contain multiple commands of different types, that are weighted by\n * confidence - either of purely the spoken word, or in the case of a remote service, the entity itself.\n * <p>\n * Dealing with these multiple eventualities, within a timescale that the user considers 'more efficient\n * by voice' and the bounds of the processing power of the device, is not easy.\n * <p>\n * In order to get as close as possible to achieving this, it's necessary to use some quantum probability\n * calculations, whereby multiple instances of weighted outcomes are assigned to a superposition. Each of\n * these multiple instances are 'entangled', and at anytime (a reduction in probability relative to its\n * equal, but opposite) the instance can be 'observed as unlikely', allowing other instances to continue\n * processing with this 'knowledge', effectively zero cancelling any resource used thus far.\n * <p>\n * Obviously, in the future, when this can be achieved at binary processing level, there will be no need\n * to consider probability - only definitive outcomes that that were 'observed' not to occur and therefore,\n * via entanglement, were never considered in the first place - but until then, you can read in more depth\n * about how this is achieved in the super class {@link Tunnelling}\n * <p>\n * Created by benrandall76@gmail.com on 08/02/2016.\n */\npublic class Quantum extends Tunnelling {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Quantum.class.getSimpleName();\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public Quantum(@NonNull final Context mContext) {\n        super(mContext);\n    }\n\n    @Override\n    protected Qubit doTunnelling(final CommandRequest cr) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"doTunnelling\");\n        }\n\n        Qubit qubit = null;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"voiceData before: \" + cr.getResultsArray().toString());\n        }\n\n        final CustomResolver customResolver = new QuantumHelper().resolve(mContext, sl, cr);\n\n        final ArrayList<String> toResolve = customResolver.getVoiceData();\n\n        if (customResolver.isCustom()) {\n            cch = customResolver.getCustomCommandHelper();\n            COMMAND = cch.getCommandConstant();\n        } else if (UtilsList.notNaked(toResolve)) {\n\n            final float[] confidence = cr.getConfidenceArray();\n\n            final SaiyDefaults.LanguageModel languageModel = SPH.getDefaultLanguageModel(mContext);\n\n            switch (languageModel) {\n\n                case LOCAL:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"LanguageModel LOCAL\");\n                    }\n\n                    COMMAND = new FrequencyAnalysis(\n                            new Resolve(mContext, toResolve, confidence, sl).resolve()).analyse();\n\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"LanguageModel EXTERNAL\");\n                    }\n\n                    COMMAND = cr.getCC();\n\n                    if (CommandRequest.inError(COMMAND)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"LanguageModelDefault.EXTERNAL inError: reverting to LOCAL\");\n                        }\n\n                        COMMAND = new FrequencyAnalysis(\n                                new Resolve(mContext, toResolve, confidence, sl).resolve()).analyse();\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"LanguageModelDefault.EXTERNAL: populated\");\n                        }\n                    }\n\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"toResolve naked\");\n            }\n\n            COMMAND = CC.COMMAND_EMPTY_ARRAY;\n        }\n\n        final Outcome outcome;\n        request.setCommand(COMMAND);\n\n        if (Network.networkProceed(mContext, COMMAND)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"DT networkProceed passed\");\n                MyLog.i(CLS_NAME, \"DT command secure: \" + COMMAND.isSecure());\n                MyLog.i(CLS_NAME, \"DT command requested securely: \" + cr.wasSecure());\n                MyLog.i(CLS_NAME, \"DT device secure: \" + UtilsDevice.isDeviceLocked(this.mContext));\n            }\n\n            secure = COMMAND.isSecure() && cr.wasSecure() && UtilsDevice.isDeviceLocked(this.mContext);\n\n            switch (COMMAND) {\n\n                case COMMAND_CANCEL:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_CANCEL.name());\n                    }\n\n                    request.setUtterance(PersonalityResponse.getCancelled(mContext, sl));\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n\n                    break;\n                case COMMAND_SPELL:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_SPELL.name());\n                    }\n\n                    outcome = new CommandSpell().getResponse(mContext, toResolve, sl, cr);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n\n                    qubit = outcome.getQubit();\n                    publishProgress(outcome.getEntangledPair());\n\n                    break;\n                case COMMAND_TRANSLATE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_TRANSLATE.name());\n                    }\n\n                    outcome = new CommandTranslate().getResponse(mContext, toResolve, sl, cr);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setTTSLocale(outcome.getTTSLocale());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n\n                    if (result == Outcome.SUCCESS) {\n                        request.setCondition(Condition.CONDITION_TRANSLATION);\n                        publishProgress(outcome.getEntangledPair());\n                        qubit = outcome.getQubit();\n                    }\n\n                    break;\n                case COMMAND_PARDON:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_PARDON.name());\n                    }\n\n                    final Memory memory = MemoryHelper.getMemory(mContext);\n\n                    if (!secure) {\n                        request.setUtterance(memory.getUtterance());\n                        request.setTTSLocale(UtilsLocale.stringToLocale(memory.getTTSLanguage()));\n                        request.setVRLocale(UtilsLocale.stringToLocale(memory.getVRLanguage()));\n                        request.setAction(memory.getAction());\n                        request.setUtteranceArray(memory.getUtteranceArray());\n                        result = Outcome.SUCCESS;\n\n                        switch (memory.getCondition()) {\n\n                            case Condition.CONDITION_TRANSLATION:\n                                request.setCondition(Condition.CONDITION_TRANSLATION);\n                                break;\n                            case Condition.CONDITION_NONE:\n                            case Condition.CONDITION_CONVERSATION:\n                            case Condition.CONDITION_ROOT:\n                            case Condition.CONDITION_USER_CUSTOM:\n                            case Condition.CONDITION_EMOTION:\n                            case Condition.CONDITION_IDENTITY:\n                            case Condition.CONDITION_GOOGLE_NOW:\n                            case Condition.CONDITION_IGNORE:\n                            default:\n                                request.setCondition(Condition.CONDITION_IGNORE);\n                                break;\n                        }\n                    } else {\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        request.setUtterance(PersonalityResponse.getSecureErrorResponse(mContext, sl));\n                        result = Outcome.SUCCESS;\n                    }\n\n                    break;\n                case COMMAND_USER_NAME:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_USER_NAME.name());\n                    }\n\n                    outcome = new CommandUserName().getResponse(mContext, toResolve, sl, cr);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n\n                    break;\n                case COMMAND_BATTERY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_BATTERY.name());\n                    }\n\n                    outcome = new CommandBattery().getResponse(mContext, toResolve, sl, cr);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n\n                    break;\n                case COMMAND_SONG_RECOGNITION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_SONG_RECOGNITION.name());\n                    }\n\n                    outcome = new CommandSongRecognition().getResponse(mContext, toResolve, sl);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n                    qubit = outcome.getQubit();\n\n                    break;\n                case COMMAND_WOLFRAM_ALPHA:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_WOLFRAM_ALPHA.name());\n                    }\n\n                    outcome = new CommandWolframAlpha().getResponse(mContext, toResolve, sl, cr);\n\n                    request.setUtterance(outcome.getUtterance());\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    result = outcome.getOutcome();\n\n                    break;\n                case COMMAND_TASKER:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_TASKER.name());\n                    }\n\n                    if (!secure) {\n\n                        outcome = new CommandTasker().getResponse(mContext, toResolve, sl, cr);\n\n                        request.setUtterance(outcome.getUtterance());\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        result = outcome.getOutcome();\n                    } else {\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        request.setUtterance(PersonalityResponse.getSecureErrorResponse(mContext, sl));\n                        result = Outcome.SUCCESS;\n                    }\n\n                    break;\n                case COMMAND_EMOTION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_EMOTION.name());\n                    }\n\n                    if (!secure) {\n\n                        outcome = new CommandEmotion().getResponse(mContext, sl);\n\n                        request.setUtterance(outcome.getUtterance());\n                        request.setAction(LocalRequest.ACTION_SPEAK_LISTEN);\n                        request.setCondition(Condition.CONDITION_EMOTION);\n                        result = outcome.getOutcome();\n                    } else {\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        request.setUtterance(PersonalityResponse.getSecureErrorResponse(mContext, sl));\n                        result = Outcome.SUCCESS;\n                    }\n\n                    break;\n                case COMMAND_HOTWORD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_HOTWORD.name());\n                    }\n\n                    //noinspection NewApi\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP\n                            && !PermissionHelper.checkUsageStatsPermission(mContext)) {\n\n                        if (ExecuteIntent.settingsIntent(mContext, IntentConstants.SETTINGS_USAGE_STATS)) {\n\n                            request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                            request.setUtterance(SaiyResourcesHelper.getStringResource(mContext, sl,\n                                    R.string.app_speech_usage_stats));\n                        } else {\n\n                            request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                            request.setUtterance(SaiyResourcesHelper.getStringResource(mContext, sl,\n                                    R.string.issue_usage_stats_bug));\n                        }\n                    } else {\n                        request.setAction(LocalRequest.ACTION_TOGGLE_HOTWORD);\n                    }\n\n                    result = Outcome.SUCCESS;\n\n                    break;\n                case COMMAND_VOICE_IDENTIFY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_VOICE_IDENTIFY.name());\n                    }\n\n                    outcome = new CommandVocalRecognition().getResponse(mContext, sl);\n                    result = outcome.getOutcome();\n                    request.setUtterance(outcome.getUtterance());\n\n                    if (result == Outcome.SUCCESS) {\n                        request.setAction(LocalRequest.ACTION_SPEAK_LISTEN);\n                        request.setCondition(Condition.CONDITION_IDENTIFY);\n                        request.setIdentityProfile((String) outcome.getExtra());\n                    } else {\n                        qubit = outcome.getQubit();\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    }\n\n                    break;\n                case COMMAND_UNKNOWN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_UNKNOWN.name());\n                    }\n\n                    final int unknownAction = SPH.getCommandUnknownAction(mContext);\n\n                    switch (unknownAction) {\n\n                        case Unknown.UNKNOWN_STATE:\n                        case Unknown.UNKNOWN_REPEAT:\n                            if (SPH.getToastUnknown(mContext)) {\n                                final EntangledPair entangledPair = new EntangledPair(Position.TOAST_SHORT,\n                                        CC.COMMAND_UNKNOWN);\n                                entangledPair.setToastContent(toResolve.get(0) + \"??\");\n                                publishProgress(entangledPair);\n                            }\n                            break;\n                    }\n\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n\n                    switch (unknownAction) {\n\n                        case Unknown.UNKNOWN_STATE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Unknown.UNKNOWN_STATE\");\n                            }\n                            request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                            break;\n                        case Unknown.UNKNOWN_REPEAT:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Unknown.UNKNOWN_STATE\");\n                            }\n                            request.setUtterance(PersonalityResponse.getRepeatCommand(mContext, sl));\n                            request.setAction(LocalRequest.ACTION_SPEAK_LISTEN);\n                            break;\n                        case Unknown.UNKNOWN_GOOGLE_SEARCH:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Unknown.UNKNOWN_STATE\");\n                            }\n\n                            if (!ExecuteIntent.googleNow(mContext, toResolve.get(0))) {\n                                request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                                SPH.setCommandUnknownAction(mContext, Unknown.UNKNOWN_STATE);\n\n                                final EntangledPair entangledPair = new EntangledPair(Position.TOAST_SHORT, CC.COMMAND_UNKNOWN);\n                                entangledPair.setToastContent(mContext.getString(R.string.error_google_now_broadcast));\n                                publishProgress(entangledPair);\n                            }\n                            break;\n                        case Unknown.UNKNOWN_WOLFRAM_ALPHA:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Unknown.UNKNOWN_STATE\");\n                            }\n\n                            if (!ExecuteIntent.wolframAlpha(mContext, toResolve.get(0))) {\n                                request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                                SPH.setCommandUnknownAction(mContext, Unknown.UNKNOWN_STATE);\n\n                                final EntangledPair entangledPair = new EntangledPair(Position.TOAST_SHORT, CC.COMMAND_UNKNOWN);\n                                entangledPair.setToastContent(mContext.getString(R.string.error_wolfram_alpha_broadcast));\n                                publishProgress(entangledPair);\n                            }\n                            break;\n                        case Unknown.UNKNOWN_TASKER:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Unknown.UNKNOWN_STATE\");\n                            }\n\n                            if (!TaskerHelper.broadcastVoiceData(mContext, toResolve)) {\n                                request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                                SPH.setCommandUnknownAction(mContext, Unknown.UNKNOWN_STATE);\n\n                                final EntangledPair entangledPair = new EntangledPair(Position.TOAST_SHORT, CC.COMMAND_UNKNOWN);\n                                entangledPair.setToastContent(mContext.getString(R.string.error_tasker_broadcast));\n                                publishProgress(entangledPair);\n                            }\n                            break;\n                    }\n\n                    break;\n                case COMMAND_EMPTY_ARRAY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_EMPTY_ARRAY.name());\n                    }\n\n                    request.setUtterance(PersonalityResponse.getErrorProfanityFilter(mContext, sl));\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n\n                    break;\n                case COMMAND_USER_CUSTOM:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_USER_CUSTOM.name());\n                    }\n\n                    if (!secure) {\n\n                        outcome = new CommandCustom().getResponse(mContext, cch.getCommand(), sl, cr);\n\n                        request.setUtterance(outcome.getUtterance());\n                        request.setAction(outcome.getAction());\n                        result = outcome.getOutcome();\n                    } else {\n                        request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                        request.setUtterance(PersonalityResponse.getSecureErrorResponse(mContext, sl));\n                        result = Outcome.SUCCESS;\n                    }\n\n                    break;\n                case COMMAND_SOMETHING_WEIRD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"DT \" + CC.COMMAND_SOMETHING_WEIRD.name());\n                    }\n\n                    request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"DT DEFAULT\");\n                    }\n\n                    request.setUtterance(PersonalityResponse.getNoComprende(mContext, sl));\n                    request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    request.setCommand(CC.COMMAND_UNKNOWN);\n\n                    break;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"DT networkProceed failed\");\n            }\n\n            request.setUtterance(PersonalityResponse.getNoNetwork(mContext, sl));\n            request.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n            request.setCommand(CC.COMMAND_UNKNOWN);\n        }\n\n        return qubit;\n    }\n\n    @Override\n    protected void onSuperposition(final Qubit qubit) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onSuperposition secure: \" + secure);\n        }\n\n        if (validateQubit(qubit)) {\n\n            switch (COMMAND) {\n\n                case COMMAND_CANCEL:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_CANCEL.name());\n                    }\n                    break;\n                case COMMAND_SPELL:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_SPELL.name());\n                    }\n\n                    if (result == Outcome.SUCCESS) {\n                        ClipboardHelper.setClipboardContent(mContext, qubit.getSpellContent());\n                    }\n\n                    break;\n                case COMMAND_TRANSLATE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_TRANSLATE.name());\n                    }\n\n                    if (result == Outcome.SUCCESS) {\n                        ClipboardHelper.setClipboardContent(mContext, qubit.getTranslatedText());\n                    }\n\n                    break;\n                case COMMAND_PARDON:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_PARDON.name());\n                    }\n                    break;\n                case COMMAND_USER_NAME:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_USER_NAME.name());\n                    }\n                    break;\n                case COMMAND_BATTERY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_BATTERY.name());\n                    }\n                    break;\n                case COMMAND_SONG_RECOGNITION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_SONG_RECOGNITION.name());\n                    }\n\n                    if (result == Outcome.FAILURE) {\n                        Intent intent = new Intent(mContext, ActivityChooserDialog.class);\n                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n                        final Bundle bundle = new Bundle();\n                        bundle.putParcelableArrayList(SongRecognitionChooser.PARCEL_KEY,\n                                qubit.getSongRecognitionChooserList());\n                        intent.putExtras(bundle);\n\n                        mContext.startActivity(intent);\n                    }\n\n                    break;\n                case COMMAND_WOLFRAM_ALPHA:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_WOLFRAM_ALPHA.name());\n                    }\n                    break;\n                case COMMAND_TASKER:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_TASKER.name());\n                    }\n                    break;\n                case COMMAND_EMOTION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_EMOTION.name());\n                    }\n                    break;\n                case COMMAND_HOTWORD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_HOTWORD.name());\n                    }\n                    break;\n                case COMMAND_VOICE_IDENTIFY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_VOICE_IDENTIFY.name());\n                    }\n\n                    if (result == Outcome.FAILURE) {\n                        final Bundle bundle = new Bundle();\n                        bundle.putInt(ActivityHome.FRAGMENT_INDEX, ActivityHome.INDEX_FRAGMENT_SUPER_USER);\n                        ExecuteIntent.saiyActivity(mContext, ActivityHome.class, bundle, true);\n                    }\n\n                    break;\n                case COMMAND_UNKNOWN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_UNKNOWN.name());\n                    }\n                    break;\n                case COMMAND_EMPTY_ARRAY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_EMPTY_ARRAY.name());\n                    }\n                    break;\n                case COMMAND_USER_CUSTOM:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_USER_CUSTOM.name());\n                    }\n                    break;\n                case COMMAND_SOMETHING_WEIRD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"OSP \" + CC.COMMAND_SOMETHING_WEIRD.name());\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"OSP CC.DEFAULT\");\n                    }\n                    break;\n            }\n        }\n\n        NotificationHelper.cancelComputingNotification(mContext);\n\n        if (DEBUG) {\n            MyLog.getElapsed(Quantum.class.getSimpleName(), then);\n        }\n\n        request.execute();\n    }\n\n\n    @Override\n    protected void onEntanglement(final EntangledPair entangledPair) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onEntanglement secure: \" + secure);\n        }\n\n        if (validatePosition(entangledPair)) {\n\n            switch (entangledPair.getPosition()) {\n\n                case TOAST_SHORT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getPosition().name());\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getCC().name());\n                        MyLog.i(CLS_NAME, \"Position getToastContent: \" + entangledPair.getToastContent());\n                    }\n                    Toast.makeText(mContext, entangledPair.getToastContent(), Toast.LENGTH_SHORT).show();\n                    break;\n                case TOAST_LONG:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getPosition().name());\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getCC().name());\n                        MyLog.i(CLS_NAME, \"Position getToastContent: \" + entangledPair.getToastContent());\n                    }\n                    Toast.makeText(mContext, entangledPair.getToastContent(), Toast.LENGTH_LONG).show();\n                    break;\n                case SPEAK:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getPosition().name());\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getCC().name());\n                    }\n\n                    final LocalRequest opuRequest = new LocalRequest(mContext);\n                    opuRequest.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n                    opuRequest.setUtterance(entangledPair.getUtterance());\n                    opuRequest.setTTSLocale(ttsLocale);\n                    opuRequest.setVRLocale(vrLocale);\n                    opuRequest.setSupportedLanguage(sl);\n                    opuRequest.execute();\n\n                    break;\n                case CLIPBOARD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getPosition().name());\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getCC().name());\n                    }\n\n                    ClipboardHelper.saveClipboardContent(mContext);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position: CLIPBOARD content: \" + ClipboardHelper.getClipboardContent());\n                    }\n                    break;\n                case SHOW_COMPUTING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getPosition().name());\n                        MyLog.i(CLS_NAME, \"Position \" + entangledPair.getCC().name());\n                    }\n                    NotificationHelper.createComputingNotification(mContext);\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Position: default\");\n                    }\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Qubit.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionChooser;\n\n/**\n * Created by benrandall76@gmail.com on 13/06/2016.\n */\npublic class Qubit {\n\n    private String clipboardContent;\n    private String spellContent;\n    private String translatedText;\n    private ArrayList<SongRecognitionChooser> songRecognitionChooserList;\n\n    public ArrayList<SongRecognitionChooser> getSongRecognitionChooserList() {\n        return songRecognitionChooserList;\n    }\n\n    public void setSongRecognitionChooserList(@NonNull final ArrayList<SongRecognitionChooser> songRecognitionChooserList) {\n        this.songRecognitionChooserList = songRecognitionChooserList;\n    }\n\n    public String getTranslatedText() {\n        return translatedText;\n    }\n\n    public void setTranslatedText(@NonNull final String translatedText) {\n        this.translatedText = translatedText;\n    }\n\n    public String getSpellContent() {\n        return spellContent;\n    }\n\n    public void setSpellContent(@NonNull final String spellContent) {\n        this.spellContent = spellContent;\n    }\n\n    public String getClipboardContent() {\n        return clipboardContent;\n    }\n\n    public void setClipboardContent(@NonNull final String clipboardContent) {\n        this.clipboardContent = clipboardContent;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/Tunnelling.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.processing;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\n\nimport java.util.Collections;\nimport java.util.Locale;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.custom.CustomCommandHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * If you are here to read more about the quantum methods used to resolve commands, I'm afraid\n * you are going to be a little disappointed...\n * <p>\n * In actual fact, the main {@link Quantum} class is built upon the standard Android AsyncTask. It\n * seemed so boring that such an integral part of the application functioned in this way, that I\n * decided to implement this class purely so I could change the names of the default methods, in\n * an attempt to make it appear more exciting. Sorry....\n * <p>\n * So, this really is the main Class for resolving and actioning a spoken command.\n * It uses the standard AsyncTask as it pretty much ticks all of the boxes for what is required as a base\n * at this point in the application process - That would be the ability to call the UI thread\n * through {@link #onProgressUpdate(EntangledPair...)} (whilst we continue background processing elsewhere)\n * and also in {@link #onPostExecute(Qubit)}, if required. Handy.\n * <p>\n * The {@link Quantum} class will inevitably become very long, as each command is assigned an Enum identifier\n * and we use the case/switch statement to enter the correct one, where analysis relative to the command\n * is performed. Regardless or not of whether the response is resolved externally, we still need to know\n * how to 'behave' at the end.\n * <p>\n * I have gone through many iterations of how best to construct what could be considered as the global\n * command resolver, and this is the fastest so far and remains relatively readable, despite the size.\n * <p>\n * Making up any lost performance is better done in the command detection process, preparation and\n * sorting classes themselves, rather than {@link Quantum}, where there may be only minimal gains to be had.\n * <p>\n * The performance of a large number of Enum constants (and extended defaults) over integers is negligible\n * and outweighed certainly by type safety, but perhaps also negated by the resource of any equivalently\n * functioning implementation.\n * <p>\n * Created by benrandall76@gmail.com on 18/09/2016.\n */\n\nabstract class Tunnelling extends AsyncTask<CommandRequest, EntangledPair, Qubit> {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = Tunnelling.class.getSimpleName();\n\n    private static final long COMPUTING_DELAY = 1000L;\n    public static final long CLIPBOARD_DELAY = 175L;\n\n    protected int result = Outcome.SUCCESS;\n    CC COMMAND = CC.COMMAND_UNKNOWN;\n    private final Timer timer = new Timer();\n    protected final long then;\n    protected boolean secure;\n\n    protected Locale vrLocale;\n    protected Locale ttsLocale;\n    protected SupportedLanguage sl;\n\n    CustomCommandHelper cch;\n\n    protected final LocalRequest request;\n    protected final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    Tunnelling(@NonNull final Context mContext) {\n        this.mContext = mContext.getApplicationContext();\n        this.request = new LocalRequest(this.mContext);\n        then = System.nanoTime();\n    }\n\n    /**\n     * Set a countdown timer to show the processing notification, so they user doesn't think nothing is\n     * happening should there be a delay - caused by network latency perhaps.\n     */\n    @Override\n    protected void onPreExecute() {\n        super.onPreExecute();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPreExecute\");\n        }\n\n        final TimerTask timerTask = new TimerTask() {\n            @Override\n            public void run() {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"timerTask: show computing\");\n                }\n                onProgressUpdate(new EntangledPair(Position.SHOW_COMPUTING, CC.COMMAND_UNKNOWN));\n            }\n        };\n\n        timer.schedule(timerTask, COMPUTING_DELAY);\n    }\n\n    @Override\n    protected Qubit doInBackground(final CommandRequest... commandRequestArray) {\n\n        final CommandRequest cr = commandRequestArray[0];\n        vrLocale = cr.getVRLocale(mContext);\n        ttsLocale = cr.getTTSLocale(mContext);\n        sl = cr.getSupportedLanguage();\n\n        request.setVRLocale(vrLocale);\n        request.setTTSLocale(ttsLocale);\n\n        cr.getResultsArray().removeAll(Collections.singleton(\"\"));\n        cr.getResultsArray().removeAll(Collections.<String>singleton(null));\n\n        return doTunnelling(cr);\n    }\n\n    protected abstract Qubit doTunnelling(final CommandRequest commandRequest);\n\n    @Override\n    protected void onProgressUpdate(final EntangledPair... entangledPairArray) {\n        onEntanglement(entangledPairArray[0]);\n    }\n\n    protected abstract void onEntanglement(final EntangledPair entangledPair);\n\n    @Override\n    protected void onPostExecute(final Qubit qubit) {\n        cancelTimer();\n        onSuperposition(qubit);\n    }\n\n    protected abstract void onSuperposition(final Qubit qubit);\n\n    /**\n     * Helper method to remove clutter when checking to see if the Pair contains any instructions.\n     *\n     * @param entangledPair the {@link EntangledPair} containing possible instructions\n     * @return true if instructions are present\n     */\n    boolean validatePosition(final EntangledPair entangledPair) {\n        return entangledPair != null && entangledPair.getPosition() != null && entangledPair.getCC() != null;\n    }\n\n    /**\n     * Helper method to remove clutter when checking to see if the Pair contains any instructions.\n     *\n     * @param qubit the {@link Qubit} containing possible instructions\n     * @return true if instructions are present\n     */\n    boolean validateQubit(final Qubit qubit) {\n        return qubit != null;\n    }\n\n    /**\n     * Cancel the notification timer, as it's no longer needed.\n     */\n    private void cancelTimer() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelTimer\");\n        }\n\n        try {\n            timer.cancel();\n            timer.purge();\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"cancelTimer: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"cancelTimer: Exception\");\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/processing/helper/QuantumHelper.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.processing.helper;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.custom.CustomCommandHelper;\nimport ai.saiy.android.custom.CustomHelper;\nimport ai.saiy.android.custom.CustomHelperHolder;\nimport ai.saiy.android.custom.CustomResolver;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.local.Profanity;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class QuantumHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = QuantumHelper.class.getSimpleName();\n\n    public CustomResolver resolve(@NonNull final Context ctx, @NonNull final SupportedLanguage sl,\n                                  @NonNull final CommandRequest cr) {\n\n        final long then = System.nanoTime();\n\n        final CustomHelperHolder holder = new CustomHelper().getCustomisationHolder(ctx);\n\n        final CustomResolver resolver = new CustomResolver();\n        final CustomCommandHelper cch = new CustomCommandHelper();\n\n        final ArrayList<String> manipulatedVoiceData = new Profanity(ctx, cr.getResultsArray(), sl).remove();\n\n        resolver.setCustom(cch.isCustomCommand(ctx, manipulatedVoiceData, sl, holder.getCustomCommandArray()));\n        resolver.setCustomCommandHelper(cch);\n        resolver.setVoiceData(manipulatedVoiceData);\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return resolver;\n\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/ProximityContext.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityContext {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/hardware/ProximityBluetooth.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity.hardware;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityBluetooth {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/hardware/ProximityWiFi.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity.hardware;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityWiFi {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/location/ProximityCellTower.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity.location;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityCellTower {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/location/ProximityContact.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity.location;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityContact {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/proximity/location/ProximityGPS.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.proximity.location;\n\n/**\n * Created by benrandall76@gmail.com on 06/07/2016.\n */\n\npublic class ProximityGPS {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/Recognition.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Set the global state of recognition, so regardless of the implementation used, it can be\n * quickly cancelled if required.\n * <p/>\n * This is a necessary evil. Must be reset under all error circumstances or after multiple attempts.\n * <p/>\n * Created by benrandall76@gmail.com on 08/02/2016.\n */\npublic class Recognition {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Recognition.class.getSimpleName();\n\n    private static volatile State state = State.IDLE;\n\n    public enum State {\n        IDLE,\n        LISTENING,\n        PROCESSING\n    }\n\n    /**\n     * Set the global state of recognition\n     */\n    public static void setState(@NonNull final State newState) {\n\n        state = newState;\n\n        switch (state) {\n            case IDLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setState: IDLE\");\n                }\n                break;\n            case LISTENING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setState: LISTENING\");\n                }\n                break;\n            case PROCESSING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setState: PROCESSING\");\n                }\n                break;\n        }\n    }\n\n    /**\n     * Get the global state of recognition\n     */\n    public static State getState() {\n\n        switch (state) {\n            case IDLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getState: IDLE\");\n                }\n                break;\n            case LISTENING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getState: LISTENING\");\n                }\n                break;\n            case PROCESSING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getState: PROCESSING\");\n                }\n                break;\n        }\n\n        return state;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/RecognitionAction.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.speech.RecognizerIntent;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.processing.Quantum;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Class that determines where to send the recognition results.\n * <p/>\n * Created by benrandall76@gmail.com on 09/02/2016.\n */\npublic class RecognitionAction {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionAction.class.getSimpleName();\n\n    private final Context mContext;\n    private final ArrayList<String> resultsRecognition;\n    private final float[] confidenceScores;\n    private final Locale vrLocale;\n    private final Locale ttsLocale;\n    private final SupportedLanguage sl;\n    private final Bundle bundle;\n\n    /**\n     * Constructor.\n     *\n     * @param mContext  the application context\n     * @param vrLocale  the voice recognition {@link Locale}\n     * @param ttsLocale the Text to Speech {@link Locale}\n     */\n    public RecognitionAction(@NonNull final Context mContext, @NonNull final Locale vrLocale,\n                             @NonNull final Locale ttsLocale, @NonNull final SupportedLanguage sl,\n                             @NonNull final Bundle bundle) {\n        this.mContext = mContext.getApplicationContext();\n        this.bundle = bundle;\n\n        this.resultsRecognition = this.bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n        this.confidenceScores = this.bundle.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES);\n        this.vrLocale = vrLocale;\n        this.ttsLocale = ttsLocale;\n        this.sl = sl;\n\n        resolve();\n    }\n\n    /**\n     * Resolve the command and check for other conditions such as conversation or root commands.\n     * <p/>\n     * Will either forward to {@link Quantum} or start an error response.\n     * <p/>\n     * The instance of {@link Quantum} should never be requested to run more than once and in\n     * theory shouldn't be possible. For the sake of weird and wonderful error handling, we will use\n     * an {@link AsyncTask#THREAD_POOL_EXECUTOR} so even if an instance is running, a new instance\n     * is started in parallel to prevent a bottle-neck. This could produce some undesired side-effects\n     * though.\n     */\n    @SuppressWarnings(\"unchecked\")\n    private void resolve() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolve\");\n        }\n\n        if (UtilsList.notNaked(resultsRecognition)) {\n\n            final CommandRequest cr = new CommandRequest(vrLocale, ttsLocale, sl);\n            cr.setResultsArray(resultsRecognition);\n            cr.setConfidenceArray(confidenceScores);\n            cr.setWasSecure(bundle.getBoolean(RecognizerIntent.EXTRA_SECURE, false));\n\n            final Quantum quantum = new Quantum(mContext);\n\n            switch (bundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n                case Condition.CONDITION_NONE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_NONE\");\n                    }\n\n                    quantum.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, cr);\n                    break;\n                case Condition.CONDITION_CONVERSATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_CONVERSATION\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_ROOT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_ROOT\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_TRANSLATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_TRANSLATION\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_USER_CUSTOM:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_USER_CUSTOM\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_EMOTION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_EMOTION\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_IDENTITY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTITY\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_IDENTIFY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTIFY\");\n                    }\n                    // TODO\n                    break;\n                case Condition.CONDITION_GOOGLE_NOW:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Condition.CONDITION_GOOGLE_NOW\");\n                    }\n                    // TODO - this is in danger of creating multiple instances\n\n                    quantum.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, cr);\n                    break;\n                case Condition.CONDITION_IGNORE:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Condition.CONDITION_IGNORE\");\n                    }\n                    sendErrorLocalRequest();\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Condition.default\");\n                    }\n                    sendErrorLocalRequest();\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"recognition results naked\");\n            }\n            sendErrorLocalRequest();\n        }\n    }\n\n    /**\n     * Utility method to notify the user of a processing error\n     */\n    private void sendErrorLocalRequest() {\n        final LocalRequest lr = new LocalRequest(mContext);\n        lr.setAction(LocalRequest.ACTION_SPEAK_ONLY);\n        lr.setUtterance(PersonalityResponse.getErrorEmptyVoiceData(mContext, sl));\n        lr.setTTSLocale(ttsLocale);\n        lr.setVRLocale(vrLocale);\n        lr.setSupportedLanguage(sl);\n        lr.execute();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/SaiyHotwordListener.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\nimport edu.cmu.pocketsphinx.Hypothesis;\nimport edu.cmu.pocketsphinx.RecognitionListener;\n\n/**\n * Created by benrandall76@gmail.com on 04/09/2016.\n */\n\npublic class SaiyHotwordListener implements RecognitionListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyHotwordListener.class.getSimpleName();\n\n    protected static final String OKAY_GOOGLE = \"okaygoogle\";\n    protected static final String WAKEUP_SAIY = \"wakeupsay\";\n    protected static final String STOP_LISTENING = \"stoplistening\";\n\n    private static final Pattern pOKAY_GOOGLE = Pattern.compile(OKAY_GOOGLE);\n    private static final Pattern pWAKEUP_SAIY = Pattern.compile(WAKEUP_SAIY);\n    private static final Pattern pSTOP_LISTENING = Pattern.compile(STOP_LISTENING);\n\n    public static final int ERROR_NULL = 1;\n    public static final int ERROR_INITIALISE = 2;\n    public static final int ERROR_PERMISSIONS = 3;\n\n    private boolean hotwordDetected;\n\n    public void onHotwordInitialised() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onHotwordInitialised\");\n        }\n        hotwordDetected = false;\n    }\n\n    public void onHotwordStarted() {\n    }\n\n    public void onHotwordDetected(@NonNull final String hotword) {\n    }\n\n    public void onHotwordError(final int errorCode) {\n    }\n\n    public void onHotwordShutdown() {\n    }\n\n    @Override\n    public void onBeginningOfSpeech() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBeginningOfSpeech\");\n        }\n    }\n\n    @Override\n    public void onEndOfSpeech() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onEndOfSpeech\");\n        }\n        System.gc();\n    }\n\n    @Override\n    public void onPartialResult(final Hypothesis hypothesis) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPartialResult: hotwordDetected: \" + hotwordDetected);\n        }\n\n        if (hypothesis != null) {\n\n            if (!hotwordDetected) {\n\n                final String detected = hypothesis.getHypstr();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onPartialResult: detected: \" + detected);\n                }\n\n                if (UtilsString.notNaked(detected)) {\n\n                    hotwordDetected = true;\n\n                    if (pOKAY_GOOGLE.matcher(detected.trim()).matches()) {\n                        onHotwordDetected(OKAY_GOOGLE);\n                    } else if (pWAKEUP_SAIY.matcher(detected.trim()).matches()) {\n                        onHotwordDetected(WAKEUP_SAIY);\n                    } else if (pSTOP_LISTENING.matcher(detected.trim()).matches()) {\n                        onHotwordDetected(STOP_LISTENING);\n                    } else {\n                        hotwordDetected = false;\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onResult(final Hypothesis hypothesis) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResult\");\n        }\n    }\n\n    @Override\n    public void onError(final Exception e) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onError\");\n        }\n    }\n\n    @Override\n    public void onTimeout() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTimeout\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/SaiyRecognitionListener.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.speech.RecognitionListener;\nimport android.speech.RecognizerIntent;\nimport android.speech.SpeechRecognizer;\n\nimport ai.saiy.android.partial.IPartial;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to handle the current bugs in the SpeechRecognizer implementation of Google 'Now'.\n * <p>\n * The boolean markers make sure that the {@link RecognitionListener} provides callbacks in the\n * correct order and ignores the ones that are caused by bugs.\n * <p>\n * Created by benrandall76@gmail.com on 23/04/2016.\n */\npublic class SaiyRecognitionListener implements RecognitionListener, IPartial {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyRecognitionListener.class.getSimpleName();\n\n    private boolean doError;\n    private boolean doEndOfSpeech;\n    private boolean doBeginningOfSpeech;\n\n    public void resetBugVariables() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resetBugVariables\");\n        }\n\n        doError = false;\n        doEndOfSpeech = false;\n        doBeginningOfSpeech = false;\n    }\n\n    /**\n     * Called when the endpointer is ready for the user to start speaking.\n     *\n     * @param params parameters set by the recognition service. Reserved for future use.\n     */\n    @Override\n    public void onReadyForSpeech(final Bundle params) {\n        doError = true;\n        doEndOfSpeech = true;\n        doBeginningOfSpeech = true;\n    }\n\n    /**\n     * The user has started to speak.\n     */\n    @Override\n    public void onBeginningOfSpeech() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBeginningOfSpeech: doEndOfSpeech: \" + doEndOfSpeech);\n            MyLog.i(CLS_NAME, \"onBeginningOfSpeech: doError: \" + doError);\n            MyLog.i(CLS_NAME, \"onBeginningOfSpeech: doBeginningOfSpeech: \" + doBeginningOfSpeech);\n        }\n\n        if (doBeginningOfSpeech) {\n            doBeginningOfSpeech = false;\n            onBeginningOfRecognition();\n        }\n\n    }\n\n    public void onBeginningOfRecognition() {\n    }\n\n    /**\n     * The sound level in the audio stream has changed. There is no guarantee that this method will\n     * be called.\n     *\n     * @param rmsdB the new RMS dB value\n     */\n    @Override\n    public void onRmsChanged(final float rmsdB) {\n    }\n\n    /**\n     * More sound has been received. The purpose of this function is to allow giving feedback to the\n     * user regarding the captured audio. There is no guarantee that this method will be called.\n     *\n     * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a\n     *               single channel audio stream. The sample rate is implementation dependent.\n     */\n    @Override\n    public void onBufferReceived(final byte[] buffer) {\n    }\n\n    /**\n     * Called after the user stops speaking.\n     */\n    @Override\n    public void onEndOfSpeech() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onEndOfSpeech: doEndOfSpeech: \" + doEndOfSpeech);\n            MyLog.i(CLS_NAME, \"onEndOfSpeech: doError: \" + doError);\n            MyLog.i(CLS_NAME, \"onEndOfSpeech: doBeginningOfSpeech: \" + doBeginningOfSpeech);\n        }\n\n        if (doEndOfSpeech) {\n            onEndOfRecognition();\n        }\n    }\n\n    public void onEndOfRecognition() {\n    }\n\n    /**\n     * A network or recognition error occurred.\n     *\n     * @param error code is defined in {@link SpeechRecognizer}\n     */\n    @Override\n    public void onError(final int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError: \" + error);\n            MyLog.w(CLS_NAME, \"onError: doEndOfSpeech: \" + doEndOfSpeech);\n            MyLog.w(CLS_NAME, \"onError: doError: \" + doError);\n            MyLog.i(CLS_NAME, \"onError: doBeginningOfSpeech: \" + doBeginningOfSpeech);\n        }\n\n        if (error != SpeechRecognizer.ERROR_NO_MATCH) {\n            doError = true;\n        }\n\n        if (doError) {\n            onRecognitionError(error);\n        }\n    }\n\n\n    /**\n     * A network or recognition error occurred.\n     *\n     * @param error code is defined in {@link SpeechRecognizer}\n     */\n    public void onRecognitionError(final int error) {\n    }\n\n    /**\n     * Called when recognition results are ready.\n     *\n     * @param results the recognition results. To retrieve the results in {@code\n     *                ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with\n     *                {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter. A float array of\n     *                confidence values might also be given in {@link SpeechRecognizer#CONFIDENCE_SCORES}.\n     */\n    @Override\n    public void onResults(final Bundle results) {\n    }\n\n    public void onComplete() {\n\n    }\n\n    /**\n     * Called when partial recognition results are available. The callback might be called at any\n     * time between {@link #onBeginningOfSpeech()} and {@link #onResults(Bundle)} when partial\n     * results are ready. This method may be called zero, one or multiple times for each call to\n     * {@link SpeechRecognizer#startListening(Intent)}, depending on the speech recognition\n     * service implementation.  To request partial results, use\n     * {@link RecognizerIntent#EXTRA_PARTIAL_RESULTS}\n     *\n     * @param partialResults the returned results. To retrieve the results in\n     *                       ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with\n     *                       {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter\n     */\n    @Override\n    public void onPartialResults(final Bundle partialResults) {\n    }\n\n    /**\n     * Reserved for adding future events.\n     *\n     * @param eventType the type of the occurred event\n     * @param params    a Bundle containing the passed parameters\n     */\n    @Override\n    public void onEvent(final int eventType, final Bundle params) {\n    }\n\n    @Override\n    public void onCancelDetected() {\n    }\n\n    @Override\n    public void onTranslateDetected() {\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/TestRecognitionAction.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.support.annotation.NonNull;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.command.helper.CommandRequest;\nimport ai.saiy.android.custom.CustomCommandHelper;\nimport ai.saiy.android.database.DBSpeech;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.processing.Quantum;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.debug.DebugAction;\n\n/**\n * Class that handles command from direct text input.\n * <p/>\n * Created by benrandall76@gmail.com on 09/02/2016.\n */\npublic class TestRecognitionAction {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = TestRecognitionAction.class.getSimpleName();\n\n    /**\n     * Constructor.\n     * <p>\n     * Handle the test command text input by the user. This can either be an attempt to test a command, or\n     * an instruction to perform debugging of some sort.\n     *\n     * @param mContext    the application context\n     * @param commandText the command text to test\n     */\n    public TestRecognitionAction(@NonNull final Context mContext, @NonNull final String commandText) {\n\n        if (!commandText.startsWith(MyLog.DO_DEBUG)) {\n\n            final Locale vrLocale = SPH.getVRLocale(mContext);\n\n            final ArrayList<String> mocCommands = new ArrayList<>(1);\n            mocCommands.add(commandText);\n\n            final float[] mocConfidence = new float[1];\n            mocConfidence[0] = 1F;\n\n            final CommandRequest cr = new CommandRequest(vrLocale, SPH.getTTSLocale(mContext),\n                    SupportedLanguage.getSupportedLanguage(vrLocale));\n            cr.setResultsArray(mocCommands);\n            cr.setConfidenceArray(mocConfidence);\n\n            new Quantum(mContext).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, cr);\n        } else {\n            runDebug(mContext, commandText);\n        }\n    }\n\n    /**\n     * The command text was a debugging instruction, handle here\n     *\n     * @param ctx         the application context\n     * @param commandText the debug instruction\n     */\n    private void runDebug(@NonNull final Context ctx, @NonNull final String commandText) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"runDebug: \" + commandText);\n        }\n\n        final String[] instructionArray = commandText.split(MyLog.DO_DEBUG);\n\n        if (instructionArray.length > 1) {\n\n            try {\n\n                final int action = Integer.parseInt(instructionArray[1].trim());\n\n                switch (action) {\n\n                    case DebugAction.DEBUG_TOGGLE_LOGGING:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"runDebug: DEBUG_TOGGLE_LOGGING\");\n                        }\n\n                        if (MyLog.DEBUG) {\n                            MyLog.DEBUG = false;\n                            toast(ctx, ctx.getString(R.string.disabled));\n                        } else {\n                            MyLog.DEBUG = true;\n                            toast(ctx, ctx.getString(R.string.enabled));\n                        }\n                        break;\n                    case DebugAction.DEBUG_VALIDATE_SIGNATURE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"runDebug: DEBUG_VALIDATE_SIGNATURE\");\n                        }\n                        toast(ctx, DebugAction.validateSignatures(ctx) ? ctx.getString(R.string.success)\n                                : ctx.getString(R.string.failed));\n                        break;\n                    case DebugAction.DEBUG_CLEAR_SYNTHESIS:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"runDebug: DEBUG_CLEAR_SYNTHESIS\");\n                        }\n\n                        final DBSpeech speech = new DBSpeech(ctx);\n                        speech.deleteTable();\n                        toast(ctx, ctx.getString(R.string.success));\n                        break;\n                    case DebugAction.DEBUG_CLEAR_CUSTOM_COMMANDS:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"runDebug: DEBUG_CLEAR_CUSTOM_COMMANDS\");\n                        }\n                        toast(ctx, CustomCommandHelper.deleteAllCommands(ctx) ? ctx.getString(R.string.success)\n                                : ctx.getString(R.string.failed));\n                        break;\n                    default:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"runDebug: default\");\n                        }\n                        toast(ctx, ctx.getString(R.string.error) + \" \" + String.valueOf(action));\n                        break;\n                }\n\n            } catch (final NumberFormatException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runDebug NumberFormatException\");\n                    e.printStackTrace();\n                }\n                toast(ctx, ctx.getString(R.string.error));\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runDebug NullPointerException\");\n                    e.printStackTrace();\n                }\n                toast(ctx, ctx.getString(R.string.error));\n            } catch (final IndexOutOfBoundsException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runDebug IndexOutOfBoundsException\");\n                    e.printStackTrace();\n                }\n                toast(ctx, ctx.getString(R.string.error));\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"runDebug Exception\");\n                    e.printStackTrace();\n                }\n                toast(ctx, ctx.getString(R.string.error));\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"runDebug: couldn't extract debug constant\");\n            }\n            toast(ctx, ctx.getString(R.string.error));\n        }\n    }\n\n    /**\n     * Toast the outcome of a debuggable action\n     *\n     * @param ctx        the application context\n     * @param toastWords the words to toast\n     */\n    private void toast(@NonNull final Context ctx, @NonNull final String toastWords) {\n        new Handler(Looper.getMainLooper()).post(new Runnable() {\n            @Override\n            public void run() {\n                Toast.makeText(ctx, toastWords,\n                        Toast.LENGTH_SHORT).show();\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/helper/GoogleNowMonitor.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.helper;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Process;\nimport android.support.annotation.NonNull;\n\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.applications.UtilsApplication;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 05/09/2016.\n */\n\npublic class GoogleNowMonitor {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = GoogleNowMonitor.class.getSimpleName();\n\n    private static final Pattern pPACKAGE_NAME_GOOGLE_NOW = Pattern.compile(IntentConstants.PACKAGE_NAME_GOOGLE_NOW);\n\n    private static final long MAX_DURATION = 180000L;\n    private static final long HISTORY = 10000L;\n    private final long then = System.currentTimeMillis();\n\n    /**\n     * Start monitoring the foreground application to see if/when the user leaves Google Now. Once they do,\n     * the hotword detection can restart. A time limit to keep this process running is defined by\n     * {@link #MAX_DURATION}\n     *\n     * @param ctx the application context\n     */\n    public void start(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"start\");\n        }\n\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);\n\n                boolean timeout = true;\n\n                while ((System.currentTimeMillis() - MAX_DURATION) < then) {\n\n                    try {\n                        Thread.sleep(5000);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n\n                    final String foregroundPackage = UtilsApplication.getForegroundPackage(ctx, HISTORY);\n\n                    if (UtilsString.notNaked(foregroundPackage)) {\n\n                        if (pPACKAGE_NAME_GOOGLE_NOW.matcher(foregroundPackage).matches()) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"foreground remains google\");\n                            }\n                        } else {\n                            GoogleNowMonitor.this.restartHotword(ctx);\n                            timeout = false;\n                            break;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"foreground package null\");\n                        }\n                    }\n                }\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"shutting down: timeout: \" + timeout);\n                }\n\n                if (timeout) {\n                    GoogleNowMonitor.this.shutdownHotword(ctx);\n                }\n            }\n        }).start();\n    }\n\n    /**\n     * Send a request to restart the hotword detection\n     *\n     * @param ctx the application context\n     */\n    private void restartHotword(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"restartHotword\");\n        }\n\n        final LocalRequest request = new LocalRequest(ctx);\n        request.prepareDefault(LocalRequest.ACTION_START_HOTWORD, null);\n        request.execute();\n    }\n\n    /**\n     * Send a request to prevent the hotword detection from restarting\n     *\n     * @param ctx the application context\n     */\n    private void shutdownHotword(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shutdownHotword\");\n        }\n\n        final LocalRequest request = new LocalRequest(ctx);\n        request.prepareDefault(LocalRequest.ACTION_STOP_HOTWORD, null);\n        request.setShutdownHotword();\n        request.execute();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/helper/RecognitionDefaults.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.helper;\n\n/**\n * Created by benrandall76@gmail.com on 03/08/2016.\n */\n\npublic class RecognitionDefaults {\n\n    public static final String GOOGLE_NOW_INTERIM_FIELD = \"com.google.android.apps.gsa.searchplate.widget.StreamingTextView\";\n    public static final String GOOGLE_NOW_FINAL_FIELD = \"com.google.android.apps.gsa.searchplate.SearchPlate\";\n    public static final String GOOGLE_NOW_FINAL_EDIT_TEXT = \"android.widget.EditText\";\n\n    public static final String PREFER_OFFLINE = \"android.speech.extra.PREFER_OFFLINE\";\n    public static final String EXTRA_SECURE = \"android.speech.extras.EXTRA_SECURE\";\n    public static final String EXTRA_CONTINUOUS = \"android.speech.extra.DICTATION_MODE\";\n    public static final String EXTRA_GET_AUDIO_FORMAT = \"android.speech.extra.GET_AUDIO_FORMAT\";\n    public static final String EXTRA_GET_AUDIO = \"android.speech.extra.GET_AUDIO\";\n    public static final String EXTRA_AUDIO_FORMAT = \"audio/AMR\";\n\n    public static final String HOTWORD_CONDITION = \"hotword_condition\";\n    public static final int SHUTDOWN_HOTWORD = 1;\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/android/RecognitionNative.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.android;\n\n/**\n * Created by benrandall76@gmail.com on 10/04/2016.\n */\npublic class RecognitionNative {\n\n    public static final String UNSTABLE_RESULTS = \"android.speech.extra.UNSTABLE_TEXT\";\n    public static final long PAUSE_TIMEOUT = 2000L;\n    public static final int MAX_RESULTS = 10;\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/bluemix/IWebSocketCallback.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.bluemix;\n\nimport org.java_websocket.handshake.ServerHandshake;\n\n/**\n * Created by benrandall76@gmail.com on 04/08/2016.\n */\n\npublic interface IWebSocketCallback {\n\n    void onOpen(final ServerHandshake handshakeData);\n\n    void onMessage(final String message);\n\n    void onClose(final int code, final String reason, final boolean remote);\n\n    void onError(final Exception ex);\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/bluemix/RecognitionBluemix.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.bluemix;\n\nimport android.os.Bundle;\nimport android.os.Process;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonSyntaxException;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.java_websocket.WebSocket;\nimport org.java_websocket.handshake.ServerHandshake;\nimport org.java_websocket.util.Base64;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.nio.charset.Charset;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.language.vr.VRLanguageIBM;\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.configuration.BluemixConfiguration;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.bluemix.Alternative;\nimport ai.saiy.android.nlu.bluemix.NLUBluemix;\nimport ai.saiy.android.nlu.bluemix.ResolveBluemix;\nimport ai.saiy.android.nlu.bluemix.Result;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.recognition.provider.bluemix.mod.TrustAllBluemixWebSocketClient;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class uses the IBM Bluemix speech SDK. At the time of writing, I find it hard to believe the accuracy\n * could be so poor, so I'm going to assume I'm doing something wrong....\n * <p/>\n * Register at <a href=\"http://www.ibm.com/cloud-computing/bluemix/\">IBM Bluemix</a>\n * and register your credentials in {@link BluemixConfiguration}\n * <p/>\n * Created by benrandall76@gmail.com on 21/09/2016.\n */\npublic class RecognitionBluemix implements IMic, IWebSocketCallback {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = RecognitionBluemix.class.getSimpleName();\n\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String CONTENT_TYPE = \"content-type\";\n    private static final long PAUSE_TIMEOUT = 750L;\n    private static final long FINAL_WAIT = 2500L;\n    private static final String AUDIO_FORMAT_DEFAULT = \"audio/l16;rate=16000\";\n\n    private static final String ACTION = \"action\";\n    private static final String BASIC = \"Basic \";\n    private static final String START = \"start\";\n    private static final String DELIMITER = \":\";\n    private static final String INTERIM_RESULTS = \"interim_results\";\n    private static final String CONTINUOUS = \"continuous\";\n    private static final String MAX_ALTERNATIVES = \"max_alternatives\";\n    private static final int MAX_ALTERNATIVE_VALUE = 5;\n    private static final String PROFANITY_FILTER = \"profanity_filter\";\n    private static final String INACTIVITY_TIMEOUT = \"inactivity_timeout\";\n\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private final AtomicBoolean doBeginning = new AtomicBoolean(true);\n    private final AtomicBoolean doError = new AtomicBoolean(true);\n    private final AtomicBoolean doRecordingEnded = new AtomicBoolean(true);\n    private final AtomicBoolean haveFinal = new AtomicBoolean(false);\n    private final AtomicBoolean doneHandshake = new AtomicBoolean();\n    private final AtomicInteger count = new AtomicInteger();\n\n    private final ArrayList<String> partialArray = new ArrayList<>();\n    private final ArrayList<String> resultsArray = new ArrayList<>();\n    private final ArrayList<Float> confidenceArray = new ArrayList<>();\n    private final Bundle bundle = new Bundle();\n\n    private volatile TrustAllBluemixWebSocketClient client;\n\n    private final RecognitionMic mic;\n    private final SaiyRecognitionListener listener;\n    private final SaiyDefaults.LanguageModel languageModel;\n    private final Locale ttsLocale;\n    private final VRLanguageIBM vrLocale;\n    private final SupportedLanguage sl;\n    private final boolean servingRemote;\n\n    /**\n     * Constructor\n     *\n     * @param listener      the associated {@link SaiyRecognitionListener}\n     * @param userName      the Bluemix service user name\n     * @param password      the Bluemix service password\n     * @param languageModel the {@link SaiyDefaults.LanguageModel}\n     * @param ttsLocale     the Text to Speech {@link Locale}\n     * @param vrLocale      the {@link VRLanguageIBM}\n     * @param sl            the {@link SupportedLanguage}\n     * @param servingRemote true if the origin is a remote request\n     */\n    public RecognitionBluemix(@NonNull final SaiyRecognitionListener listener,\n                              @NonNull final String userName,\n                              @NonNull final String password,\n                              @NonNull final SaiyDefaults.LanguageModel languageModel,\n                              @NonNull final Locale ttsLocale, @NonNull final VRLanguageIBM vrLocale,\n                              @NonNull final SupportedLanguage sl,\n                              final boolean servingRemote, @NonNull final RecognitionMic mic) {\n        this.listener = listener;\n        this.languageModel = languageModel;\n        this.ttsLocale = ttsLocale;\n        this.vrLocale = vrLocale;\n        this.sl = sl;\n        this.servingRemote = servingRemote;\n        this.mic = mic;\n\n        this.mic.setMicListener(this);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"language: \" + vrLocale.getLocaleString());\n            MyLog.i(CLS_NAME, \"model: \" + vrLocale.getModel());\n        }\n\n        final String auth = BASIC + Base64.encodeBytes((userName + DELIMITER + password)\n                .getBytes(Charset.forName(Constants.ENCODING_UTF8)));\n\n        final HashMap<String, String> header = new HashMap<>();\n        header.put(CONTENT_TYPE, AUDIO_FORMAT_DEFAULT);\n        header.put(AUTHORIZATION, auth);\n\n        //noinspection ConstantConditions\n        client = new TrustAllBluemixWebSocketClient(BluemixConfiguration.getSpeechURI(this.vrLocale.getModel()),\n                header, this);\n    }\n\n    /**\n     * Attempt to start the client, catching any exceptions\n     *\n     * @return true if the client started successfully, false otherwise\n     */\n    private boolean startClient() {\n\n        try {\n            client.start();\n            return true;\n        } catch (final NoSuchAlgorithmException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"NoSuchAlgorithmException\");\n                e.printStackTrace();\n            }\n        } catch (final KeyManagementException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"KeyManagementException\");\n                e.printStackTrace();\n            }\n        } catch (final InterruptedException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"InterruptedException\");\n                e.printStackTrace();\n            }\n        } catch (final CertificateException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"CertificateException\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Start the recognition.\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startListening\");\n        }\n\n        if (doError.get()) {\n            if (mic.isAvailable()) {\n                if (client != null) {\n                    isRecording.set(true);\n                    mic.startRecording();\n\n                    if (startClient()) {\n\n                        new Thread() {\n                            public void run() {\n                                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                                try {\n\n                                    synchronized (mic.getLock()) {\n                                        while (mic.isRecording()) {\n                                            try {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"mic lock waiting\");\n                                                }\n                                                mic.getLock().wait();\n                                            } catch (final InterruptedException e) {\n                                                if (DEBUG) {\n                                                    MyLog.e(CLS_NAME, \"InterruptedException\");\n                                                    e.printStackTrace();\n                                                }\n                                                break;\n                                            }\n                                        }\n                                    }\n\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"record lock released: interrupted: \" + mic.isInterrupted());\n                                    }\n\n                                } catch (final IllegalStateException e) {\n                                    if (DEBUG) {\n                                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                                        e.printStackTrace();\n                                    }\n                                    onError(SpeechRecognizer.ERROR_NETWORK);\n                                } catch (final NullPointerException e) {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                                        e.printStackTrace();\n                                    }\n                                    onError(SpeechRecognizer.ERROR_NETWORK);\n                                } catch (final Exception e) {\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"Exception\");\n                                        e.printStackTrace();\n                                    }\n                                    onError(SpeechRecognizer.ERROR_NETWORK);\n                                } finally {\n                                    stopListening();\n                                }\n                            }\n                        }.start();\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"startListening client error\");\n                        }\n\n                        onError(SpeechRecognizer.ERROR_NETWORK);\n                        mic.forceAudioShutdown();\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"startListening client null\");\n                    }\n\n                    onError(SpeechRecognizer.ERROR_NETWORK);\n                    mic.forceAudioShutdown();\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"startListening mic unavailable\");\n                }\n\n                onError(Speaker.ERROR_AUDIO);\n                mic.forceAudioShutdown();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"startListening error already thrown\");\n            }\n\n            mic.forceAudioShutdown();\n        }\n    }\n\n    /**\n     * Stop the recognition.\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        if (isRecording.get()) {\n            isRecording.set(false);\n\n            new Thread() {\n                public void run() {\n                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n                    mic.stopRecording();\n                    Recognition.setState(Recognition.State.IDLE);\n\n                    if (!haveFinal.get() || !doError.get()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"stopRecording: sleeping\");\n                        }\n\n                        try {\n                            Thread.sleep(FINAL_WAIT);\n                        } catch (final InterruptedException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"stopRecording: InterruptedException\");\n                                e.printStackTrace();\n                            }\n                        }\n                    }\n\n                    closeConnection();\n                }\n            }.start();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"called stopRecording: isRecording false\");\n            }\n        }\n    }\n\n    /**\n     * Close the web socket connection\n     */\n    private void closeConnection() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"closeConnection\");\n        }\n\n        isRecording.set(false);\n\n        if (client != null) {\n\n            try {\n                client.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"closeConnection: Exception\");\n                    e.printStackTrace();\n                }\n            } finally {\n                client = null;\n            }\n        }\n    }\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n\n        if (client != null && isRecording.get()) {\n\n            count.set(count.get() + 1);\n\n            if (count.get() == 1 && doBeginning.get()) {\n                doBeginning.set(false);\n                listener.onBeginningOfSpeech();\n            }\n\n            final WebSocket.READYSTATE readyState = client.getReadyState();\n\n            // TODO - missed audio\n            switch (readyState) {\n\n                case NOT_YET_CONNECTED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"READYSTATE: NOT_YET_CONNECTED\");\n                    }\n                    break;\n                case CONNECTING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"READYSTATE: CONNECTING\");\n                    }\n                    break;\n                case OPEN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"READYSTATE: OPEN\");\n                    }\n\n                    if (doneHandshake.get()) {\n                        client.send(buffer);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"Waiting for handshake\");\n                        }\n\n                        if (count.get() > 10) {\n                            onError(new Exception(\"AudioRecord.ERROR\"));\n                        }\n                    }\n\n                    break;\n                case CLOSING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"READYSTATE: CLOSING\");\n                    }\n                    break;\n                case CLOSED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"READYSTATE: CLOSED\");\n                    }\n                    break;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onBufferReceived: recording finished\");\n            }\n        }\n    }\n\n    @Override\n    public void onError(final int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        if (doError.get()) {\n            doError.set(false);\n            stopListening();\n\n            Recognition.setState(Recognition.State.IDLE);\n\n            if (mic.isInterrupted()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onError: isInterrupted\");\n                }\n\n                listener.onError(error);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onError: listener onComplete\");\n                }\n\n                listener.onComplete();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onError: doError false\");\n            }\n        }\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            stopListening();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onPauseDetected: isRecording false\");\n            }\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n        listener.onReadyForSpeech(null);\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n\n        if (doRecordingEnded.get()) {\n            doRecordingEnded.set(false);\n            listener.onEndOfSpeech();\n            listener.onComplete();\n            Recognition.setState(Recognition.State.IDLE);\n        }\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFileWriteComplete: \" + success);\n        }\n    }\n\n\n    @Override\n    public void onOpen(final ServerHandshake handshakeData) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onOpen\");\n        }\n\n        final JSONObject jsonObject = new JSONObject();\n\n        try {\n            jsonObject.put(ACTION, START);\n            jsonObject.put(CONTENT_TYPE, AUDIO_FORMAT_DEFAULT);\n            jsonObject.put(INTERIM_RESULTS, true);\n            jsonObject.put(CONTINUOUS, true);\n            jsonObject.put(MAX_ALTERNATIVES, MAX_ALTERNATIVE_VALUE);\n            jsonObject.put(PROFANITY_FILTER, false);\n            jsonObject.put(INACTIVITY_TIMEOUT, PAUSE_TIMEOUT);\n        } catch (final JSONException e) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onOpen: JSONException\");\n                e.printStackTrace();\n            }\n        }\n\n        client.send(jsonObject.toString());\n        doneHandshake.set(true);\n    }\n\n    @Override\n    public void onMessage(final String message) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onMessage: \" + message);\n        }\n\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        NLUBluemix nluBluemix = null;\n\n        try {\n            nluBluemix = gson.fromJson(message, NLUBluemix.class);\n        } catch (final JsonSyntaxException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"JsonSyntaxException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        if (nluBluemix != null) {\n\n            if (UtilsString.notNaked(nluBluemix.getState())) {\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"Status message: \" + nluBluemix.getState());\n                }\n            } else {\n\n                partialArray.clear();\n                resultsArray.clear();\n                confidenceArray.clear();\n                bundle.clear();\n\n                final List<Result> results = nluBluemix.getResults();\n\n                if (UtilsList.notNaked(results)) {\n\n                    final int resultsSize = results.size();\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"results size: \" + resultsSize);\n                    }\n\n                    if (!detectFinal(results)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onMessage: have partial\");\n                        }\n\n                        for (int i = 0; i < resultsSize; i++) {\n\n                            final List<Alternative> alternatives = results.get(i).getAlternatives();\n\n                            final int size = alternatives.size();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"partial alternatives size: \" + size);\n                            }\n\n                            for (int j = 0; j < size; j++) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"final result: \" + alternatives.get(j).getTranscript()\n                                            + \" ~ \" + alternatives.get(j).getConfidence());\n                                }\n\n                                partialArray.add(alternatives.get(j).getTranscript().trim());\n                            }\n                        }\n\n                        bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, partialArray);\n                        listener.onPartialResults(bundle);\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onMessage: have final\");\n                        }\n\n                        haveFinal.set(true);\n\n                        if (doError.get()) {\n                            stopListening();\n                        }\n\n                        for (int i = 0; i < resultsSize; i++) {\n\n                            final List<Alternative> alternatives = results.get(i).getAlternatives();\n\n                            final int size = alternatives.size();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"final alternatives size: \" + size);\n                            }\n\n                            for (int j = 0; j < size; j++) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"final result: \" + alternatives.get(j).getTranscript()\n                                            + \" ~ \" + alternatives.get(j).getConfidence());\n                                }\n\n                                confidenceArray.add(alternatives.get(j).getConfidence());\n                                resultsArray.add(alternatives.get(j).getTranscript().trim());\n                            }\n                        }\n\n                        Recognition.setState(Recognition.State.IDLE);\n\n                        if (languageModel == SaiyDefaults.LanguageModel.IBM) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"final: nlu required\");\n                            }\n\n                            if (servingRemote) {\n                                bundle.putString(Request.RESULTS_NLU, message);\n                                bundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES,\n                                        ArrayUtils.toPrimitive(confidenceArray.toArray(new Float[0]), 0.0F));\n                                bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n                                listener.onResults(bundle);\n                            } else {\n                                new ResolveBluemix(mic.getContext(), sl, UtilsLocale.stringToLocale(vrLocale.toString()),\n                                        ttsLocale, ArrayUtils.toPrimitive(confidenceArray.toArray(new Float[0]), 0.0F),\n                                        resultsArray).unpack(nluBluemix);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"final: nlu not required\");\n                            }\n\n                            bundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES,\n                                    ArrayUtils.toPrimitive(confidenceArray.toArray(new Float[0]), 0.0F));\n                            bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n                            listener.onResults(bundle);\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onMessage: nluBluemix results naked\");\n                    }\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"onMessage: nluBluemix null\");\n            }\n        }\n    }\n\n    /**\n     * Loop through the {@link Result} list to detect a final parameter\n     *\n     * @param results the list of {@link Result}\n     * @return true if the final parameter is detected, false otherwise\n     */\n    private boolean detectFinal(@NonNull final List<Result> results) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"detectFinal\");\n        }\n\n        for (final Result result : results) {\n            if (result.isFinal()) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    @Override\n    public void onClose(final int code, final String reason, final boolean remote) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClose: \" + reason + \" ~ \" + remote);\n        }\n\n        // TODO\n        if (remote && (!doError.get() || isRecording.get())) {\n            onError(SpeechRecognizer.ERROR_NETWORK);\n        }\n    }\n\n    @Override\n    public void onError(final Exception e) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n            e.printStackTrace();\n        }\n        onError(SpeechRecognizer.ERROR_NETWORK);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/bluemix/mod/TrustAllBluemixWebSocketClient.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.bluemix.mod;\n\nimport android.annotation.SuppressLint;\nimport android.support.annotation.NonNull;\n\nimport org.java_websocket.client.DefaultSSLWebSocketClientFactory;\nimport org.java_websocket.client.WebSocketClient;\nimport org.java_websocket.drafts.Draft_17;\nimport org.java_websocket.exceptions.WebsocketNotConnectedException;\nimport org.java_websocket.handshake.ServerHandshake;\n\nimport java.net.URI;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.Map;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\n\nimport ai.saiy.android.recognition.provider.bluemix.IWebSocketCallback;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 03/08/2016.\n */\n\npublic class TrustAllBluemixWebSocketClient extends WebSocketClient {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = TrustAllBluemixWebSocketClient.class.getSimpleName();\n\n    private static final String SSL_NULL = \"ssl == null\";\n\n    private final IWebSocketCallback callback;\n\n    public TrustAllBluemixWebSocketClient(@NonNull final URI serverURL, @NonNull final Map<String, String> header,\n                                          final IWebSocketCallback callback) {\n        super(serverURL, new Draft_17(), header, 10000);\n        this.callback = callback;\n    }\n\n    public void start() throws NoSuchAlgorithmException, KeyManagementException, InterruptedException, CertificateException {\n\n        final SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n\n        sslContext.init(null, new TrustManager[]{new X509TrustManager() {\n            public X509Certificate[] getAcceptedIssuers() {\n                return new X509Certificate[]{};\n            }\n\n            @SuppressLint(\"TrustAllX509TrustManager\")\n            public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {\n            }\n\n            @SuppressLint(\"TrustAllX509TrustManager\")\n            public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {\n            }\n        }}, new java.security.SecureRandom());\n\n        this.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sslContext));\n\n        this.connectBlocking();\n    }\n\n    public void disconnect() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"disconnect\");\n        }\n\n        boolean shouldDisconnect = true;\n\n        switch (getReadyState()) {\n\n            case NOT_YET_CONNECTED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"READY_STATE: NOT_YET_CONNECTED\");\n                }\n                break;\n            case CONNECTING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"READY_STATE: CONNECTING\");\n                }\n                break;\n            case OPEN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"READY_STATE: OPEN\");\n                }\n                break;\n            case CLOSING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"READY_STATE: CLOSING\");\n                }\n                shouldDisconnect = false;\n                break;\n            case CLOSED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"READY_STATE: CLOSED\");\n                }\n                shouldDisconnect = false;\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"READY_STATE: default\");\n                }\n                break;\n        }\n\n        if (shouldDisconnect) {\n\n            try {\n                this.send(new byte[0]);\n            } catch (final WebsocketNotConnectedException e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"disconnect: byte0: WebSocketNotConnectedException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"disconnect: byte0: Exception\");\n                    e.printStackTrace();\n                }\n            }\n\n            try {\n                this.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"disconnect: close: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    @Override\n    public void close() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"close\");\n        }\n        super.close();\n    }\n\n    @Override\n    public void onOpen(final ServerHandshake handshakeData) {\n        callback.onOpen(handshakeData);\n    }\n\n    @Override\n    public void onMessage(final String message) {\n        callback.onMessage(message);\n    }\n\n    @Override\n    public void onClose(final int code, final String reason, final boolean remote) {\n        callback.onClose(code, reason, remote);\n    }\n\n    @Override\n    public void onError(final Exception e) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n            MyLog.w(CLS_NAME, \"onError: getMessage: \" + e.getMessage());\n            e.printStackTrace();\n        }\n\n        if (shouldError(e)) {\n            callback.onError(e);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onError: ignoring SSL message\");\n            }\n        }\n    }\n\n    private boolean shouldError(@NonNull final Exception e) {\n        return !(UtilsString.notNaked(e.getMessage()) && e.getMessage().matches(SSL_NULL));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/google/chromium/RecognitionGoogleChromium.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.google.chromium;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.os.Bundle;\nimport android.os.Process;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.text.TextUtils;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.json.JSONTokener;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.ArrayList;\nimport java.util.Scanner;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.api.language.vr.VRLanguageGoogle;\nimport ai.saiy.android.audio.SaiyRecorder;\nimport ai.saiy.android.audio.SaiySoundPool;\nimport ai.saiy.android.audio.pause.PauseDetector;\nimport ai.saiy.android.audio.pause.PauseListener;\nimport ai.saiy.android.configuration.GoogleConfiguration;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to use the unofficial Chrome Speech API. You'll need to register in the Chromium Google\n * group and enable the feature in your the Google API console. **BOTH ARE REQUIRED!!**\n * The API key should be entered in the {@link GoogleConfiguration} file.\n * <p>\n * Google's proposition here is just fantastic. If only the Android version of this in Google Now\n * offered such control. It's lightning quick and pretty damn accurate.\n * <p>\n * Created by benrandall76@gmail.com on 12/02/2016.\n * <p>\n * Adapted from posts originating from https://mikepultz.com/2013/07/google-speech-api-full-duplex-php-version\n */\npublic class RecognitionGoogleChromium implements PauseListener {\n\n    // TODO - The interface is messy as multiple errors of the same type can be thrown from each thread\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionGoogleChromium.class.getSimpleName();\n\n    private static final long MIN = 10000000;\n    private static final long MAX = 900000009999998L;\n\n    private static final String RESULT = \"result\";\n    private static final String TRANSCRIPT = \"transcript\";\n    private static final String CONFIDENCE = \"confidence\";\n    private static final String ALTERNATIVE = \"alternative\";\n    private static final String FINAL = \"final\";\n    private static final String RESULTS_THREAD = \"resultsThread\";\n    private static final String AUDIO_THREAD = \"audioThread\";\n    private static final String TRANSFER_ENCODING = \"Transfer-Encoding\";\n    private static final String CHUNKED = \"chunked\";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String CONTENT_TYPE_AUDIO_PARAMS = \"audio/l16; rate=8000\";\n\n    private static final int ERROR_HTTP = 1;\n    private static final int ERROR_STREAM = 2;\n    private static final int ERROR_AUDIO = 3;\n    private static final int ERROR_API = 4;\n    private static final int ERROR_TIMEOUT = 5;\n\n    private static final String GOOGLE_DUPLEX_SPEECH_BASE = \"https://www.google.com/speech-api/full-duplex/v1/\";\n    private static final String RESULTS_URL = GOOGLE_DUPLEX_SPEECH_BASE + \"down?maxresults=1&pair=\";\n    private static final String AUDIO_URL = GOOGLE_DUPLEX_SPEECH_BASE + \"up?lm=dictation&interim&client=chromium&key=\";\n\n    private final String LANGUAGE = \"&lang=\";\n    private final String PAIR = \"&pair=\";\n\n    private final Object audioLock = new Object();\n    private final Object errorLock = new Object();\n\n    private static final int nChannels = 1;\n\n    private final int audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;\n    private final int sampleRateInHz = 8000;\n    private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;\n    private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n\n    private volatile SaiyRecorder saiyRecorder;\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private volatile boolean thrown;\n    private volatile boolean haveResults;\n\n    private final SaiySoundPool ssp;\n\n    private PauseDetector pauseDetector;\n\n    private final SaiyRecognitionListener listener;\n    private final VRLanguageGoogle language;\n    private final String apiKey;\n    private final boolean pauseDetection;\n\n    /**\n     * Constructor\n     *\n     * @param listener       the associated {@link SaiyRecognitionListener}\n     * @param language       the Locale we are using to analyse the voice data. This is not necessarily the\n     *                       Locale of the device, as the user may be multi-lingual and have set a custom\n     *                       recognition language in a launcher short-cut.\n     * @param apiKey         the Chromium Google API key\n     * @param pauseDetection if pause detection is required\n     */\n    public RecognitionGoogleChromium(@NonNull final SaiyRecognitionListener listener,\n                                     @NonNull final VRLanguageGoogle language, @NonNull final String apiKey,\n                                     final boolean pauseDetection, @NonNull final SaiySoundPool ssp) {\n        this.listener = listener;\n        this.language = language;\n        this.apiKey = apiKey;\n        this.pauseDetection = pauseDetection;\n        this.ssp = ssp;\n\n        if (this.pauseDetection) {\n            pauseDetector = new PauseDetector(this, sampleRateInHz, nChannels,\n                    PauseDetector.DEFAULT_PAUSE_IGNORE_TIME);\n        }\n\n        saiyRecorder = new SaiyRecorder(audioSource, sampleRateInHz, channelConfig, audioFormat, true);\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            stopListening();\n        }\n    }\n\n    /**\n     * Stop the recognition.\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        isRecording.set(false);\n        Recognition.setState(Recognition.State.PROCESSING);\n    }\n\n    /**\n     * Start the recognition.\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called startRecording\");\n        }\n\n        isRecording.set(true);\n        thrown = false;\n\n        ssp.play(ssp.getBeepStart());\n\n        final long apiPair = MIN + (long) (Math.random() * ((MAX - MIN) + 1L));\n\n        final Thread resultsThread = new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                try {\n\n                    final URL url = new URL(RESULTS_URL + apiPair);\n\n                    final HttpsURLConnection httpConnResults = (HttpsURLConnection) url.openConnection();\n                    httpConnResults.setAllowUserInteraction(false);\n                    httpConnResults.setInstanceFollowRedirects(true);\n                    httpConnResults.setRequestMethod(Constants.HTTP_GET);\n                    httpConnResults.connect();\n                    final int responseCode = httpConnResults.getResponseCode();\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"resultsThread responseCode: \" + responseCode);\n                    }\n\n                    if (responseCode == HttpsURLConnection.HTTP_OK) {\n\n                        final InputStream inStream = httpConnResults.getInputStream();\n\n                        if (inStream != null) {\n\n                            final Scanner scanner = new Scanner(inStream);\n\n                            while (scanner.hasNextLine()) {\n                                parseResults(scanner.nextLine());\n                            }\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"resultsThread stream closed\");\n                            }\n\n                            closeResources(inStream, scanner, httpConnResults);\n\n                            if (isRecording.get()) {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"isRecording true\");\n                                }\n\n                                isRecording.set(false);\n\n                                audioShutdown(RESULTS_THREAD);\n                                handleError(ERROR_TIMEOUT);\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"isRecording false\");\n                                }\n\n                                if (!haveResults) {\n                                    listener.onError(SpeechRecognizer.ERROR_NO_MATCH);\n                                }\n                            }\n\n                            System.gc();\n\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"resultsThread inStream: null\");\n                            }\n\n                            audioShutdown(RESULTS_THREAD);\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"resultsThread ErrorStream: \"\n                                    + UtilsString.streamToString(httpConnResults.getErrorStream()));\n                        }\n\n                        audioShutdown(RESULTS_THREAD);\n\n                        switch (responseCode) {\n\n                            case 400:\n                                handleError(ERROR_STREAM);\n                                break;\n                            case 403:\n                                handleError(ERROR_API);\n                                break;\n                            default:\n                                handleError(ERROR_STREAM);\n                                break;\n\n                        }\n                    }\n\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"resultsThread MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                    audioShutdown(RESULTS_THREAD);\n                    handleError(ERROR_HTTP);\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"resultsThread IOException\");\n                        e.printStackTrace();\n                    }\n                    audioShutdown(RESULTS_THREAD);\n                    handleError(ERROR_HTTP);\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"resultsThread NullPointerException\");\n                        e.printStackTrace();\n                    }\n                    audioShutdown(RESULTS_THREAD);\n                    handleError(ERROR_HTTP);\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"resultsThread Exception\");\n                        e.printStackTrace();\n                    }\n                    audioShutdown(RESULTS_THREAD);\n                    handleError(ERROR_HTTP);\n                }\n            }\n        };\n\n        final Thread audioThread = new Thread() {\n\n            public void run() {\n                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);\n\n                if (pauseDetection) {\n                    pauseDetector.begin();\n                }\n\n                final int bufferSize = saiyRecorder.getBufferSize();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"audioThread: bufferSize: \" + bufferSize);\n                }\n\n                final byte[] buffer = new byte[bufferSize];\n\n                switch (saiyRecorder.initialise()) {\n\n                    case AudioRecord.STATE_INITIALIZED:\n\n                        try {\n\n                            final URL url = new URL(AUDIO_URL + apiKey + LANGUAGE + language + PAIR + apiPair);\n                            final URLConnection urlConn = url.openConnection();\n\n                            final HttpsURLConnection httpConnAudio = (HttpsURLConnection) urlConn;\n                            httpConnAudio.setAllowUserInteraction(false);\n                            httpConnAudio.setInstanceFollowRedirects(true);\n                            httpConnAudio.setRequestMethod(Constants.HTTP_POST);\n                            httpConnAudio.setDoOutput(true);\n                            httpConnAudio.setRequestProperty(TRANSFER_ENCODING, CHUNKED);\n                            httpConnAudio.setChunkedStreamingMode(0);\n                            httpConnAudio.setRequestProperty(CONTENT_TYPE, CONTENT_TYPE_AUDIO_PARAMS);\n\n                            httpConnAudio.connect();\n\n                            final OutputStream out = httpConnAudio.getOutputStream();\n\n                            switch (saiyRecorder.startRecording()) {\n\n                                case AudioRecord.RECORDSTATE_RECORDING: {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"audioThread: Check: AudioRecord.RECORDSTATE_RECORDING\");\n                                    }\n\n                                    int count = 0;\n                                    while (isRecording.get() && saiyRecorder != null\n                                            && saiyRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n\n                                        if (count == 0) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"Recording Started\");\n                                            }\n\n                                            Recognition.setState(Recognition.State.LISTENING);\n\n                                            listener.onReadyForSpeech(null);\n                                            resultsThread.start();\n                                            count++;\n                                        }\n\n                                        if (saiyRecorder != null) {\n                                            final int bufferReadResult = saiyRecorder.read(buffer);\n                                            listener.onBufferReceived(buffer);\n\n                                            if (pauseDetection && !pauseDetector.hasDetected()) {\n                                                pauseDetector.addLength(buffer, bufferReadResult);\n                                                pauseDetector.monitor();\n                                            }\n\n                                            for (int i = 0; i < bufferReadResult; i++) {\n                                                out.write(buffer[i]);\n                                            }\n                                        }\n                                    }\n\n                                    audioShutdown(RESULTS_THREAD);\n\n                                    if (out != null) {\n                                        try {\n                                            out.close();\n                                        } catch (final IOException e) {\n                                            if (DEBUG) {\n                                                MyLog.e(CLS_NAME, \"audioThread out.close()\");\n                                            }\n                                        }\n                                    }\n\n                                    final int responseCode = httpConnAudio.getResponseCode();\n\n                                    if (DEBUG) {\n                                        MyLog.d(CLS_NAME, \"audioThread responseCode: \" + responseCode);\n                                    }\n\n                                    if (responseCode != HttpsURLConnection.HTTP_OK) {\n                                        if (DEBUG) {\n                                            MyLog.e(CLS_NAME, \"audioThread ErrorStream: \"\n                                                    + UtilsString.streamToString(httpConnAudio.getErrorStream()));\n                                        }\n\n                                        audioShutdown(RESULTS_THREAD);\n                                        handleError(ERROR_STREAM);\n\n                                    }\n\n                                    try {\n                                        httpConnAudio.disconnect();\n                                    } catch (final NullPointerException e) {\n                                        if (DEBUG) {\n                                            MyLog.e(CLS_NAME, \"audioThread NullPointerException disconnect()\");\n                                            e.printStackTrace();\n                                        }\n                                    } catch (final Exception e) {\n                                        if (DEBUG) {\n                                            MyLog.e(CLS_NAME, \"audioThread Exception disconnect()\");\n                                            e.printStackTrace();\n                                        }\n                                    }\n                                }\n\n                                break;\n                                case AudioRecord.ERROR:\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"audioThread: != AudioRecord.RECORDSTATE_RECORDING\");\n                                    }\n                                    handleError(ERROR_AUDIO);\n                                    break;\n                            }\n\n                        } catch (final MalformedURLException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"audioThread MalformedURLException\");\n                                e.printStackTrace();\n                            }\n                            audioShutdown(RESULTS_THREAD);\n                            handleError(ERROR_HTTP);\n                        } catch (final IOException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"audioThread IOException\");\n                                e.printStackTrace();\n                            }\n                            audioShutdown(RESULTS_THREAD);\n                            handleError(ERROR_HTTP);\n                        } catch (final IllegalStateException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"audioThread IllegalStateException\");\n                                e.printStackTrace();\n                            }\n                            audioShutdown(RESULTS_THREAD);\n                            handleError(ERROR_AUDIO);\n                        } catch (final NullPointerException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"audioThread NullPointerException\");\n                                e.printStackTrace();\n                            }\n                            audioShutdown(RESULTS_THREAD);\n                            handleError(ERROR_HTTP);\n                        } catch (final Exception e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"audioThread Exception\");\n                                e.printStackTrace();\n                            }\n                            audioShutdown(RESULTS_THREAD);\n                            handleError(ERROR_HTTP);\n                        }\n\n                        audioShutdown(AUDIO_THREAD);\n\n                        break;\n\n                    case AudioRecord.STATE_UNINITIALIZED:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"AudioRecord.STATE_UNINITIALIZED\");\n                        }\n\n                        handleError(ERROR_AUDIO);\n                        break;\n                }\n            }\n        };\n\n        audioThread.start();\n    }\n\n    /**\n     * Parse the recognition results\n     */\n    private void parseResults(final String response) {\n\n        if (!TextUtils.isEmpty(response)) {\n\n            try {\n\n                final StringBuilder stringBuilder = new StringBuilder();\n\n                final JSONObject object = (JSONObject) new JSONTokener(response).nextValue();\n                final JSONArray resultArray = object.getJSONArray(RESULT);\n\n                final ArrayList<String> partialArray = new ArrayList<>();\n                final ArrayList<String> resultsArray = new ArrayList<>();\n                final Bundle resultsBundle = new Bundle();\n                final Bundle partialBundle = new Bundle();\n\n                boolean haveFinal;\n\n                for (int i = 0; i < resultArray.length(); i++) {\n\n                    final JSONObject objectHeader = resultArray.getJSONObject(i);\n\n                    final JSONArray alternativeArray = objectHeader.getJSONArray(ALTERNATIVE);\n\n                    haveFinal = objectHeader.has(FINAL);\n\n                    if (haveFinal) {\n\n                        final float[] floatsArray = new float[alternativeArray.length()];\n\n                        for (int j = 0; j < alternativeArray.length(); j++) {\n                            final JSONObject transcriptObject = alternativeArray.getJSONObject(j);\n                            resultsArray.add(transcriptObject.getString(TRANSCRIPT));\n\n                            if (transcriptObject.has(CONFIDENCE)) {\n                                floatsArray[j] = (float) transcriptObject.getDouble(CONFIDENCE);\n                            } else {\n                                floatsArray[j] = 0.5f;\n                            }\n                        }\n\n                        if (i == (resultArray.length() - 1)) {\n                            resultsBundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n                            resultsBundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, floatsArray);\n                            listener.onResults(resultsBundle);\n                            haveResults = true;\n                            break;\n                        }\n\n                    } else {\n\n                        for (int j = 0; j < alternativeArray.length(); j++) {\n                            final JSONObject transcriptObject = alternativeArray.getJSONObject(j);\n                            stringBuilder.append(transcriptObject.getString(TRANSCRIPT));\n                        }\n\n                        if (i == (resultArray.length() - 1)) {\n                            partialArray.add(stringBuilder.toString());\n                            partialBundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, partialArray);\n                            listener.onPartialResults(partialBundle);\n                            haveResults = true;\n                        }\n                    }\n                }\n\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"parseResults JSON NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final JSONException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"parseResults JSONException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"parseResults JSON Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Handle errors - TODO - fugly\n     */\n    private void handleError(final int errorCode) {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"in handleError\");\n        }\n\n        synchronized (errorLock) {\n\n            if (!thrown) {\n\n                Recognition.setState(Recognition.State.IDLE);\n                thrown = true;\n\n                switch (errorCode) {\n\n                    case ERROR_HTTP:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: throwing: ERROR_HTTP\");\n                        }\n                        listener.onError(errorCode);\n                        break;\n                    case ERROR_STREAM:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: throwing: ERROR_STREAM\");\n                        }\n                        listener.onError(errorCode);\n                        break;\n                    case ERROR_AUDIO:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: throwing: ERROR_AUDIO\");\n                        }\n                        listener.onError(errorCode);\n                        break;\n                    case ERROR_API:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: throwing: ERROR_API\");\n                        }\n                        listener.onError(errorCode);\n                        break;\n                    case ERROR_TIMEOUT:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: throwing: ERROR_TIMEOUT\");\n                        }\n                        listener.onError(errorCode);\n                        break;\n                    default:\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"handleError: default throwing: ERROR_HTTP\");\n                        }\n                        listener.onError(ERROR_HTTP);\n                        break;\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"handleError: thrown already: \" + thrown);\n                }\n            }\n        }\n    }\n\n    /**\n     * Shutdown the microphone and release the resources\n     */\n    private void audioShutdown(final String from) {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"in audioShutdown: \" + from);\n        }\n\n        synchronized (audioLock) {\n\n            if (saiyRecorder != null) {\n                ssp.play(ssp.getBeepStop());\n                Recognition.setState(Recognition.State.IDLE);\n                listener.onEndOfSpeech();\n                saiyRecorder.shutdown(from);\n\n                if (DEBUG) {\n                    MyLog.d(CLS_NAME, \"audioShutdown: audioSession set to NULL ~ \" + from);\n                }\n\n                saiyRecorder = null;\n\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"audioShutdown: NULL ~ \" + from);\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"audioShutdown: finished synchronisation ~ \" + from);\n            }\n        }\n    }\n\n    /**\n     * Attempt to shutdown any resources. Only here to avoid clutter above.\n     *\n     * @param inStream        the InputStream\n     * @param scanner         the Scanner\n     * @param httpConnResults the HttpsURLConnection\n     */\n    private void closeResources(final InputStream inStream, final Scanner scanner,\n                                final HttpsURLConnection httpConnResults) {\n\n        if (inStream != null) {\n            try {\n                inStream.close();\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"resultsThread inStream.close()\");\n                }\n            }\n        }\n\n        if (scanner != null) {\n            try {\n                scanner.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"resultsThread inStream.close()\");\n                }\n            }\n        }\n\n        if (httpConnResults != null) {\n            try {\n                httpConnResults.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"resultsThread httpConnResults.disconnect()\");\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/google/cloud/GoogleCredentialsInterceptor.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.google.cloud;\n\n/**\n * Created by benrandall76@gmail.com on 27/09/2016.\n */\n\nimport com.google.auth.Credentials;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.grpc.CallOptions;\nimport io.grpc.Channel;\nimport io.grpc.ClientCall;\nimport io.grpc.ClientInterceptor;\nimport io.grpc.ClientInterceptors;\nimport io.grpc.Metadata;\nimport io.grpc.MethodDescriptor;\nimport io.grpc.Status;\nimport io.grpc.StatusException;\n\n/**\n * Authenticates the gRPC channel.\n */\npublic class GoogleCredentialsInterceptor implements ClientInterceptor {\n\n    private final Credentials mCredentials;\n\n    private Metadata mCached;\n\n    private Map<String, List<String>> mLastMetadata;\n\n    public GoogleCredentialsInterceptor(Credentials credentials) {\n        mCredentials = credentials;\n    }\n\n    @Override\n    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(final MethodDescriptor<ReqT, RespT> method,\n                                                               CallOptions callOptions, final Channel next) {\n        return new ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(\n                next.newCall(method, callOptions)) {\n            @Override\n            protected void checkedStart(Listener<RespT> responseListener, Metadata headers)\n                    throws StatusException {\n\n                Metadata cachedSaved;\n                URI uri = serviceUri(next, method);\n                synchronized (GoogleCredentialsInterceptor.this) {\n                    Map<String, List<String>> latestMetadata = getRequestMetadata(uri);\n                    if (mLastMetadata == null || mLastMetadata != latestMetadata) {\n                        mLastMetadata = latestMetadata;\n                        mCached = toHeaders(mLastMetadata);\n                    }\n                    cachedSaved = mCached;\n                }\n                headers.merge(cachedSaved);\n                delegate().start(responseListener, headers);\n            }\n        };\n    }\n\n    /**\n     * Generate a JWT-specific service URI. The URI is simply an identifier with enough\n     * information for a service to know that the JWT was intended for it. The URI will\n     * commonly be verified with a simple string equality check.\n     */\n    private URI serviceUri(Channel channel, MethodDescriptor<?, ?> method)\n            throws StatusException {\n\n        String authority = channel.authority();\n        if (authority == null) {\n            throw Status.UNAUTHENTICATED\n                    .withDescription(\"Channel has no authority\")\n                    .asException();\n        }\n        // Always use HTTPS, by definition.\n        final String scheme = \"https\";\n        final int defaultPort = 443;\n        String path = \"/\" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());\n        URI uri;\n        try {\n            uri = new URI(scheme, authority, path, null, null);\n        } catch (URISyntaxException e) {\n            throw Status.UNAUTHENTICATED\n                    .withDescription(\"Unable to construct service URI for auth\")\n                    .withCause(e).asException();\n        }\n        // The default port must not be present. Alternative ports should be present.\n        if (uri.getPort() == defaultPort) {\n            uri = removePort(uri);\n        }\n        return uri;\n    }\n\n    private URI removePort(URI uri) throws StatusException {\n        try {\n            return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,\n                    uri.getPath(), uri.getQuery(), uri.getFragment());\n        } catch (URISyntaxException e) {\n            throw Status.UNAUTHENTICATED\n                    .withDescription(\"Unable to construct service URI after removing port\")\n                    .withCause(e).asException();\n        }\n    }\n\n    private Map<String, List<String>> getRequestMetadata(URI uri) throws StatusException {\n        try {\n            return mCredentials.getRequestMetadata(uri);\n        } catch (IOException e) {\n            throw Status.UNAUTHENTICATED.withCause(e).asException();\n        }\n    }\n\n    private static Metadata toHeaders(Map<String, List<String>> metadata) {\n        Metadata headers = new Metadata();\n        if (metadata != null) {\n            for (String key : metadata.keySet()) {\n                Metadata.Key<String> headerKey = Metadata.Key.of(\n                        key, Metadata.ASCII_STRING_MARSHALLER);\n                for (String value : metadata.get(key)) {\n                    headers.put(headerKey, value);\n                }\n            }\n        }\n        return headers;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/google/cloud/RecognitionGoogleCloud.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.google.cloud;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.os.Process;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport com.google.android.gms.common.GoogleApiAvailability;\nimport com.google.android.gms.common.GooglePlayServicesNotAvailableException;\nimport com.google.android.gms.common.GooglePlayServicesRepairableException;\nimport com.google.android.gms.security.ProviderInstaller;\nimport com.google.auth.oauth2.AccessToken;\nimport com.google.auth.oauth2.GoogleCredentials;\nimport com.google.cloud.speech.v1beta1.RecognitionConfig;\nimport com.google.cloud.speech.v1beta1.SpeechGrpc;\nimport com.google.cloud.speech.v1beta1.SpeechRecognitionAlternative;\nimport com.google.cloud.speech.v1beta1.StreamingRecognitionConfig;\nimport com.google.cloud.speech.v1beta1.StreamingRecognitionResult;\nimport com.google.cloud.speech.v1beta1.StreamingRecognizeRequest;\nimport com.google.cloud.speech.v1beta1.StreamingRecognizeResponse;\nimport com.google.protobuf.ByteString;\nimport com.google.protobuf.TextFormat;\n\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport ai.saiy.android.api.language.vr.VRLanguageGoogle;\nimport ai.saiy.android.audio.IMic;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport io.grpc.ManagedChannel;\nimport io.grpc.Status;\nimport io.grpc.StatusException;\nimport io.grpc.StatusRuntimeException;\nimport io.grpc.internal.DnsNameResolverProvider;\nimport io.grpc.okhttp.OkHttpChannelProvider;\nimport io.grpc.stub.StreamObserver;\n\n/**\n * Created by benrandall76@gmail.com on 21/09/2016.\n */\n\npublic class RecognitionGoogleCloud implements IMic, StreamObserver<StreamingRecognizeResponse> {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = RecognitionGoogleCloud.class.getSimpleName();\n\n    private static final int UNRECOVERABLE = -99;\n\n    private static final List<String> OAUTH2_SCOPES = Collections.singletonList(\n            \"https://www.googleapis.com/auth/cloud-platform\");\n    private static final String HOSTNAME = \"speech.googleapis.com\";\n    private static final int PORT = 443;\n\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private final AtomicBoolean doBeginning = new AtomicBoolean(true);\n    private final AtomicBoolean doError = new AtomicBoolean(true);\n    private final AtomicBoolean doEnd = new AtomicBoolean(true);\n    private final AtomicBoolean doResults = new AtomicBoolean(true);\n    private final AtomicBoolean doRecordingEnded = new AtomicBoolean(true);\n\n    private final ArrayList<String> partialArray = new ArrayList<>();\n    private final ArrayList<String> resultsArray = new ArrayList<>();\n    private final ArrayList<Float> confidenceArray = new ArrayList<>();\n    private final Bundle bundle = new Bundle();\n\n    private final SaiyRecognitionListener listener;\n    private final RecognitionMic mic;\n    private final Context mContext;\n\n    private volatile StreamObserver<StreamingRecognizeRequest> requestObserver;\n    private volatile StreamingRecognizeRequest initial;\n    private volatile SpeechGrpc.SpeechStub mApi;\n\n    /**\n     * Constructor\n     *\n     * @param mContext    the application context\n     * @param listener    the associated {@link SaiyRecognitionListener}\n     * @param language    the Locale we are using to analyse the voice data. This is not necessarily the\n     *                    Locale of the device, as the user may be multi-lingual and have set a custom\n     *                    recognition language in a launcher short-cut.\n     * @param accessToken the Chromium Google API key\n     */\n    public RecognitionGoogleCloud(@NonNull final Context mContext, @NonNull final SaiyRecognitionListener listener,\n                                  @NonNull final VRLanguageGoogle language, @NonNull final AccessToken accessToken,\n                                  @NonNull final RecognitionMic mic) {\n        this.listener = listener;\n        this.mic = mic;\n        this.mContext = mContext;\n\n        this.mic.setMicListener(this);\n\n        try {\n\n            ProviderInstaller.installIfNeeded(mContext);\n\n            final GoogleCredentials googleCredentials = new GoogleCredentials(accessToken) {\n                @Override\n                public AccessToken refreshAccessToken() throws IOException {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"refreshAccessToken\");\n                    }\n                    return accessToken;\n                }\n            }.createScoped(OAUTH2_SCOPES);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"language: \" + language);\n            }\n\n            final ManagedChannel channel = new OkHttpChannelProvider()\n                    .builderForAddress(HOSTNAME, PORT)\n                    .nameResolverFactory(new DnsNameResolverProvider())\n                    .intercept(new GoogleCredentialsInterceptor(googleCredentials.createScoped(OAUTH2_SCOPES)))\n                    .enableKeepAlive(false)\n                    .build();\n\n            final long then = System.nanoTime();\n\n            mApi = SpeechGrpc.newStub(channel);\n\n            if (DEBUG) {\n                MyLog.getElapsed(CLS_NAME + \" End of requestObserver\", then);\n            }\n\n            requestObserver = mApi.streamingRecognize(this);\n\n            initial = StreamingRecognizeRequest.newBuilder().setStreamingConfig(\n                    StreamingRecognitionConfig.newBuilder()\n                            .setConfig(RecognitionConfig.newBuilder()\n                                    .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)\n                                    .setSampleRate(RecognitionMic.SAMPLE_RATE_HZ_16000)\n                                    .setMaxAlternatives(10)\n                                    .setProfanityFilter(false)\n                                    .setLanguageCode(language.getLocaleString())\n                                    .build())\n                            .setInterimResults(true)\n                            .setSingleUtterance(true)\n                            .build()).build();\n\n        } catch (final GooglePlayServicesRepairableException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Constructor GooglePlayServicesRepairableException\");\n                e.printStackTrace();\n            }\n            showPlayServicesError(e.getConnectionStatusCode());\n        } catch (final GooglePlayServicesNotAvailableException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Constructor GooglePlayServicesNotAvailableException\");\n                e.printStackTrace();\n            }\n            showPlayServicesError(UNRECOVERABLE);\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Constructor Exception\");\n                e.printStackTrace();\n            }\n            onError(SpeechRecognizer.ERROR_CLIENT);\n        }\n    }\n\n    private void showPlayServicesError(final int errorCode) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showPlayServicesError\");\n        }\n\n        onError(SpeechRecognizer.ERROR_CLIENT);\n\n        switch (errorCode) {\n\n            case UNRECOVERABLE:\n                // TODO\n                break;\n            default:\n                final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();\n                apiAvailability.showErrorNotification(mContext, errorCode);\n                break;\n        }\n    }\n\n\n    /**\n     * Start the recognition.\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startListening\");\n        }\n\n        if (doError.get()) {\n            if (mic.isAvailable()) {\n                isRecording.set(true);\n                mic.startRecording();\n\n                requestObserver.onNext(initial);\n\n                new Thread() {\n                    public void run() {\n                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                        try {\n\n                            synchronized (mic.getLock()) {\n                                while (mic.isRecording()) {\n                                    try {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"mic lock waiting\");\n                                        }\n                                        mic.getLock().wait();\n                                    } catch (final InterruptedException e) {\n                                        if (DEBUG) {\n                                            MyLog.e(CLS_NAME, \"InterruptedException\");\n                                            e.printStackTrace();\n                                        }\n                                        break;\n                                    }\n                                }\n                            }\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"record lock released: interrupted: \" + mic.isInterrupted());\n                            }\n\n                        } catch (final IllegalStateException e) {\n                            if (DEBUG) {\n                                MyLog.e(CLS_NAME, \"IllegalStateException\");\n                                e.printStackTrace();\n                            }\n                            onError(SpeechRecognizer.ERROR_NETWORK);\n                        } catch (final NullPointerException e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"NullPointerException\");\n                                e.printStackTrace();\n                            }\n                            onError(SpeechRecognizer.ERROR_NETWORK);\n                        } catch (final Exception e) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"Exception\");\n                                e.printStackTrace();\n                            }\n                            onError(SpeechRecognizer.ERROR_NETWORK);\n                        } finally {\n                            stopListening();\n                        }\n                    }\n                }.start();\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"startListening mic unavailable\");\n                }\n\n                onError(Speaker.ERROR_AUDIO);\n                mic.forceAudioShutdown();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"startListening error already thrown\");\n            }\n\n            mic.forceAudioShutdown();\n        }\n    }\n\n\n    @Override\n    public void onBufferReceived(final int bufferReadResult, final byte[] buffer) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBufferReceived\");\n        }\n\n        if (isRecording.get()) {\n\n            try {\n\n                requestObserver.onNext(StreamingRecognizeRequest.newBuilder()\n                        .setAudioContent(ByteString.copyFrom(buffer, 0, bufferReadResult))\n                        .build());\n\n            } catch (final IllegalStateException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onBufferReceived IllegalStateException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onBufferReceived Exception\");\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onBufferReceived: recording finished\");\n            }\n        }\n    }\n\n    @Override\n    public void onError(final int error) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        if (doError.get()) {\n            doError.set(false);\n            stopListening();\n\n            Recognition.setState(Recognition.State.IDLE);\n\n            if (mic.isInterrupted()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onError: isInterrupted\");\n                }\n\n                listener.onError(error);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onError: listener onComplete\");\n                }\n\n                listener.onComplete();\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onError: doError false\");\n            }\n        }\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            stopListening();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onPauseDetected: isRecording false\");\n            }\n        }\n    }\n\n    @Override\n    public void onRecordingStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingStarted\");\n        }\n        listener.onReadyForSpeech(null);\n    }\n\n    @Override\n    public void onRecordingEnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRecordingEnded\");\n        }\n\n        if (doRecordingEnded.get()) {\n            doRecordingEnded.set(false);\n            listener.onEndOfSpeech();\n            listener.onComplete();\n            Recognition.setState(Recognition.State.IDLE);\n        }\n    }\n\n    @Override\n    public void onFileWriteComplete(final boolean success) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFileWriteComplete: \" + success);\n        }\n    }\n\n    /**\n     * Stop the recognition.\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        if (isRecording.get()) {\n            isRecording.set(false);\n\n            new Thread() {\n                public void run() {\n                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);\n\n                    mic.stopRecording();\n                    requestObserver.onCompleted();\n                    Recognition.setState(Recognition.State.IDLE);\n\n                    final ManagedChannel channel = (ManagedChannel) mApi.getChannel();\n                    if (channel != null && !channel.isShutdown()) {\n                        try {\n                            channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);\n                        } catch (final InterruptedException e) {\n                            MyLog.e(CLS_NAME, \"Error shutting down the gRPC channel.\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n            }.start();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"called stopRecording: isRecording false\");\n            }\n        }\n    }\n\n\n    /**\n     * Receives a value from the stream.\n     * <p>\n     * <p>Can be called many times but is never called after {@link #onError(Throwable)} or {@link\n     * #onCompleted()} are called.\n     * <p>\n     * <p>Unary calls must invoke onNext at most once.  Clients may invoke onNext at most once for\n     * server streaming calls, but may receive many onNext callbacks.  Servers may invoke onNext at\n     * most once for client streaming calls, but may receive many onNext callbacks.\n     * <p>\n     * <p>If an exception is thrown by an implementation the caller is expected to terminate the\n     * stream by calling {@link #onError(Throwable)} with the caught exception prior to\n     * propagating it.\n     *\n     * @param value the value passed to the stream\n     */\n    @Override\n    public void onNext(final StreamingRecognizeResponse value) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onNext: \" + TextFormat.printToString(value));\n        }\n\n        final StreamingRecognizeResponse.EndpointerType endpointerType = value.getEndpointerType();\n\n        switch (endpointerType) {\n\n            case START_OF_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: START_OF_SPEECH\");\n                }\n                if (doBeginning.get()) {\n                    doBeginning.set(false);\n                    listener.onBeginningOfSpeech();\n                }\n                break;\n            case END_OF_SPEECH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: END_OF_SPEECH\");\n                }\n                if (doEnd.get()) {\n                    doEnd.set(false);\n                    stopListening();\n                }\n                break;\n            case END_OF_AUDIO:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: END_OF_AUDIO\");\n                }\n                if (doEnd.get()) {\n                    doEnd.set(false);\n                    stopListening();\n                }\n                break;\n            case END_OF_UTTERANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: END_OF_UTTERANCE\");\n                }\n                if (doEnd.get()) {\n                    doEnd.set(false);\n                    stopListening();\n                }\n                break;\n            case UNRECOGNIZED:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: UNRECOGNIZED\");\n                }\n                break;\n            case ENDPOINTER_EVENT_UNSPECIFIED:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: ENDPOINTER_EVENT_UNSPECIFIED\");\n                }\n                break;\n        }\n\n        if (doResults.get()) {\n\n            if (UtilsList.notNaked(value.getResultsList())) {\n\n                partialArray.clear();\n                resultsArray.clear();\n                confidenceArray.clear();\n                bundle.clear();\n\n                boolean isFinal = false;\n                for (final StreamingRecognitionResult recognitionResult : value.getResultsList()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"recognitionResult stability: \" + recognitionResult.getStability());\n                    }\n\n                    isFinal = recognitionResult.getIsFinal();\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"isFinal: \" + isFinal);\n                    }\n\n                    for (final SpeechRecognitionAlternative alternative : recognitionResult.getAlternativesList()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"alternative: \" + alternative.getTranscript());\n                        }\n\n                        if (isFinal) {\n                            resultsArray.add(alternative.getTranscript());\n                            confidenceArray.add(alternative.getConfidence());\n                        } else {\n\n                            if (partialArray.isEmpty()) {\n                                partialArray.add(alternative.getTranscript());\n                            } else {\n                                partialArray.add(partialArray.get(0) + \" \" + alternative.getTranscript());\n                            }\n                        }\n                    }\n                }\n\n                doResults.set(!isFinal);\n\n                if (isFinal) {\n                    bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n                    bundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES,\n                            ArrayUtils.toPrimitive(confidenceArray.toArray(new Float[0]), 0.0F));\n                    listener.onResults(bundle);\n                    stopListening();\n                } else {\n                    bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, partialArray);\n                    listener.onPartialResults(bundle);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNext: results list naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onNext: doResults false\");\n            }\n        }\n    }\n\n    /**\n     * Receives a terminating error from the stream.\n     * <p>\n     * <p>May only be called once and if called it must be the last method called. In particular if an\n     * exception is thrown by an implementation of {@code onError} no further calls to any method are\n     * allowed.\n     * <p>\n     * <p>{@code t} should be a {@link StatusException} or {@link\n     * StatusRuntimeException}, but other {@code Throwable} types are possible. Callers should\n     * generally convert from a {@link Status} via {@link Status#asException()} or\n     * {@link Status#asRuntimeException()}. Implementations should generally convert to a\n     * {@code Status} via {@link Status#fromThrowable(Throwable)}.\n     *\n     * @param throwable the error occurred on the stream\n     */\n    @Override\n    public void onError(final Throwable throwable) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n            throwable.printStackTrace();\n            final Status status = Status.fromThrowable(throwable);\n            MyLog.w(CLS_NAME, \"onError: \" + status.toString());\n        }\n\n        if (doError.get()) {\n            doError.set(false);\n            stopListening();\n            listener.onError(SpeechRecognizer.ERROR_NETWORK);\n        }\n    }\n\n    /**\n     * Receives a notification of successful stream completion.\n     * <p>\n     * <p>May only be called once and if called it must be the last method called. In particular if an\n     * exception is thrown by an implementation of {@code onCompleted} no further calls to any method\n     * are allowed.\n     */\n    @Override\n    public void onCompleted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCompleted\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/microsoft/RecognitionMicrosoft.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.microsoft;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.microsoft.bing.speech.SpeechClientStatus;\nimport com.microsoft.projectoxford.speechrecognition.Confidence;\nimport com.microsoft.projectoxford.speechrecognition.ISpeechRecognitionServerEvents;\nimport com.microsoft.projectoxford.speechrecognition.MicrophoneRecognitionClient;\nimport com.microsoft.projectoxford.speechrecognition.RecognitionResult;\nimport com.microsoft.projectoxford.speechrecognition.SpeechRecognitionMode;\nimport com.microsoft.projectoxford.speechrecognition.SpeechRecognitionServiceFactory;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.language.nlu.NLULanguageMicrosoft;\nimport ai.saiy.android.api.language.vr.VRLanguageMicrosoft;\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.audio.SaiySoundPool;\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.microsoft.ResolveMicrosoft;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsLocale;\n\n\n/**\n * Class uses the Project Oxford speech SDK. At the time of writing, the initialisation is too slow\n * to be usable in production.\n * <p/>\n * Register at <a href=\"https://www.microsoft.com/cognitive-services/en-us/sign-up\">Microsoft Cognitive Services</a>\n * to get 2 API keys and enter them in {@link MicrosoftConfiguration}\n * <p/>\n * Created by benrandall76@gmail.com on 18/04/2016.\n */\npublic class RecognitionMicrosoft implements ISpeechRecognitionServerEvents {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionMicrosoft.class.getSimpleName();\n\n    private final Bundle partialBundle = new Bundle();\n    private final Bundle resultsBundle = new Bundle();\n    private final ArrayList<String> partialArray = new ArrayList<>();\n\n    private final SaiySoundPool ssp;\n\n    private final Context mContext;\n    private final MicrophoneRecognitionClient client;\n    private final SaiyRecognitionListener listener;\n    private final SaiyDefaults.LanguageModel languageModel;\n    private final Locale ttsLocale;\n    private final VRLanguageMicrosoft vrLocale;\n    private final SupportedLanguage sl;\n    private final boolean servingRemote;\n\n    private boolean error;\n\n    private final ArrayList<String> resultsArray = new ArrayList<>();\n    private float[] floatsArray = null;\n\n\n    /**\n     * Constructor\n     *\n     * @param mContext             the application context\n     * @param listener             the associated {@link SaiyRecognitionListener}\n     * @param apiKey1              the Project Oxford key 1\n     * @param apiKey2              the Project Oxford key 2\n     * @param appId                the LUIS app id\n     * @param subscriptionId       the LUIS subscription id\n     * @param languageModel        the {@link SaiyDefaults.LanguageModel}\n     * @param ttsLocale            the Text to Speech {@link Locale}\n     * @param vrLocale             the {@link VRLanguageMicrosoft}\n     * @param nluLanguageMicrosoft the {@link NLULanguageMicrosoft}\n     * @param sl                   the {@link SupportedLanguage}\n     * @param servingRemote        true if the origin is a remote request\n     */\n    public RecognitionMicrosoft(@NonNull final Context mContext, @NonNull final SaiyRecognitionListener listener,\n                                @NonNull final String apiKey1,\n                                @NonNull final String apiKey2, @NonNull final String appId,\n                                @NonNull final String subscriptionId,\n                                @NonNull final SaiyDefaults.LanguageModel languageModel,\n                                @NonNull final Locale ttsLocale, @NonNull final VRLanguageMicrosoft vrLocale,\n                                @Nullable final NLULanguageMicrosoft nluLanguageMicrosoft,\n                                @NonNull final SupportedLanguage sl,\n                                final boolean servingRemote, final SaiySoundPool ssp) {\n        this.mContext = mContext;\n        this.listener = listener;\n        this.languageModel = languageModel;\n        this.ttsLocale = ttsLocale;\n        this.vrLocale = vrLocale;\n        this.sl = sl;\n        this.servingRemote = servingRemote;\n        this.ssp = ssp;\n\n        error = false;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"language: \" + vrLocale.getLocaleString());\n        }\n\n        if (languageModel != SaiyDefaults.LanguageModel.MICROSOFT) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu not required\");\n            }\n\n            client = SpeechRecognitionServiceFactory.createMicrophoneClient(SpeechRecognitionMode.ShortPhrase,\n                    vrLocale.getLocaleString(), this, apiKey1, apiKey2);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu required\");\n            }\n\n            //noinspection ConstantConditions\n            client = SpeechRecognitionServiceFactory.createMicrophoneClientWithIntent(\n                    nluLanguageMicrosoft.getLocaleString(), this, apiKey1, apiKey2, appId, subscriptionId);\n\n        }\n    }\n\n    /**\n     * Start the recognition.\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called startRecording\");\n        }\n        client.startMicAndRecognition();\n    }\n\n    /**\n     * Stop the recognition.\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        Recognition.setState(Recognition.State.PROCESSING);\n\n        if (client != null) {\n            client.endMicAndRecognition();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"called stopRecording: client null or cancelled\");\n            }\n        }\n    }\n\n    @Override\n    public void onPartialResponseReceived(final String partial) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPartialResponseReceived: \" + partial);\n        }\n\n        partialArray.clear();\n        partialBundle.clear();\n\n        partialArray.add(partial);\n        partialBundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, partialArray);\n        listener.onPartialResults(partialBundle);\n    }\n\n    @Override\n    public void onFinalResponseReceived(final RecognitionResult response) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFinalResponseReceived: \" + response.RecognitionStatus.name());\n        }\n\n        switch (response.RecognitionStatus) {\n\n            case RecognitionSuccess:\n                doSuccess(response);\n                break;\n            case NoMatch:\n            case InitialSilenceTimeout:\n                error = true;\n                listener.onError(SpeechRecognizer.ERROR_NO_MATCH);\n                break;\n            case BabbleTimeout:\n            case Intermediate:\n            case HotWordMaximumTime:\n            case Cancelled:\n            case RecognitionError:\n            case DictationEndSilenceTimeout:\n            case EndOfDictation:\n            case None:\n                error = true;\n                listener.onError(SpeechRecognizer.ERROR_CLIENT);\n                break;\n        }\n\n        if (client != null) {\n            client.endMicAndRecognition();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onFinalResponseReceived: client null\");\n            }\n        }\n    }\n\n    /**\n     * Process the {@link RecognitionResult}\n     *\n     * @param response the {@link RecognitionResult}\n     */\n    private void doSuccess(final RecognitionResult response) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"doSuccess\");\n        }\n\n        resultsArray.clear();\n        resultsBundle.clear();\n\n        floatsArray = new float[response.Results.length];\n\n        for (int i = 0; i < response.Results.length; i++) {\n            floatsArray[i] = convertConfidence(response.Results[i].Confidence);\n\n            if (response.Results[i].DisplayText.endsWith(\".\")) {\n                resultsArray.add(response.Results[i].DisplayText.substring(0,\n                        response.Results[i].DisplayText.length() - 1));\n            } else {\n                resultsArray.add(response.Results[i].DisplayText);\n            }\n        }\n\n        resultsBundle.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, floatsArray);\n        resultsBundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n\n        if (languageModel != SaiyDefaults.LanguageModel.MICROSOFT) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu not required: triggering onResults\");\n            }\n            listener.onResults(resultsBundle);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu required: holding results\");\n            }\n        }\n\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    @Override\n    public void onIntentReceived(final String payload) {\n        if (DEBUG) {\n            try {\n                MyLog.i(CLS_NAME, \"onIntentReceived\");\n                MyLog.i(CLS_NAME, \"onIntentReceived: \" + new JSONObject(payload).toString(4));\n            } catch (JSONException e) {\n                e.printStackTrace();\n            }\n        }\n\n        if (languageModel == SaiyDefaults.LanguageModel.MICROSOFT) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onIntentReceived: nlu required\");\n            }\n\n            if (servingRemote) {\n                resultsBundle.putString(Request.RESULTS_NLU, payload);\n                listener.onResults(resultsBundle);\n            } else {\n                new ResolveMicrosoft(mContext, sl, UtilsLocale.stringToLocale(vrLocale.getLocaleString()),\n                        ttsLocale, floatsArray, resultsArray).unpack(payload);\n            }\n        }\n    }\n\n    @Override\n    public void onError(final int errorCode, final String errorString) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError: \" + errorString);\n            MyLog.w(CLS_NAME, \"onError: \" + SpeechClientStatus.fromInt(errorCode) + \" \" + errorCode);\n        }\n        error = true;\n        listener.onError(errorCode);\n    }\n\n    @Override\n    public void onAudioEvent(final boolean recording) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onAudioEvent: recording: \" + recording);\n        }\n\n        if (recording) {\n            listener.onReadyForSpeech(null);\n            listener.onBeginningOfSpeech();\n            ssp.play(ssp.getBeepStart());\n        } else {\n            ssp.play(ssp.getBeepStop());\n            client.endMicAndRecognition();\n\n            if (!error) {\n                listener.onEndOfSpeech();\n            }\n        }\n    }\n\n    /**\n     * Convert the {@link Confidence} enum into an equivalent float value\n     *\n     * @param confidence the {@link Confidence}\n     * @return the equivalent float value\n     */\n    private float convertConfidence(@NonNull final Confidence confidence) {\n\n        switch (confidence) {\n            case None:\n                return 0.1f;\n            case Low:\n                return 0.3f;\n            case Normal:\n                return 0.6f;\n            case High:\n                return 0.9f;\n            default:\n                return 0.1f;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/nuance/RecognitionNuance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.nuance;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.MainThread;\nimport android.support.annotation.NonNull;\n\nimport com.nuance.speechkit.Audio;\nimport com.nuance.speechkit.DetectionType;\nimport com.nuance.speechkit.Interpretation;\nimport com.nuance.speechkit.Language;\nimport com.nuance.speechkit.RecognitionType;\nimport com.nuance.speechkit.RecognizedPhrase;\nimport com.nuance.speechkit.Session;\nimport com.nuance.speechkit.Transaction;\nimport com.nuance.speechkit.TransactionException;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.language.vr.VRLanguageNuance;\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.command.cancel.Cancel;\nimport ai.saiy.android.configuration.NuanceConfiguration;\nimport ai.saiy.android.localisation.SaiyResources;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.nuance.ResolveNuance;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsLocale;\n\n/**\n * This class uses the SpeechKit SDK from Nuance to analyse the voice recognition.\n * <p/>\n * Unfortunately, it's a pretty poor effort and is slow and lacking in essential features such as\n * partial results and a way to control the pause-timeout, which just doesn't work properly. The\n * result is to have to use workarounds to detect commands we would have analysed using the\n * partial results.\n * <p/>\n * You'll need to register <a href=\"https://developer.nuance.com\">Nuance Developers</a> to get an\n * API key for either just the recognition or for the NLU platform too. The details need to be\n * entered in the {@link NuanceConfiguration}file.\n * <p/>\n * Created by benrandall76@gmail.com on 07/02/2016.\n */\npublic class RecognitionNuance {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionNuance.class.getSimpleName();\n\n    private static final String RECORDER = \"recorder\";\n    private static final String RETRY = \"retry\";\n    private static final String CANCELLED = \"cancelled\";\n\n    private final Session speechSession;\n    private Transaction recoTransaction;\n    private final Transaction.Options options;\n\n    private final SaiyRecognitionListener listener;\n\n    private ArrayList<String> resultsArray;\n    private float[] floatsArray;\n    private boolean isCancelled;\n\n    private final SaiyDefaults.LanguageModel languageModel;\n    private final Context mContext;\n    private final boolean servingRemote;\n    private final String contextTag;\n    private final Locale ttsLocale;\n    private final VRLanguageNuance vrLocale;\n    private final SupportedLanguage sl;\n\n    /**\n     * Constructor\n     *\n     * @param mContext      the application context\n     * @param listener      the {@link SaiyRecognitionListener}\n     * @param detectionType one of {@link RecognitionType#DICTATION} {@link RecognitionType#SEARCH}\n     * @param serverUri     the URI of the Nuance Recognition\n     * @param nluServerUri  the URI of the Nuance Mix Recognition\n     * @param appKey        for Nuance\n     * @param contextTag    for the Mix.nlu language model\n     * @param servingRemote whether this is a local request\n     * @param languageModel how to resolve the utterance\n     * @param ttsLocale     the Text to Speech {@link Locale}\n     * @param vrLocale      the {@link VRLanguageNuance}\n     * @param sl            the {@link SupportedLanguage}\n     */\n    @MainThread\n    public RecognitionNuance(@NonNull final Context mContext, @NonNull final SaiyRecognitionListener listener,\n                             @NonNull final DetectionType detectionType, @NonNull final Uri serverUri,\n                             @NonNull final Uri nluServerUri, @NonNull final String appKey, @NonNull final String contextTag,\n                             final boolean servingRemote, @NonNull final SaiyDefaults.LanguageModel languageModel,\n                             @NonNull final Locale ttsLocale, @NonNull final VRLanguageNuance vrLocale,\n                             @NonNull final SupportedLanguage sl) {\n        this.mContext = mContext.getApplicationContext();\n        this.listener = listener;\n        this.servingRemote = servingRemote;\n        this.contextTag = contextTag;\n        this.languageModel = languageModel;\n        this.ttsLocale = ttsLocale;\n        this.vrLocale = vrLocale;\n        this.sl = sl;\n\n        if (languageModel != SaiyDefaults.LanguageModel.NUANCE) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"serverUri: \" + nluServerUri);\n                MyLog.i(CLS_NAME, \"appKey: \" + appKey);\n            }\n            speechSession = Session.Factory.session(this.mContext, serverUri, appKey);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nluServerUri: \" + nluServerUri);\n                MyLog.i(CLS_NAME, \"appKey: \" + appKey);\n            }\n            speechSession = Session.Factory.session(this.mContext, nluServerUri, appKey);\n        }\n\n        final Audio startEarcon = new Audio(this.mContext, ai.saiy.android.R.raw.sk_start, NuanceConfiguration.PCM_FORMAT);\n        final Audio stopEarcon = new Audio(this.mContext, ai.saiy.android.R.raw.sk_stop, NuanceConfiguration.PCM_FORMAT);\n        final Audio errorEarcon = new Audio(this.mContext, ai.saiy.android.R.raw.sk_error, NuanceConfiguration.PCM_FORMAT);\n\n        options = new Transaction.Options();\n        options.setDetection(detectionType);\n        options.setEarcons(startEarcon, stopEarcon, errorEarcon, null);\n\n        if (languageModel != SaiyDefaults.LanguageModel.NUANCE) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu not required\");\n            }\n\n            options.setLanguage(new Language(this.vrLocale.getLocaleString()));\n            options.setRecognitionType(RecognitionType.DICTATION);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu required\");\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"language: \" + vrLocale.getLocaleString());\n            }\n\n            // Currently eng-USA only supported\n            if (!vrLocale.equals(VRLanguageNuance.ENGLISH_US)) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"unsupported Mix.NLU language: \" + vrLocale.getLocaleString());\n                }\n\n                options.setLanguage(new Language(VRLanguageNuance.ENGLISH_US.getLocaleString()));\n            } else {\n                options.setLanguage(new Language(vrLocale.getLocaleString()));\n            }\n        }\n\n    }\n\n    /**\n     * Start the recognition\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startListening\");\n        }\n\n\n        if (languageModel != SaiyDefaults.LanguageModel.NUANCE) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"nlu not required\");\n            }\n            recoTransaction = speechSession.recognize(options, recoListener);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"LanguageModelDefault.NUANCE\");\n                MyLog.i(CLS_NAME, \"contextTag: \" + contextTag);\n            }\n            recoTransaction = speechSession.recognizeWithService(contextTag,\n                    new JSONObject(), options, recoListener);\n        }\n    }\n\n\n    /**\n     * Stop the recognition\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopListening\");\n        }\n\n        recoTransaction.stopRecording();\n    }\n\n    /**\n     * Stop the recognition and cancel any pending requests\n     */\n    public void cancelListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelListening\");\n        }\n\n        // Check that this hasn't been called by us\n        if (!isCancelled) {\n            recoTransaction.cancel();\n        }\n\n        // @onFinishedRecording not always called\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    /**\n     * Listener for recognition events\n     */\n    private final Transaction.Listener recoListener = new Transaction.Listener() {\n\n        @Override\n        public void onStartedRecording(final Transaction transaction) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onStartedRecording\");\n            }\n\n            Recognition.setState(Recognition.State.LISTENING);\n\n            listener.onReadyForSpeech(null);\n            listener.onBeginningOfSpeech();\n        }\n\n        @Override\n        public void onFinishedRecording(final Transaction transaction) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onFinishedRecording\");\n            }\n\n            // check for race condition against onError\n            switch (Recognition.getState()) {\n                case LISTENING:\n                    Recognition.setState(Recognition.State.PROCESSING);\n                    break;\n            }\n\n            // check for race condition against onError\n            if (Recognition.getState() != Recognition.State.IDLE) {\n                listener.onEndOfSpeech();\n            }\n        }\n\n        @Override\n        public void onRecognition(final Transaction transaction, final com.nuance.speechkit.Recognition recognition) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onRecognition\");\n            }\n\n            int size = recognition.getDetails().size();\n            floatsArray = new float[size];\n            resultsArray = new ArrayList<>(size);\n\n            for (int i = 0; i < size; i++) {\n\n                final RecognizedPhrase phrase = recognition.getDetails().get(i);\n\n                resultsArray.add(phrase.getText());\n                floatsArray[i] = (float) phrase.getConfidence();\n            }\n\n            final Bundle results = new Bundle();\n            results.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, resultsArray);\n            results.putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, floatsArray);\n\n            if (languageModel != SaiyDefaults.LanguageModel.NUANCE) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"nlu not required: triggering onResults\");\n                }\n                listener.onResults(results);\n            } else {\n                // Due to Nuance not supporting partial results, we need to check for a cancel\n                // command so we can react quickly and not waste resource. This is a race condition.\n                isCancelled = !servingRemote && new Cancel(sl, new SaiyResources(mContext, sl),\n                        true).detectPartial(results);\n\n                if (isCancelled) {\n                    listener.onResults(results);\n                }\n            }\n\n            Recognition.setState(Recognition.State.IDLE);\n        }\n\n        @Override\n        public void onInterpretation(final Transaction transaction, final Interpretation interpretation) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onInterpretation\");\n\n                try {\n                    MyLog.d(CLS_NAME, interpretation.getResult().toString(2));\n                } catch (final JSONException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            Recognition.setState(Recognition.State.IDLE);\n\n            // We would expect the recognition results to be returned first\n            // Check if the user has cancelled the request.\n            if (!isCancelled) {\n                if (servingRemote) {\n                    final Bundle results = new Bundle();\n                    results.putStringArrayList(Request.RESULTS_RECOGNITION, resultsArray);\n                    results.putFloatArray(Request.CONFIDENCE_SCORES, floatsArray);\n                    results.putString(Request.RESULTS_NLU, interpretation.getResult().toString());\n                    listener.onResults(results);\n                } else {\n                    new ResolveNuance(mContext, sl, UtilsLocale.stringToLocale(vrLocale.getLocaleString()),\n                            ttsLocale, floatsArray, resultsArray).unpack(interpretation.getResult());\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onInterpretation: cancelled ignoring\");\n                }\n            }\n        }\n\n        @Override\n        public void onSuccess(final Transaction transaction, final String s) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onSuccess\");\n            }\n\n            tidyUp();\n        }\n\n        @Override\n        public void onError(final Transaction transaction, final String s,\n                            final TransactionException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"onError: \" + e.getMessage() + \" ~ \" + s);\n            }\n\n            // this could be null\n            if (e != null && e.getMessage() != null) {\n\n                // not the greatest way to differentiate between errors\n                final String errorMessage = e.getMessage();\n\n                if (errorMessage.contains(RETRY)) {\n                    listener.onError(SpeechRecognizer.ERROR_NO_MATCH);\n                } else if (errorMessage.contains(RECORDER)) {\n                    listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                } else if (errorMessage.contains(CANCELLED)) {\n                    listener.onError(SpeechRecognizer.ERROR_CLIENT);\n                } else {\n                    listener.onError(SpeechRecognizer.ERROR_NETWORK);\n                }\n\n            } else {\n                listener.onError(SpeechRecognizer.ERROR_NETWORK);\n            }\n\n            Recognition.setState(Recognition.State.IDLE);\n\n            tidyUp();\n        }\n    };\n\n    /**\n     * Signal to the garbage collector\n     */\n    private void tidyUp() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"tidyUp\");\n        }\n\n        // Signal to clean up\n        System.gc();\n    }\n\n    /**\n     * Controls how long the user can pause for before end of speech is detected\n     *\n     * @param detectionType one of {@link DetectionType#None} {@link DetectionType#Short} {@link DetectionType#Long}\n     * @return the {@link DetectionType}\n     */\n    private DetectionType getDetectionType(final int detectionType) {\n\n        switch (detectionType) {\n            case 0:\n                return DetectionType.None;\n            case 1:\n                return DetectionType.Short;\n            case 2:\n                return DetectionType.Long;\n            default:\n                return DetectionType.Long;\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/remote/RecognitionRemote.java",
    "content": "package ai.saiy.android.recognition.provider.remote;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.net.ParseException;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.json.JSONTokener;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.audio.SaiyRecorder;\nimport ai.saiy.android.audio.SaiySoundPool;\nimport ai.saiy.android.audio.pause.PauseDetector;\nimport ai.saiy.android.audio.pause.PauseListener;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 29/06/2016.\n */\n\npublic class RecognitionRemote implements PauseListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionRemote.class.getSimpleName();\n\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String LANGUAGE = \"language\";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String HEADER_CONTENT_TYPE = \"application/x-www-form-urlencoded; charset=UTF-8\";\n    private static final String TRANSFER_ENCODING = \"Transfer-Encoding\";\n    private static final String CHUNKED = \"chunked\";\n    private static final String CONTENT_TYPE_AUDIO_PARAMS = \"audio/l16; rate=8000\";\n\n    // May become part of the constructor.\n    private static final short nChannels = 1;\n    private final int audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;\n    private final int sampleRateInHz = 8000;\n    private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;\n    private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n\n    public static int bufferSize;\n\n    private volatile SaiyRecorder saiyRecorder;\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private final SaiySoundPool ssp;\n\n    private HttpsURLConnection urlConnection;\n    private OutputStream outputStream;\n    private InputStream inputStream;\n\n    private final PauseDetector pauseDetector;\n\n    private final SaiyRecognitionListener listener;\n    private final String language;\n    private final String apiKey;\n    private final Uri remoteUri;\n\n    /**\n     * Constructor\n     *\n     * @param listener  the associated {@link SaiyRecognitionListener}\n     * @param language  the Locale we are using to analyse the voice data. This is not necessarily the\n     *                  Locale of the device, as the user may be multi-lingual and have set a custom\n     *                  recognition language in a launcher short-cut.\n     * @param remoteUri the Uri of the remote server\n     * @param apiKey    the Remote API key or access token\n     */\n    public RecognitionRemote(@NonNull final SaiyRecognitionListener listener,\n                             @NonNull final String language, @NonNull final Uri remoteUri,\n                             @NonNull final String apiKey, @NonNull final SaiySoundPool ssp) {\n        this.listener = listener;\n        this.language = language;\n        this.apiKey = apiKey;\n        this.remoteUri = remoteUri;\n        this.ssp = ssp;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"language: \" + language);\n        }\n\n        saiyRecorder = new SaiyRecorder(audioSource, sampleRateInHz, channelConfig, audioFormat, true);\n        pauseDetector = new PauseDetector(this, sampleRateInHz, nChannels,\n                PauseDetector.DEFAULT_PAUSE_IGNORE_TIME);\n\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            stopListening();\n        }\n    }\n\n    public void cancel() {\n    }\n\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        isRecording.set(false);\n        Recognition.setState(Recognition.State.PROCESSING);\n    }\n\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startRecording\");\n        }\n        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);\n\n        isRecording.set(true);\n        ssp.play(ssp.getBeepStart());\n        pauseDetector.begin();\n\n        bufferSize = saiyRecorder.getBufferSize();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"bufferSize: \" + bufferSize);\n        }\n\n        final byte[] buffer = new byte[bufferSize];\n\n        switch (saiyRecorder.initialise()) {\n\n            case AudioRecord.STATE_INITIALIZED:\n\n                try {\n\n                    urlConnection = (HttpsURLConnection) new URL(remoteUri.toString()).openConnection();\n                    urlConnection.setAllowUserInteraction(false);\n                    urlConnection.setInstanceFollowRedirects(true);\n                    urlConnection.setRequestMethod(Constants.HTTP_POST);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, HEADER_CONTENT_TYPE);\n                    urlConnection.setRequestProperty(AUTHORIZATION, BEARER_ + apiKey);\n                    urlConnection.setRequestProperty(LANGUAGE, language);\n                    urlConnection.setUseCaches(false);\n                    urlConnection.setDoOutput(true);\n                    urlConnection.setRequestProperty(TRANSFER_ENCODING, CHUNKED);\n                    urlConnection.setChunkedStreamingMode(0);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, CONTENT_TYPE_AUDIO_PARAMS);\n                    urlConnection.connect();\n\n                    outputStream = urlConnection.getOutputStream();\n\n                    switch (saiyRecorder.startRecording()) {\n\n                        case AudioRecord.RECORDSTATE_RECORDING: {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"AudioRecord.RECORDSTATE_RECORDING\");\n                            }\n\n                            int count = 0;\n                            int bufferReadResult;\n                            while (isRecording.get() && saiyRecorder != null\n                                    && saiyRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n\n                                if (count == 0) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"Recording Started\");\n                                    }\n\n                                    Recognition.setState(Recognition.State.LISTENING);\n                                    listener.onReadyForSpeech(null);\n                                    count++;\n                                }\n\n                                if (saiyRecorder != null) {\n                                    bufferReadResult = saiyRecorder.read(buffer);\n                                    listener.onBufferReceived(buffer);\n\n                                    if (!pauseDetector.hasDetected()) {\n                                        pauseDetector.addLength(buffer, bufferReadResult);\n                                        pauseDetector.monitor();\n                                    }\n\n                                    for (int i = 0; i < bufferReadResult; i++) {\n                                        outputStream.write(buffer[i]);\n                                    }\n                                }\n                            }\n\n                            audioShutdown();\n\n                            final int responseCode = urlConnection.getResponseCode();\n\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n                            }\n\n                            if (responseCode != HttpsURLConnection.HTTP_OK) {\n                                if (DEBUG) {\n                                    MyLog.e(CLS_NAME, \"audioThread ErrorStream: \"\n                                            + UtilsString.streamToString(urlConnection.getErrorStream()));\n                                }\n                            } else {\n                                inputStream = urlConnection.getInputStream();\n\n                                final String response = UtilsString.streamToString(inputStream);\n\n                                if (DEBUG) {\n                                    MyLog.d(CLS_NAME, \"response: \" + response);\n                                }\n\n                                final JSONObject object = (JSONObject) new JSONTokener(response).nextValue();\n                                final JSONArray arrayCategories = object.getJSONArray(Request.RESULTS);\n\n                                final int size = arrayCategories.length();\n                                if (DEBUG) {\n                                    MyLog.d(CLS_NAME, \"response size: \" + size);\n                                }\n\n                                final float[] floatsArray = new float[size];\n                                final ArrayList<String> resultsArray = new ArrayList<>(size);\n\n                                JSONObject objectSegment;\n                                for (int i = 0; i < size; i++) {\n                                    objectSegment = arrayCategories.getJSONObject(i);\n\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"segment: \" + objectSegment.getString(Request.SPEECH)\n                                                + \" ~\" + objectSegment.getDouble(Request.CONFIDENCE));\n                                    }\n\n                                    resultsArray.add(objectSegment.getString(Request.SPEECH));\n                                    floatsArray[i] = (float) objectSegment.getDouble(Request.CONFIDENCE);\n                                }\n\n                                final Bundle results = new Bundle();\n                                results.putStringArrayList(Request.RESULTS_RECOGNITION, resultsArray);\n                                results.putFloatArray(Request.CONFIDENCE_SCORES, floatsArray);\n                                results.putString(Request.RESULTS_NLU, response);\n                                listener.onResults(results);\n                            }\n                        }\n\n                        break;\n                        case AudioRecord.ERROR:\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"AudioRecord.ERROR\");\n                            }\n                            listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                            break;\n                    }\n\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                } catch (final ParseException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"ParseException\");\n                        e.printStackTrace();\n                    }\n                } catch (final UnknownHostException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnknownHostException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IOException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                } finally {\n                    closeConnection();\n                }\n\n                audioShutdown();\n                break;\n\n            case AudioRecord.STATE_UNINITIALIZED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"AudioRecord.STATE_UNINITIALIZED\");\n                }\n\n                listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                break;\n        }\n    }\n\n    /**\n     * Shutdown the microphone and release the resources\n     */\n    private void audioShutdown() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"audioShutdown\");\n        }\n\n        synchronized (this) {\n\n            if (saiyRecorder != null) {\n                ssp.play(ssp.getBeepStop());\n                Recognition.setState(Recognition.State.IDLE);\n                listener.onEndOfSpeech();\n                saiyRecorder.shutdown(CLS_NAME);\n                saiyRecorder = null;\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"audioShutdown: saiyRecorder already null\");\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"audioShutdown: finished synchronisation\");\n            }\n        }\n    }\n\n    private void closeConnection() {\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (inputStream != null) {\n\n            try {\n                inputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/saiy/RecognitionSaiy.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.saiy;\n\n/**\n * Created by benrandall76@gmail.com on 03/06/2016.\n */\npublic class RecognitionSaiy {\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/saiy/assist/SaiyInteractionService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.saiy.assist;\n\n/**\n * Created by benrandall76@gmail.com on 22/08/2016.\n */\n\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.service.voice.AlwaysOnHotwordDetector;\nimport android.service.voice.AlwaysOnHotwordDetector.Callback;\nimport android.service.voice.AlwaysOnHotwordDetector.EventPayload;\nimport android.service.voice.VoiceInteractionService;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.RequiresApi;\n\nimport java.util.List;\nimport java.util.Locale;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\n\n@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\npublic class SaiyInteractionService extends VoiceInteractionService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyInteractionService.class.getSimpleName();\n\n    private static final String ACTION_MANAGE_VOICE_KEYPHRASES = \"com.android.intent.action.MANAGE_VOICE_KEYPHRASES\";\n    public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT = \"com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT\";\n    public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = \"com.android.intent.extra.VOICE_KEYPHRASE_LOCALE\";\n    private static final long MINI_SLEEP = 1500L;\n\n    private AlwaysOnHotwordDetector mHotwordDetector;\n    private String hotword;\n    private Locale locale;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        final PackageManager pm = getPackageManager();\n        final List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(\n                new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY);\n\n        if (!UtilsList.notNaked(resolveInfoList)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onCreate: no enrollment services available on the device\");\n            }\n\n            this.stopSelf();\n        }\n    }\n\n    @Override\n    public int onStartCommand(final Intent intent, final int flags, final int startId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStartCommand: \" + startId);\n        }\n\n        final Bundle bundle = intent.getExtras();\n        if (bundle != null && !bundle.isEmpty()) {\n\n            hotword = bundle.getString(EXTRA_VOICE_KEYPHRASE_HINT_TEXT);\n            locale = UtilsLocale.stringToLocale(bundle.getString(EXTRA_VOICE_KEYPHRASE_LOCALE,\n                    SupportedLanguage.ENGLISH.getLanguageCountry()));\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"hotword: \" + hotword);\n                MyLog.i(CLS_NAME, \"locale: \" + locale.toString());\n            }\n        }\n\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    @Override\n    public void onReady() {\n        super.onReady();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onReady\");\n        }\n\n        if (hotword != null && locale != null) {\n            mHotwordDetector = createAlwaysOnHotwordDetector(hotword, locale, hotwordCallback);\n        } else {\n            new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onReady: trying again\");\n                    }\n                    if (hotword != null && locale != null) {\n                        mHotwordDetector = SaiyInteractionService.this.createAlwaysOnHotwordDetector(hotword, locale, hotwordCallback);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onReady: hotword info permanently missing\");\n                        }\n                    }\n                }\n            }, MINI_SLEEP);\n        }\n    }\n\n    /**\n     * Callbacks for hotword registration and availability\n     */\n    private final Callback hotwordCallback = new Callback() {\n\n        @Override\n        public void onAvailabilityChanged(int status) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onAvailabilityChanged\");\n            }\n\n            switch (status) {\n                case AlwaysOnHotwordDetector.STATE_HARDWARE_UNAVAILABLE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"STATE_HARDWARE_UNAVAILABLE\");\n                    }\n                    break;\n                case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNSUPPORTED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"STATE_KEYPHRASE_UNSUPPORTED\");\n                    }\n                    break;\n                case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"STATE_KEYPHRASE_UNENROLLED\");\n                    }\n\n                    Intent enroll = mHotwordDetector.createEnrollIntent();\n                    MyLog.i(CLS_NAME, \"Need to enroll with \" + enroll);\n\n                    break;\n                case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"STATE_KEYPHRASE_ENROLLED - starting recognition\");\n                    }\n\n                    try {\n\n                        if (mHotwordDetector.startRecognition(\n                                AlwaysOnHotwordDetector.RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"startRecognition succeeded\");\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"startRecognition failed\");\n                            }\n                        }\n\n                    } catch (final IllegalStateException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"startRecognition IllegalStateException\");\n                            e.printStackTrace();\n                        }\n                    } catch (final UnsupportedOperationException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"startRecognition UnsupportedOperationException\");\n                            e.printStackTrace();\n                        }\n                    }\n\n                    break;\n            }\n        }\n\n        @Override\n        public void onDetected(@NonNull final EventPayload eventPayload) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onDetected\");\n            }\n        }\n\n        @Override\n        public void onError() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onError\");\n            }\n        }\n\n        @Override\n        public void onRecognitionPaused() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onRecognitionPaused\");\n            }\n        }\n\n        @Override\n        public void onRecognitionResumed() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onRecognitionResumed\");\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/saiy/assist/SaiyInteractionSession.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.saiy.assist;\n\nimport android.app.assist.AssistContent;\nimport android.app.assist.AssistStructure;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.service.voice.VoiceInteractionSession;\nimport android.support.annotation.RequiresApi;\n\n/**\n * Created by benrandall76@gmail.com on 22/08/2016.\n */\n@RequiresApi(api = Build.VERSION_CODES.M)\npublic class SaiyInteractionSession extends VoiceInteractionSession {\n\n    SaiyInteractionSession(Context context) {\n        super(context);\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n    @Override\n    public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) {\n    }\n\n    @Override\n    public void onHandleScreenshot(Bitmap screenshot) {\n    }\n\n\n    @Override\n    public void onLockscreenShown() {\n\n    }\n\n    @Override\n    public boolean[] onGetSupportedCommands(String[] commands) {\n        boolean[] res = new boolean[commands.length];\n        return res;\n    }\n\n\n    @Override\n    public void onRequestConfirmation(ConfirmationRequest request) {\n    }\n\n    @Override\n    public void onRequestPickOption(PickOptionRequest request) {\n    }\n\n\n    @Override\n    public void onRequestCompleteVoice(CompleteVoiceRequest request) {\n    }\n\n    @Override\n    public void onRequestAbortVoice(AbortVoiceRequest request) {\n    }\n\n    @Override\n    public void onRequestCommand(CommandRequest request) {\n    }\n\n    @Override\n    public void onCancelRequest(Request request) {\n        request.cancel();\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/saiy/assist/SaiyInteractionSessionService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.saiy.assist;\n\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.service.voice.VoiceInteractionSession;\nimport android.service.voice.VoiceInteractionSessionService;\nimport android.support.annotation.RequiresApi;\n\n/**\n * Created by benrandall76@gmail.com on 22/08/2016.\n */\n@RequiresApi(api = Build.VERSION_CODES.M)\npublic class SaiyInteractionSessionService extends VoiceInteractionSessionService {\n\n    @Override\n    public VoiceInteractionSession onNewSession(Bundle args) {\n        return new SaiyInteractionSession(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/sphinx/RecognitionSphinx.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.recognition.provider.sphinx;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.WorkerThread;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyHotwordListener;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsFile;\nimport edu.cmu.pocketsphinx.Assets;\nimport edu.cmu.pocketsphinx.SpeechRecognizer;\n\nimport static edu.cmu.pocketsphinx.SpeechRecognizerSetup.defaultSetup;\n\n/**\n * Created by benrandall76@gmail.com on 04/09/2016.\n */\n\npublic class RecognitionSphinx {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionSphinx.class.getSimpleName();\n\n    private static final String HOTWORD_SEARCH = \"hotwords\";\n    private static final String HOTWORD_FILE = \"hotwords.txt\";\n    private static final String ACOUSTIC_MODEL_EN = \"en-us-ptm\";\n    private static final String DICTIONARY_FILE = \"basic.dic\";\n    private static final String CONTEXT_INDEPENDENT = \"-allphone_ci\";\n    private static final String VOICE_ACTIVATION_THRESHOLD = \"-vad_threshold\";\n\n    private static final float VAD_THRESHOLD = 3.0f;\n\n    private final Context mContext;\n    private final SaiyHotwordListener listener;\n\n    private final String dictionary;\n\n    private volatile SpeechRecognizer recognizer;\n\n    private static String dirPath = null;\n    private static File assetsDir = null;\n\n    /**\n     * @param mContext the application context\n     * @param listener the {@link SaiyHotwordListener}\n     * @param sl       the {@link SupportedLanguage}\n     */\n    @WorkerThread\n    public RecognitionSphinx(@NonNull final Context mContext, @NonNull final SaiyHotwordListener listener,\n                             @NonNull final SupportedLanguage sl) {\n        this.mContext = mContext;\n        this.listener = listener;\n\n        // TODO - multiple language support\n\n        switch (sl) {\n\n            case ENGLISH:\n            case ENGLISH_US:\n                dictionary = DICTIONARY_FILE;\n                break;\n            default:\n                dictionary = DICTIONARY_FILE;\n                break;\n        }\n\n        setUp();\n    }\n\n    /**\n     * Set up the recognizer. This is resource intensive.\n     */\n    private void setUp() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setUp\");\n        }\n\n        if (dirPath == null) {\n            dirPath = UtilsFile.getPrivateDirPath(mContext);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setUp: have path: \" + dirPath);\n            }\n        }\n\n        if (dirPath != null) {\n\n            try {\n\n                if (assetsDir == null) {\n                    assetsDir = new Assets(mContext, dirPath).syncAssets();\n                }\n\n                assetsDir = new Assets(mContext, dirPath).syncAssets();\n\n                recognizer = defaultSetup()\n                        .setAcousticModel(new File(assetsDir, ACOUSTIC_MODEL_EN))\n                        .setDictionary(new File(assetsDir, dictionary))\n                        .setBoolean(CONTEXT_INDEPENDENT, true)\n                        .setFloat(VOICE_ACTIVATION_THRESHOLD, VAD_THRESHOLD)\n                        .getRecognizer();\n\n                recognizer.addKeywordSearch(HOTWORD_SEARCH, new File(assetsDir, HOTWORD_FILE));\n                recognizer.addListener(listener);\n\n                listener.onHotwordInitialised();\n\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setUp IOException\");\n                    e.printStackTrace();\n                }\n                onError(SaiyHotwordListener.ERROR_INITIALISE);\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setUp NullPointerException\");\n                    e.printStackTrace();\n                }\n                onError(SaiyHotwordListener.ERROR_INITIALISE);\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setUp Exception\");\n                    e.printStackTrace();\n                }\n                onError(SaiyHotwordListener.ERROR_INITIALISE);\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setUp dirPath null\");\n            }\n            onError(SaiyHotwordListener.ERROR_PERMISSIONS);\n        }\n    }\n\n    /**\n     * Start the recognition\n     */\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startListening\");\n        }\n\n        if (recognizer != null) {\n            Recognition.setState(Recognition.State.LISTENING);\n            System.gc();\n            recognizer.startListening(HOTWORD_SEARCH);\n            listener.onHotwordStarted();\n        } else {\n            onError(SaiyHotwordListener.ERROR_NULL);\n        }\n    }\n\n    /**\n     * Stop the recognition\n     */\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopListening\");\n        }\n\n        Recognition.setState(Recognition.State.IDLE);\n\n        if (recognizer != null) {\n\n            try {\n                recognizer.cancel();\n                recognizer.shutdown();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stopListening Exception\");\n                    e.printStackTrace();\n                }\n            } finally {\n                listener.onHotwordShutdown();\n                recognizer = null;\n                System.gc();\n            }\n        }\n    }\n\n    /**\n     * Report an error to the {@link SaiyHotwordListener}\n     *\n     * @param errorCode of the problem\n     */\n    private void onError(final int errorCode) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        listener.onHotwordError(errorCode);\n        stopListening();\n    }\n\n    /**\n     * Check if the hotword detection is currently active\n     *\n     * @return true if it's active, false otherwise\n     */\n    public boolean isListening() {\n        return recognizer != null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/sphinx/mod/SaiyAssets.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n * /* ====================================================================\n * Copyright (c) 2014 Alpha Cephei Inc.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY ALPHA CEPHEI INC. ``AS IS'' AND\n * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY\n * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * ====================================================================\n */\n\npackage ai.saiy.android.recognition.provider.sphinx.mod;\n\nimport android.content.Context;\n\nimport java.io.IOException;\n\nimport edu.cmu.pocketsphinx.Assets;\n\n/**\n * Created by benrandall76@gmail.com on 04/09/2016.\n */\n\npublic class SaiyAssets extends Assets {\n\n    public SaiyAssets(final Context context) throws IOException {\n        super(context);\n\n//        File appDir = context.getExternalFilesDir(null);\n//        if (null == appDir)\n//            throw new IOException(\"cannot get external files dir, \"\n//                    + \"external storage state is \" + Environment.getExternalStorageState());\n//        externalDir = new File(appDir, SYNC_DIR);\n//        assetManager = context.getAssets();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/recognition/provider/wit/RecognitionWit.java",
    "content": "package ai.saiy.android.recognition.provider.wit;\n\nimport android.content.Context;\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.net.ParseException;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.language.vr.VRLanguageIBM;\nimport ai.saiy.android.api.language.vr.VRLanguageWit;\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.audio.SaiyRecorder;\nimport ai.saiy.android.audio.SaiySoundPool;\nimport ai.saiy.android.audio.pause.PauseDetector;\nimport ai.saiy.android.audio.pause.PauseListener;\nimport ai.saiy.android.configuration.WitConfiguration;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.wit.NLUWit;\nimport ai.saiy.android.nlu.wit.ResolveWit;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 29/06/2016.\n */\n\npublic class RecognitionWit implements PauseListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = RecognitionWit.class.getSimpleName();\n\n    private static final String AUTHORIZATION = \"Authorization\";\n    private static final String BEARER_ = \"Bearer \";\n    private static final String CONTENT_TYPE = \"Content-Type\";\n    private static final String HEADER_CONTENT_TYPE = \"audio/raw;encoding=signed-integer;bits=16;rate=16000;endian=little\";\n    private static final String TRANSFER_ENCODING = \"Transfer-Encoding\";\n    private static final String CHUNKED = \"chunked\";\n    private static final String ACCEPT_HEADER = \"Accept\";\n    private static final String N_HEADER = \"n\";\n    private static final String ACCEPT_VERSION = \"application/vnd.wit.\" + \"20160526\";\n\n    // May become part of the constructor.\n    private static final short nChannels = 1;\n    private final int audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;\n    private final int sampleRateInHz = 16000;\n    private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;\n    private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n\n    private HttpsURLConnection urlConnection;\n    private OutputStream outputStream;\n    private InputStream inputStream;\n\n    private final PauseDetector pauseDetector;\n\n    private final Object lock = new Object();\n\n    private volatile SaiyRecorder saiyRecorder;\n    private final AtomicBoolean isRecording = new AtomicBoolean();\n    private final SaiySoundPool ssp;\n    private final SaiyRecognitionListener listener;\n    private final Context mContext;\n    private final SaiyDefaults.LanguageModel languageModel;\n    private final Locale ttsLocale;\n    private final VRLanguageWit vrLocale;\n    private final SupportedLanguage sl;\n    private final boolean servingRemote;\n    private final String accessToken;\n\n    /**\n     * Constructor\n     *\n     * @param mContext      the application context\n     * @param listener      the associated {@link SaiyRecognitionListener}\n     * @param accessToken   the Bluemix service user name\n     * @param languageModel the {@link SaiyDefaults.LanguageModel}\n     * @param ttsLocale     the Text to Speech {@link Locale}\n     * @param vrLocale      the {@link VRLanguageIBM}\n     * @param sl            the {@link SupportedLanguage}\n     * @param servingRemote true if the origin is a remote request\n     */\n    public RecognitionWit(@NonNull final Context mContext, @NonNull final SaiyRecognitionListener listener,\n                          @NonNull final String accessToken, @NonNull final SaiyDefaults.LanguageModel languageModel,\n                          @NonNull final Locale ttsLocale, @NonNull final VRLanguageWit vrLocale,\n                          @NonNull final SupportedLanguage sl, final boolean servingRemote,\n                          @NonNull final SaiySoundPool ssp) {\n        this.mContext = mContext;\n        this.listener = listener;\n        this.languageModel = languageModel;\n        this.ttsLocale = ttsLocale;\n        this.vrLocale = vrLocale;\n        this.sl = sl;\n        this.servingRemote = servingRemote;\n        this.accessToken = accessToken;\n        this.ssp = ssp;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"language: \" + vrLocale.getLocaleString());\n        }\n\n        saiyRecorder = new SaiyRecorder(audioSource, sampleRateInHz, channelConfig, audioFormat, true);\n        pauseDetector = new PauseDetector(this, sampleRateInHz, nChannels,\n                PauseDetector.DEFAULT_PAUSE_IGNORE_TIME);\n\n    }\n\n    @Override\n    public void onPauseDetected() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPauseDetected\");\n        }\n\n        if (isRecording.get()) {\n            stopListening();\n        }\n    }\n\n    public void cancel() {\n    }\n\n    public void stopListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"called stopRecording\");\n        }\n\n        isRecording.set(false);\n        Recognition.setState(Recognition.State.IDLE);\n    }\n\n    public void startListening() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startRecording\");\n        }\n        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);\n\n        isRecording.set(true);\n        pauseDetector.begin();\n\n        final int bufferSize = saiyRecorder.getBufferSize();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"bufferSize: \" + bufferSize);\n        }\n\n        final byte[] buffer = new byte[bufferSize];\n\n        switch (saiyRecorder.initialise()) {\n\n            case AudioRecord.STATE_INITIALIZED:\n\n                try {\n\n                    urlConnection = (HttpsURLConnection) new URL(WitConfiguration.WIT_SPEECH_URL).openConnection();\n                    urlConnection.setAllowUserInteraction(false);\n                    urlConnection.setInstanceFollowRedirects(true);\n                    urlConnection.setRequestMethod(Constants.HTTP_POST);\n                    urlConnection.setRequestProperty(CONTENT_TYPE, HEADER_CONTENT_TYPE);\n                    urlConnection.setRequestProperty(AUTHORIZATION, BEARER_ + accessToken);\n                    urlConnection.setRequestProperty(ACCEPT_HEADER, ACCEPT_VERSION);\n                    urlConnection.setRequestProperty(N_HEADER, \"5\");\n                    urlConnection.setUseCaches(false);\n                    urlConnection.setDoOutput(true);\n                    urlConnection.setRequestProperty(TRANSFER_ENCODING, CHUNKED);\n                    urlConnection.setChunkedStreamingMode(0);\n                    urlConnection.connect();\n\n                    outputStream = urlConnection.getOutputStream();\n\n                    ssp.play(ssp.getBeepStart());\n\n                    switch (saiyRecorder.startRecording()) {\n\n                        case AudioRecord.RECORDSTATE_RECORDING: {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"AudioRecord.RECORDSTATE_RECORDING\");\n                            }\n\n                            int bufferReadResult;\n                            int count = 0;\n                            while (isRecording.get() && saiyRecorder != null\n                                    && saiyRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n\n                                if (count == 0) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"Recording Started\");\n                                    }\n\n                                    Recognition.setState(Recognition.State.LISTENING);\n                                    listener.onReadyForSpeech(null);\n                                    listener.onBeginningOfSpeech();\n                                    count++;\n                                }\n\n                                if (saiyRecorder != null) {\n                                    bufferReadResult = saiyRecorder.read(buffer);\n                                    listener.onBufferReceived(buffer);\n\n                                    if (!pauseDetector.hasDetected()) {\n                                        pauseDetector.addLength(buffer, bufferReadResult);\n                                        pauseDetector.monitor();\n                                    }\n\n                                    for (int i = 0; i < bufferReadResult; i++) {\n                                        outputStream.write(buffer[i]);\n                                    }\n                                }\n                            }\n\n                            audioShutdown();\n\n                            final int responseCode = urlConnection.getResponseCode();\n\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"responseCode: \" + responseCode);\n                            }\n\n                            if (responseCode != HttpsURLConnection.HTTP_OK) {\n                                if (DEBUG) {\n                                    MyLog.e(CLS_NAME, \"audioThread ErrorStream: \"\n                                            + UtilsString.streamToString(urlConnection.getErrorStream()));\n                                }\n\n                                listener.onError(SpeechRecognizer.ERROR_NETWORK);\n\n                            } else {\n                                inputStream = urlConnection.getInputStream();\n\n                                final String response = UtilsString.streamToString(inputStream);\n\n                                final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n                                final NLUWit nluWit = gson.fromJson(response, NLUWit.class);\n\n                                final ArrayList<String> resultsArray = new ArrayList<>();\n                                resultsArray.add(nluWit.getText());\n\n                                final float[] floatsArray = new float[1];\n                                floatsArray[0] = nluWit.getConfidence();\n\n                                final Bundle results = new Bundle();\n\n                                if (languageModel == SaiyDefaults.LanguageModel.WIT) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"final: nlu required\");\n                                    }\n\n                                    if (servingRemote) {\n                                        results.putString(Request.RESULTS_NLU, response);\n                                        results.putStringArrayList(Request.RESULTS_RECOGNITION, resultsArray);\n                                        results.putFloatArray(Request.CONFIDENCE_SCORES, floatsArray);\n                                        listener.onResults(results);\n                                    } else {\n                                        new ResolveWit(mContext, sl, UtilsLocale.stringToLocale(vrLocale.toString()),\n                                                ttsLocale, floatsArray, resultsArray).unpack(nluWit);\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"final: nlu not required\");\n                                    }\n\n                                    results.putStringArrayList(Request.RESULTS_RECOGNITION, resultsArray);\n                                    results.putFloatArray(Request.CONFIDENCE_SCORES, floatsArray);\n                                    listener.onResults(results);\n                                }\n                            }\n                        }\n\n                        break;\n                        case AudioRecord.ERROR:\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"AudioRecord.ERROR\");\n                            }\n                            listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                            break;\n                    }\n\n                } catch (final MalformedURLException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"MalformedURLException\");\n                        e.printStackTrace();\n                    }\n                } catch (final ParseException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"ParseException\");\n                        e.printStackTrace();\n                    }\n                } catch (final UnknownHostException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"UnknownHostException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IOException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IOException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IllegalStateException e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"IllegalStateException\");\n                        e.printStackTrace();\n                    }\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                } finally {\n                    closeConnection();\n                }\n\n                audioShutdown();\n                break;\n\n            case AudioRecord.STATE_UNINITIALIZED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"AudioRecord.STATE_UNINITIALIZED\");\n                }\n\n                listener.onError(SpeechRecognizer.ERROR_AUDIO);\n                break;\n        }\n    }\n\n\n    /**\n     * Shutdown the microphone and release the resources\n     */\n    private void audioShutdown() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"audioShutdown\");\n        }\n\n        synchronized (lock) {\n\n            if (saiyRecorder != null) {\n                ssp.play(ssp.getBeepStop());\n                Recognition.setState(Recognition.State.IDLE);\n                listener.onEndOfSpeech();\n                saiyRecorder.shutdown(CLS_NAME);\n                saiyRecorder = null;\n            } else {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"audioShutdown: saiyRecorder already null\");\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"audioShutdown: finished synchronisation\");\n            }\n        }\n    }\n\n    private void closeConnection() {\n\n        if (outputStream != null) {\n\n            try {\n                outputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (inputStream != null) {\n\n            try {\n                inputStream.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        if (urlConnection != null) {\n\n            try {\n                urlConnection.disconnect();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/NotificationService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service;\n\nimport android.app.IntentService;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport java.util.Set;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.AnalysisResultHelper;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to handle notification clicks, either from the foreground {@link SelfAware} service or other\n * notifications that are shown.\n * <p>\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic class NotificationService extends IntentService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = NotificationService.class.getSimpleName();\n\n    private long then;\n\n    public static final String CLICK_ACTION = \"click_action\";\n    public static final String INTENT_CLICK = \"ai.saiy.android.INTENT_CLICK\";\n\n    public static final int NOTIFICATION_FOREGROUND = 77;\n    public static final int NOTIFICATION_LISTENING = 21;\n    public static final int NOTIFICATION_SPEAKING = 22;\n    public static final int NOTIFICATION_FETCHING = 23;\n    public static final int NOTIFICATION_COMPUTING = 24;\n    public static final int NOTIFICATION_PERMISSIONS = 25;\n    public static final int NOTIFICATION_EMOTION = 26;\n    public static final int NOTIFICATION_HOTWORD = 27;\n    public static final int NOTIFICATION_INITIALISING = 28;\n    public static final int NOTIFICATION_IDENTIFICATION = 29;\n\n    /**\n     * Constructor\n     */\n    public NotificationService() {\n        super(NotificationService.class.getSimpleName());\n    }\n\n    @Override\n    public void onCreate() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        then = System.nanoTime();\n        super.onCreate();\n    }\n\n    @Override\n    protected void onHandleIntent(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onHandleIntent\");\n        }\n\n        if (intent != null) {\n\n            final String action = intent.getAction();\n            final Bundle bundle = intent.getExtras();\n\n            if (bundle != null) {\n\n                if (DEBUG) {\n                    examineIntent(intent);\n                }\n\n                if (UtilsString.notNaked(action)) {\n\n                    if (intent.getAction().equals(INTENT_CLICK)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: INTENT_CLICK\");\n                        }\n\n                        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(\n                                SPH.getVRLocale(getApplicationContext()));\n                        bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n\n                        switch (bundle.getInt(CLICK_ACTION, 0)) {\n\n                            case NOTIFICATION_FOREGROUND:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_FOREGROUND\");\n                                }\n\n                                bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_LISTEN);\n                                bundle.putString(LocalRequest.EXTRA_UTTERANCE, PersonalityHelper.getIntro(getApplicationContext(), sl));\n                                bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, SPH.getVRLocale(getApplicationContext()).toString());\n                                bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(getApplicationContext()).toString());\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n\n                                break;\n                            case NOTIFICATION_LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_LISTENING\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_SPEAKING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_SPEAKING\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_FETCHING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_FETCHING\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_INITIALISING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_INITIALISING\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_COMPUTING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_COMPUTING\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_PERMISSIONS:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_PERMISSIONS\");\n                                }\n\n                                ExecuteIntent.openApplicationSpecificSettings(getApplicationContext(), getPackageName());\n\n                                bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_ONLY);\n                                bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE,\n                                        SPH.getVRLocale(getApplicationContext()).toString());\n                                bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE,\n                                        SPH.getTTSLocale(getApplicationContext()).toString());\n\n                                String permissionContent;\n\n                                switch (bundle.getInt(PermissionHelper.REQUESTED_PERMISSION, 0)) {\n\n                                    case PermissionHelper.REQUEST_AUDIO:\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onHandleIntent: REQUEST_AUDIO\");\n                                        }\n                                        permissionContent = getString(ai.saiy.android.R.string.permission_audio);\n                                        break;\n                                    case PermissionHelper.REQUEST_GROUP_CONTACTS:\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onHandleIntent: REQUEST_GROUP_CONTACTS\");\n                                        }\n                                        permissionContent = getString(ai.saiy.android.R.string.permission_group_contacts);\n                                        break;\n                                    case PermissionHelper.REQUEST_FILE:\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onHandleIntent: REQUEST_FILE\");\n                                        }\n                                        // TODO\n                                        permissionContent = getString(ai.saiy.android.R.string.permission_unknown);\n                                        break;\n                                    default:\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"onHandleIntent: PermissionHelper.UNKNOWN\");\n                                        }\n                                        permissionContent = getString(ai.saiy.android.R.string.permission_unknown);\n                                        break;\n                                }\n\n                                bundle.putString(LocalRequest.EXTRA_UTTERANCE, permissionContent);\n\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_EMOTION:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_EMOTION\");\n                                }\n\n                                bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_ONLY);\n\n                                final String emotionAnalysis = AnalysisResultHelper.getEmotionDescription(\n                                        getApplicationContext(), sl);\n\n                                bundle.putString(LocalRequest.EXTRA_UTTERANCE, emotionAnalysis);\n                                bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE,\n                                        SPH.getVRLocale(getApplicationContext()).toString());\n                                bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE,\n                                        SPH.getTTSLocale(getApplicationContext()).toString());\n                                bundle.putSerializable(LocalRequest.EXTRA_COMMAND, CC.COMMAND_EMOTION);\n\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                            case NOTIFICATION_HOTWORD:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_HOTWORD\");\n                                }\n\n                                bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_STOP_HOTWORD);\n                                final LocalRequest request = new LocalRequest(getApplicationContext(), bundle);\n                                request.setShutdownHotword();\n                                request.execute();\n\n                                final Intent closeShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);\n                                sendBroadcast(closeShadeIntent);\n\n                                break;\n                            case NOTIFICATION_IDENTIFICATION:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onHandleIntent: NOTIFICATION_IDENTIFICATION\");\n                                }\n\n                                bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_ONLY);\n                                bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE,\n                                        SPH.getVRLocale(getApplicationContext()).toString());\n                                bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE,\n                                        SPH.getTTSLocale(getApplicationContext()).toString());\n\n                                switch (bundle.getInt(LocalRequest.EXTRA_CONDITION)) {\n\n                                    case Condition.CONDITION_IDENTIFY:\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onHandleIntent: CONDITION_IDENTIFY\");\n                                        }\n\n                                        final Speaker.Confidence confidence = (Speaker.Confidence) bundle.getSerializable(\n                                                Speaker.EXTRA_IDENTIFY_OUTCOME);\n\n                                        if (confidence != null) {\n\n                                            switch (confidence) {\n\n                                                case HIGH:\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"checkResult: confidence: \"\n                                                                + Speaker.Confidence.HIGH.name());\n                                                    }\n\n                                                    bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                            PersonalityResponse.getVocalIDHigh(getApplicationContext(),\n                                                                    sl));\n                                                    break;\n                                                case NORMAL:\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"checkResult: confidence: \"\n                                                                + Speaker.Confidence.NORMAL.name());\n                                                    }\n                                                    bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                            PersonalityResponse.getVocalIDMedium(getApplicationContext(),\n                                                                    sl));\n                                                    break;\n                                                case LOW:\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"checkResult: confidence: \"\n                                                                + Speaker.Confidence.LOW.name());\n                                                    }\n                                                    bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                            PersonalityResponse.getVocalIDLow(getApplicationContext(),\n                                                                    sl));\n                                                    break;\n                                                case UNDEFINED:\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"checkResult: confidence: \"\n                                                                + Speaker.Confidence.UNDEFINED.name());\n                                                    }\n                                                    bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                            PersonalityResponse.getVocalIDLow(getApplicationContext(),\n                                                                    sl));\n                                                    break;\n                                                case ERROR:\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"checkResult: confidence: \"\n                                                                + Speaker.Confidence.ERROR.name());\n                                                    }\n                                                    bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                            PersonalityResponse.getVocalIDError(getApplicationContext(),\n                                                                    sl));\n                                                    break;\n                                            }\n                                        }\n\n                                        break;\n                                    case Condition.CONDITION_IDENTITY:\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"onHandleIntent: CONDITION_IDENTITY\");\n                                        }\n\n                                        if (bundle.getBoolean(Speaker.EXTRA_IDENTITY_OUTCOME, false)) {\n                                            bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                    SaiyResourcesHelper.getStringResource(getApplicationContext(),\n                                                            sl, R.string.speech_enroll_success));\n                                        } else {\n                                            bundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                                    PersonalityResponse.getEnrollmentError(getApplicationContext(),\n                                                            sl));\n                                        }\n\n                                        break;\n                                }\n\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n\n                                break;\n                            default:\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"onHandleIntent: default\");\n                                }\n                                new LocalRequest(getApplicationContext(), bundle).execute();\n                                break;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"onHandleIntent: ACTION_UNKNOWN\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onHandleIntent: Bundle null\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onHandleIntent: Action null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \" onHandleIntent: Intent null\");\n            }\n        }\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param intent containing potential extras\n     */\n    private void examineIntent(@NonNull final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineIntent\");\n        }\n\n        final Bundle bundle = intent.getExtras();\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            //noinspection Convert2streamapi\n            for (final String key : keys) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"examineIntent: \" + key + \" ~ \" + bundle.get(key));\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/SelfAware.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service;\n\nimport android.app.Notification;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Binder;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Process;\nimport android.os.RemoteException;\nimport android.speech.SpeechRecognizer;\nimport android.speech.tts.TextToSpeech.OnInitListener;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.WorkerThread;\nimport android.telephony.PhoneStateListener;\nimport android.telephony.TelephonyManager;\nimport android.util.Pair;\nimport android.widget.Toast;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.DeclinedBinder;\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.helper.CallbackType;\nimport ai.saiy.android.api.remote.Request;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.audio.AudioParameters;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.BeyondVerbal;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.SpeakerEnrollment;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.SpeakerIdentification;\nimport ai.saiy.android.cognitive.motion.provider.google.MotionRecognition;\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.command.translate.provider.bing.BingCredentials;\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.nlu.apiai.ResolveAPIAI;\nimport ai.saiy.android.nlu.local.InitStrings;\nimport ai.saiy.android.partial.PartialHelper;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.RecognitionAction;\nimport ai.saiy.android.recognition.SaiyHotwordListener;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.recognition.helper.GoogleNowMonitor;\nimport ai.saiy.android.recognition.provider.bluemix.RecognitionBluemix;\nimport ai.saiy.android.recognition.provider.google.chromium.RecognitionGoogleChromium;\nimport ai.saiy.android.recognition.provider.google.cloud.RecognitionGoogleCloud;\nimport ai.saiy.android.recognition.provider.microsoft.RecognitionMicrosoft;\nimport ai.saiy.android.recognition.provider.nuance.RecognitionNuance;\nimport ai.saiy.android.recognition.provider.remote.RecognitionRemote;\nimport ai.saiy.android.recognition.provider.sphinx.RecognitionSphinx;\nimport ai.saiy.android.recognition.provider.wit.RecognitionWit;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareCache;\nimport ai.saiy.android.service.helper.SelfAwareConditions;\nimport ai.saiy.android.service.helper.SelfAwareParameters;\nimport ai.saiy.android.service.helper.SelfAwareVerbose;\nimport ai.saiy.android.sound.VolumeHelper;\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\nimport ai.saiy.android.tts.TTS;\nimport ai.saiy.android.tts.engine.EngineNuance;\nimport ai.saiy.android.tts.helper.PendingTTS;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsBundle;\n\n/**\n * This foreground service class will remain running unless the user deactivates it\n * from the Saiy application settings, or the device becomes extremely low on memory. It is started\n * at boot. A permanent notification is required in order for Android to keep the service alive.\n * <p/>\n * Despite its name, as much as possible (always), this service should be stateless in terms of\n * performing requests, consider it a RESTful service. Requests that relate to or depend upon previous\n * actions should not be handled directly here. Instead, parameters should be provided with each request,\n * most often in the form of a {@link Bundle} that is subsequently actioned and then forgotten.\n * <p/>\n * This class does however manage the Text to Speech and Voice Recognition objects, as its lifecycle dictates\n * how and when these are required. With the management of these objects comes the responsibility of\n * handling bugs and idiosyncratic conditions....\n * <p>\n * Ideally, this service would run in its own process, but the requirement to behave according to the global\n * application settings, would make it less than trivial.\n * <p>\n * The service is exposed to remote connections, for applications that wish to utilise features of\n * Saiy. This class manages their connection and execution and the conditions that surround them.\n * <p>\n * To keep such an important class as readable and concise as possible, tasks are resolved in the\n * helper classes {@link SelfAwareConditions} {@link SelfAwareParameters} {@link SelfAwareCache}\n * wherever possible.\n * <p>\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic class SelfAware extends Service {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SelfAware.class.getSimpleName();\n\n    private static final int MAX_INIT_ATTEMPTS = 4;\n    private static final long SPEECH_ELAPSED_WARNING = 150;\n    private static final long WARM_UP_SLEEP = 500L;\n    private static final int WARM_UP_LOOP = 4;\n\n    public static final int JB_TIMEOUT_ERROR = 134;\n    private static final long OKAY_GOOGLE_DELAY = 500L;\n\n    private static final long MONITOR_ENGINE = 10000L;\n\n    private volatile SaiyTextToSpeech tts;\n\n    private volatile EngineNuance en;\n\n    private volatile RecognitionNuance recogNuance;\n    private volatile RecognitionGoogleCloud recogGoogleCloud;\n    private volatile RecognitionGoogleChromium recogGoogleChromium;\n    private volatile RecognitionMicrosoft recogOxford;\n    private volatile RecognitionWit recogWit;\n    private volatile RecognitionBluemix recogIBM;\n    private volatile RecognitionRemote recogRemote;\n    private volatile RecognitionSphinx recogSphinx;\n    private volatile RecognitionMic recogMic;\n    private volatile SpeechRecognizer recogNative;\n\n    private volatile SelfAwareConditions conditions;\n    private volatile SelfAwareParameters params;\n    private volatile SelfAwareCache cache;\n\n    private volatile PartialHelper partialHelper;\n    private volatile PendingTTS pendingTTS;\n    private volatile int initCount;\n\n    private final MotionRecognition motionRecognition = new MotionRecognition();\n\n    private final AtomicBoolean initPending = new AtomicBoolean();\n\n    private final IBinder localBinder = new BoundSA();\n    private static SelfAware instance = null;\n\n    private TelephonyManager telephonyManager;\n\n    /**\n     * Exposed instance for local binding.\n     */\n    public class BoundSA extends Binder {\n        public SelfAware getService() {\n            return SelfAware.this;\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        setInstance();\n        startForeground(NotificationHelper.NOTIFICATION_SELF_AWARE);\n        conditions = new SelfAwareConditions(getApplicationContext(), getTelephonyManager());\n        params = new SelfAwareParameters(getApplicationContext());\n        cache = new SelfAwareCache(getApplicationContext());\n\n        if (SPH.getMotionEnabled(getApplicationContext())) {\n            motionRecognition.prepare(getApplicationContext());\n            motionRecognition.connect();\n        }\n    }\n\n    /**\n     * We check here to see if the Text to Speech Engine should be warmed up, to avoid any future\n     * initialisation delays. A 'race condition' of attempting to instantiate multiple TTS objects\n     * is avoided by using the {@link #initSaiyTTS()} and following {@link #initTTS()} helper methods.\n     */\n    @Override\n    public int onStartCommand(final Intent intent, final int flags, final int startId) {\n        if (!UtilsBundle.isSuspicious(intent)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onStartCommand: \" + startId);\n                MyLog.d(CLS_NAME, \"onStartCommand: package: \" + conditions.getPackage(intent));\n            }\n\n            if (conditions.shouldWarmUp(tts, intent)) {\n                initTTS();\n                restartStatusMonitor();\n            }\n\n            new InitStrings(getApplicationContext()).init();\n\n            switch (conditions.checkNotificationInstruction(intent)) {\n\n                case Condition.CONDITION_SELF_AWARE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onStartCommand: CONDITION_SELF_AWARE\");\n                    }\n                    startForeground(NotificationHelper.NOTIFICATION_SELF_AWARE);\n                    break;\n                case Condition.CONDITION_NONE:\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onStartCommand: CONDITION_NONE\");\n                    }\n                    break;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"onStartCommand: intent suspicious: stopping: \" + startId);\n            }\n\n            this.stopSelf(startId);\n        }\n\n        return Service.START_STICKY;\n\n    }\n\n\n    @Override\n    public IBinder onBind(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBind\");\n        }\n\n        final Pair<Boolean, Boolean> shouldBind = conditions.shouldBind(intent);\n\n        if (shouldBind.first) {\n            restartStatusMonitor();\n\n            new InitStrings(getApplicationContext()).init();\n\n            if (shouldBind.second) {\n                return localBinder;\n            } else {\n                return remoteBinder;\n            }\n        }\n\n        return new DeclinedBinder();\n    }\n\n    @Override\n    public void onRebind(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRebind\");\n        }\n\n        if (conditions.shouldRebind(intent)) {\n            restartStatusMonitor();\n            super.onRebind(intent);\n        }\n    }\n\n    @Override\n    public boolean onUnbind(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onUnbind\");\n        }\n        return conditions.shouldUnbind(intent);\n    }\n\n    /**\n     * Remote access for external bound clients\n     */\n    private final ISaiy.Stub remoteBinder = new ISaiy.Stub() {\n\n        @Override\n        public boolean isReal() throws RemoteException {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"remoteBinder: isReal\");\n                MyLog.v(CLS_NAME, \"remoteBinder: calling app: \"\n                        + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            }\n            return true;\n        }\n\n        @Override\n        public void speakListen(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"remoteBinder: speakListen\");\n                MyLog.v(CLS_NAME, \"remoteBinder: calling app: \"\n                        + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            }\n\n            if (conditions.checkSaiyRemotePermission()) {\n                if (conditions.shouldAction(rl, bundle)) {\n                    ((SelfAware.BoundSA) localBinder).getService().\n                            speakListen(bundle);\n                }\n            } else {\n                throw new RemoteException(getString(ai.saiy.android.R.string.error_missing_permissions));\n            }\n        }\n\n        @Override\n        public void speakOnly(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"remoteBinder: speakOnly\");\n                MyLog.v(CLS_NAME, \"remoteBinder: calling app: \"\n                        + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            }\n\n            if (conditions.checkSaiyRemotePermission()) {\n                if (conditions.shouldAction(rl, bundle)) {\n                    ((SelfAware.BoundSA) localBinder).getService().\n                            speakOnly(bundle);\n                }\n            } else {\n                throw new RemoteException(getString(ai.saiy.android.R.string.error_missing_permissions));\n            }\n        }\n    };\n\n    /**\n     * Check if the hotword detection is currently active\n     *\n     * @return {@link Pair} with the first parameter denoting if the hotword is active, and the second if\n     * it is set to restart\n     */\n    protected Pair<Boolean, Boolean> isHotwordActive() {\n        return new Pair<>(conditions.isHotwordActive(recogSphinx), conditions.restartHotword());\n    }\n\n    /**\n     * Check if the recognition is currently in use\n     *\n     * @return true if the state is {@link Recognition.State#LISTENING}\n     * or {@link Recognition.State#PROCESSING}. Note, active hotword detection is not considered here.\n     */\n    protected boolean isListening() {\n        return !conditions.isHotwordActive(recogSphinx) && conditions.isListening();\n    }\n\n    /**\n     * Check if the tts engine is currently in use\n     *\n     * @return true if the engine is speaking\n     */\n    protected Pair<Boolean, Integer> isSpeaking() {\n        return conditions.isSpeaking(tts);\n    }\n\n    /**\n     * Called to start the hotword detection.\n     *\n     * @param bundle of instructions\n     */\n    @SuppressWarnings(\"UnusedParameters\")\n    protected void startHotwordDetection(@NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startHotwordDetection\");\n            MyLog.v(CLS_NAME, \"startHotwordDetection: calling app: \"\n                    + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            MyLog.i(CLS_NAME, \"startHotwordDetection: isMain thread: \" + (Looper.myLooper() == Looper.getMainLooper()));\n            MyLog.i(CLS_NAME, \"startHotwordDetection: threadTid: \" + android.os.Process.getThreadPriority(android.os.Process.myTid()));\n        }\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);\n                recogSphinx = conditions.getSphinxRecognition(hotwordListener);\n                recogSphinx.startListening();\n            }\n        });\n\n        conditions.onVRComplete();\n    }\n\n    /**\n     * Called when voice recognition should begin once this utterance has completed\n     *\n     * @param bundle of instructions\n     */\n    protected void speakListen(@NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"speakListen\");\n            MyLog.v(CLS_NAME, \"speakListen: calling app: \"\n                    + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            MyLog.i(CLS_NAME, \"speakListen: isMain thread: \" + (Looper.myLooper() == Looper.getMainLooper()));\n            MyLog.i(CLS_NAME, \"speakListen: threadTid: \" + android.os.Process.getThreadPriority(android.os.Process.myTid()));\n        }\n\n        speak(true, bundle);\n    }\n\n    /**\n     * Called when only speech is required with no voice recognition on the completion\n     * of the utterance\n     *\n     * @param bundle of instructions\n     */\n    protected void speakOnly(@NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"speakOnly\");\n            MyLog.v(CLS_NAME, \"speakOnly: calling app: \"\n                    + getPackageManager().getNameForUid(Binder.getCallingUid()));\n            MyLog.i(CLS_NAME, \"speakOnly: isMain thread: \" + (Looper.myLooper() == Looper.getMainLooper()));\n            MyLog.i(CLS_NAME, \"speakOnly: threadTid: \" + android.os.Process.getThreadPriority(android.os.Process.myTid()));\n        }\n\n        speak(false, bundle);\n    }\n\n    /**\n     * Called to initiate speech\n     *\n     * @param bundle        of instructions\n     * @param isSpeakListen true if the recognition should begin once the utterance has completed\n     */\n    private void speak(final boolean isSpeakListen, @NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"speak\");\n        }\n\n        NotificationHelper.cancelComputingNotification(getApplicationContext());\n\n        if (conditions.checkSaiyPermission(Binder.getCallingUid())) {\n\n            if (conditions.isHotwordActive(recogSphinx)) {\n                stopListening(false);\n            }\n\n            final Pair<Pair<Boolean, Boolean>, Pair<Boolean, Boolean>> priorityPair = conditions.proceedPriority(tts, bundle);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"speak: priorityPair: \" + priorityPair.first.first + \" ~ \" + priorityPair.first.second);\n            }\n\n            if (priorityPair.first.first) {\n\n                if (!priorityPair.second.second) {\n\n                    if (priorityPair.first.second) {\n                        stopSpeech(true);\n                    }\n\n                    if (priorityPair.first.second || !priorityPair.second.first || conditions.isQueueAdd(bundle)) {\n\n                        switch (conditions.getDefaultTTS()) {\n\n                            case LOCAL:\n                                conditions.setInitialisingCountdown();\n                                break;\n                            case NETWORK_NUANCE:\n                                conditions.setFetchingCountdown();\n                                break;\n                        }\n\n                        if (isSpeakListen) {\n                            switch (conditions.getDefaultRecognition()) {\n                                case GOOGLE_CLOUD:\n                                    switch (Recognition.getState()) {\n                                        case IDLE:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"GOOGLE_CLOUD: IDLE\");\n                                            }\n                                            new Thread() {\n                                                public void run() {\n                                                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"speakListen: GOOGLE_CLOUD: warming up\");\n                                                    }\n\n                                                    recogMic = conditions.getMicRecognition(null,\n                                                            AudioParameters.getDefaultMicrosoft(),\n                                                            false, 0, true, false);\n                                                    recogGoogleCloud = conditions.getGoogleCloudRecognition(recogMic, recognitionListener);\n                                                }\n                                            }.start();\n                                            break;\n                                    }\n                                    break;\n                                case IBM:\n                                    switch (Recognition.getState()) {\n                                        case IDLE:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"IBM: IDLE\");\n                                            }\n                                            new Thread() {\n                                                public void run() {\n                                                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"speakListen: IBM: warming up\");\n                                                    }\n\n                                                    recogMic = conditions.getMicRecognition(null,\n                                                            AudioParameters.getDefaultMicrosoft(),\n                                                            false, 0, true, false);\n                                                    recogIBM = conditions.getIBMRecognition(recogMic, recognitionListener);\n                                                }\n                                            }.start();\n                                            break;\n                                    }\n                                    break;\n                                case MICROSOFT:\n                                    switch (Recognition.getState()) {\n                                        case IDLE:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"MICROSOFT: IDLE\");\n                                            }\n                                            new Thread() {\n                                                public void run() {\n                                                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);\n                                                    if (DEBUG) {\n                                                        MyLog.i(CLS_NAME, \"speakListen: MICROSOFT: warming up\");\n                                                    }\n                                                    recogOxford = conditions.getMicrosoftRecognition(recognitionListener);\n                                                }\n                                            }.start();\n                                            break;\n                                    }\n                                    break;\n                            }\n                        }\n\n                        restartEngineMonitor();\n                        params.setParams(isSpeakListen, conditions);\n\n                        /*\n                         * Bundle is replace here\n                         */\n                        if (conditions.checkConditions(tts, conditions.getDefaultTTS(), bundle)) {\n\n                            final String utterance = conditions.getUtterance();\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"speak: \" + utterance);\n                            }\n\n                            if (!conditions.isSilentUtterance()) {\n\n                                switch (conditions.getDefaultTTS()) {\n\n                                    case LOCAL:\n                                        conditions.setVoice(tts, params);\n                                        doSpeech(tts.speak(utterance, conditions.getQueueType(),\n                                                params, params.getUtteranceId()),\n                                                conditions.getQueueType(), utterance);\n                                        break;\n                                    case NETWORK_NUANCE:\n\n                                        if (conditions.servingRemote()) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"speak: setting MainLooper\");\n                                            }\n\n                                            new Handler(Looper.getMainLooper()).post(new Runnable() {\n                                                @Override\n                                                public void run() {\n                                                    en = conditions.getEngineNuance(params, progressListener);\n                                                    en.startSpeech(utterance);\n                                                }\n                                            });\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"speak: not setting MainLooper\");\n                                            }\n\n                                            en = conditions.getEngineNuance(params, progressListener);\n                                            en.startSpeech(utterance);\n                                        }\n\n                                        break;\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"speak: processing silence\");\n                                }\n                                conditions.removeRunnableCallback(engineMonitor);\n                                progressListener.onDone(params.getUtteranceId());\n                            }\n                        } else {\n                            speechRequestError(conditions.getQueueType(), conditions.getUtterance());\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"speak: isSpeaking: not queue add\");\n                        }\n                        stopSpeech(false);\n                        conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speak: isListening\");\n                    }\n                    stopListening(false);\n                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"speak: priority lower, ignoring\");\n                }\n                conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n            }\n        } else {\n            MyLog.e(CLS_NAME, getString(ai.saiy.android.R.string.error_missing_permissions));\n            conditions.manageCallback(CallbackType.CB_ERROR_DEVELOPER, null);\n        }\n    }\n\n    /**\n     * Called to stop speech\n     */\n    protected void stopSpeech(final boolean preventRecognition) {\n        conditions.stopSpeech(tts, params, en, preventRecognition);\n        resetPendingConditions();\n    }\n\n\n    /**\n     * Stop the recognition currently in use\n     *\n     * @param shutdown true if the hotword detection should be permanently shutdown\n     */\n    protected void stopListening(final boolean shutdown) {\n        conditions.setHotwordShutdown(recogSphinx, shutdown);\n        conditions.stopListening(recogNuance, recogGoogleCloud, recogGoogleChromium, recogOxford,\n                recogWit, recogIBM, recogRemote, recogMic, recogNative, recogSphinx);\n    }\n\n    /**\n     * Although we can't rely on this reporting correctly, due to weird and wonderful conditions,\n     * we use it as a safety net by exposing a static instance.\n     */\n    public static boolean checkInstance() {\n        return SelfAware.instance != null;\n    }\n\n    /**\n     * Runnable for speech only commands\n     */\n    private final Runnable soRun = new Runnable() {\n\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"soRun\");\n            }\n\n            conditions.removeRunnableCallback(this);\n\n            switch (conditions.getCondition()) {\n\n                case Condition.CONDITION_TRANSLATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"soRun: Condition.CONDITION_TRANSLATION\");\n                    }\n\n                    if (!TranslationProvider.shouldAction(getApplicationContext(),\n                            conditions.getSupportedLanguage(conditions.servingRemote()))) {\n\n                        if (conditions.restartHotword()) {\n                            startHotwordDetection(conditions.getBundle());\n                        }\n                    }\n                    break;\n                case Condition.CONDITION_NONE:\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"soRun: Condition.NONE\");\n                    }\n\n                    System.gc();\n\n                    if (conditions.restartHotword()) {\n                        startHotwordDetection(conditions.getBundle());\n                    }\n\n                    break;\n            }\n\n            SPH.setLastUsed(getApplicationContext());\n        }\n    };\n\n    /**\n     * Runnable for speech then recognition commands\n     */\n    private final Runnable slRun = new Runnable() {\n\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"slRun\");\n            }\n\n            if (conditions.checkAudioPermission()) {\n\n                if (conditions.isNetworkAvailable() ||\n                        conditions.getDefaultRecognition() == SaiyDefaults.VR.NATIVE) {\n\n                    switch (conditions.getDefaultRecognition()) {\n\n                        case NUANCE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"NUANCE/NUANCE_NLU\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NUANCE: IDLE\");\n                                    }\n\n                                    recognitionListener.resetBugVariables();\n                                    recogNuance = conditions.getNuanceRecognition(recognitionListener);\n                                    recogNuance.startListening();\n\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NUANCE: PROCESSING\");\n                                    }\n\n                                    recogNuance.cancelListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NUANCE: LISTENING\");\n                                    }\n\n                                    recogNuance.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n\n                        case GOOGLE_CLOUD:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"GOOGLE_CLOUD\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CLOUD: IDLE\");\n                                    }\n\n                                    recognitionListener.resetBugVariables();\n\n                                    AsyncTask.execute(new Runnable() {\n                                        @Override\n                                        public void run() {\n                                            if (!waitGoogleCloud()) {\n                                                if (DEBUG) {\n                                                    MyLog.w(CLS_NAME, \"GOOGLE_CLOUD: IDLE: failed warm up loop\");\n                                                }\n\n                                                recogMic = conditions.getMicRecognition(null,\n                                                        AudioParameters.getDefaultMicrosoft(), false, 0, true, false);\n                                                recogGoogleCloud = conditions.getGoogleCloudRecognition(recogMic,\n                                                        recognitionListener);\n                                            }\n\n                                            recogGoogleCloud.startListening();\n                                        }\n                                    });\n\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CLOUD: PROCESSING\");\n                                    }\n\n                                    recogGoogleCloud.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CLOUD: LISTENING\");\n                                    }\n\n                                    recogGoogleCloud.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n\n                        case GOOGLE_CHROMIUM:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"GOOGLE_CHROMIUM\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CHROMIUM: IDLE\");\n                                    }\n\n                                    recognitionListener.resetBugVariables();\n\n                                    AsyncTask.execute(new Runnable() {\n                                        @Override\n                                        public void run() {\n                                            recogGoogleChromium = conditions.getGoogleChromiumRecognition(recognitionListener);\n                                            recogGoogleChromium.startListening();\n                                        }\n                                    });\n\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CHROMIUM: PROCESSING\");\n                                    }\n\n                                    recogGoogleChromium.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"GOOGLE_CHROMIUM: LISTENING\");\n                                    }\n\n                                    recogGoogleChromium.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n\n                        case MICROSOFT:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"MICROSOFT\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MICROSOFT: IDLE\");\n                                    }\n\n                                    conditions.setFetchingCountdown();\n                                    recognitionListener.resetBugVariables();\n\n                                    AsyncTask.execute(new Runnable() {\n                                        @Override\n                                        public void run() {\n                                            if (!waitMicrosoft()) {\n                                                if (DEBUG) {\n                                                    MyLog.w(CLS_NAME, \"MICROSOFT: IDLE: failed warm up loop\");\n                                                }\n                                                recogOxford = conditions.getMicrosoftRecognition(recognitionListener);\n                                            }\n                                            recogOxford.startListening();\n                                        }\n                                    });\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MICROSOFT: PROCESSING\");\n                                    }\n\n                                    recogOxford.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MICROSOFT: LISTENING\");\n                                    }\n\n                                    recogOxford.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n                        case WIT:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"WIT\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"WIT: IDLE\");\n                                    }\n\n                                    conditions.setFetchingCountdown();\n                                    recognitionListener.resetBugVariables();\n                                    AsyncTask.execute(new Runnable() {\n                                        @Override\n                                        public void run() {\n                                            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);\n                                            recogWit = conditions.getWitRecognition(recognitionListener);\n                                            recogWit.startListening();\n                                        }\n                                    });\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"WIT: PROCESSING\");\n                                    }\n\n                                    recogWit.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"WIT: LISTENING\");\n                                    }\n\n                                    recogWit.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n                        case IBM:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"IBM\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"IBM: IDLE\");\n                                    }\n\n                                    conditions.setFetchingCountdown();\n                                    AsyncTask.execute(new Runnable() {\n                                        @Override\n                                        public void run() {\n                                            if (!waitIBM()) {\n                                                if (DEBUG) {\n                                                    MyLog.w(CLS_NAME, \"IBM: IDLE: failed warm up loop\");\n                                                }\n\n                                                recogMic = conditions.getMicRecognition(null,\n                                                        AudioParameters.getDefaultMicrosoft(), false, 0, true, false);\n                                                recogIBM = conditions.getIBMRecognition(recogMic, recognitionListener);\n                                            }\n                                            recogIBM.startListening();\n                                        }\n                                    });\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"IBM: PROCESSING\");\n                                    }\n\n                                    recogIBM.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"WIT: LISTENING\");\n                                    }\n\n                                    recogIBM.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n                        case NATIVE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"NATIVE\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NATIVE: IDLE\");\n                                    }\n\n                                    if (SpeechRecognizer.isRecognitionAvailable(getApplicationContext())) {\n\n                                        recognitionListener.resetBugVariables();\n\n                                        if (SPH.getRecogniserBusyFix(getApplicationContext())) {\n                                            releaseRecognition();\n                                        }\n\n                                        if (recogNative == null) {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"NATIVE: creating\");\n                                            }\n\n                                            final Pair<SpeechRecognizer, Intent> nativePair =\n                                                    conditions.getNativeRecognition(recognitionListener);\n\n                                            if (nativePair != null) {\n                                                recogNative = nativePair.first;\n                                                recogNative.startListening(nativePair.second);\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.w(CLS_NAME, \"NATIVE: Pair null: Unavailable\");\n                                                }\n                                                conditions.issueNoVRProvider();\n                                                conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                                            }\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"NATIVE: exists\");\n                                            }\n                                            recogNative.startListening(conditions.getNativeIntent());\n                                        }\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.w(CLS_NAME, \"NATIVE: Unavailable\");\n                                        }\n                                        conditions.issueNoVRProvider();\n                                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                                    }\n\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NATIVE: PROCESSING\");\n                                    }\n\n                                    if (recogNative != null) {\n                                        recogNative.stopListening();\n                                    }\n\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"NATIVE: LISTENING\");\n                                    }\n\n                                    if (recogNative != null) {\n                                        recogNative.stopListening();\n                                    }\n\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n\n                            break;\n                        case REMOTE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"REMOTE\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"REMOTE: IDLE\");\n                                    }\n\n                                    recognitionListener.resetBugVariables();\n                                    recogRemote = conditions.getRemoteRecognition(recognitionListener);\n                                    recogRemote.startListening();\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"REMOTE: PROCESSING\");\n                                    }\n\n                                    recogRemote.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"REMOTE: LISTENING\");\n                                    }\n                                    recogRemote.stopListening();\n                                    conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                                    break;\n                            }\n                            break;\n                        case MIC:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"MIC\");\n                            }\n\n                            switch (Recognition.getState()) {\n\n                                case IDLE:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MIC: IDLE\");\n                                    }\n\n                                    switch (conditions.getBundle().getInt(LocalRequest.EXTRA_CONDITION,\n                                            Condition.CONDITION_NONE)) {\n\n                                        case Condition.CONDITION_EMOTION:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"MIC: CONDITION_EMOTION\");\n                                            }\n\n                                            conditions.setFetchingCountdown();\n                                            recogMic = conditions.getMicRecognition(recognitionListener,\n                                                    AudioParameters.getDefaultBeyondVerbal(), true,\n                                                    BeyondVerbal.MINIMUM_AUDIO_TIME, false, false);\n\n                                            AsyncTask.execute(new Runnable() {\n                                                @Override\n                                                public void run() {\n                                                    new BeyondVerbal(getApplicationContext(),\n                                                            recogMic, conditions.getSupportedLanguage(false)).stream();\n                                                }\n                                            });\n                                            break;\n                                        case Condition.CONDITION_IDENTITY:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"MIC: CONDITION_IDENTITY\");\n                                            }\n\n                                            conditions.setFetchingCountdown();\n                                            recogMic = conditions.getMicRecognition(recognitionListener,\n                                                    AudioParameters.getDefaultMicrosoft(), true,\n                                                    SpeakerEnrollment.MINIMUM_AUDIO_TIME, false, true);\n\n                                            AsyncTask.execute(new Runnable() {\n                                                @Override\n                                                public void run() {\n\n                                                    new SpeakerEnrollment(recogMic,\n                                                            conditions.getSupportedLanguage(false),\n                                                            MicrosoftConfiguration.OCP_APIM_KEY_1,\n                                                            conditions.getIdentityProfile(), true).record();\n\n                                                }\n                                            });\n\n                                            break;\n                                        case Condition.CONDITION_IDENTIFY:\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"MIC: CONDITION_IDENTIFY\");\n                                            }\n\n                                            conditions.setFetchingCountdown();\n                                            recogMic = conditions.getMicRecognition(recognitionListener,\n                                                    AudioParameters.getDefaultMicrosoft(), true,\n                                                    SpeakerIdentification.MINIMUM_AUDIO_TIME, false, true);\n\n                                            AsyncTask.execute(new Runnable() {\n                                                @Override\n                                                public void run() {\n\n                                                    new SpeakerIdentification(recogMic,\n                                                            conditions.getSupportedLanguage(false),\n                                                            MicrosoftConfiguration.OCP_APIM_KEY_1,\n                                                            conditions.getIdentityProfile(), true).record();\n                                                }\n                                            });\n                                            break;\n                                    }\n\n                                    break;\n                                case PROCESSING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MIC: PROCESSING\");\n                                    }\n                                    recogMic.stopRecording();\n                                    break;\n                                case LISTENING:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"MIC: LISTENING\");\n                                    }\n                                    recogMic.stopRecording();\n                                    break;\n                            }\n\n                            break;\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"UNKNOWN\");\n                            }\n                            conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                            break;\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"No network connection\");\n                    }\n\n                    if (conditions.servingRemote()) {\n                        conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                    } else {\n                        conditions.showToast(getString(ai.saiy.android.R.string.error_network), Toast.LENGTH_SHORT);\n                    }\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Requesting permission\");\n                }\n                conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n            }\n\n            conditions.removeRunnableCallback(this);\n        }\n\n    };\n\n    /**\n     * Due to misbehaving voice engines, it's often necessary to restart them even if {@link #onInitListener}\n     * has returned successfully. This method handles both eventualities and sends errors on to a\n     * restarting loop.\n     */\n    private void doSpeech(final int speechResult, final int queueType, @NonNull final String utterance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"doSpeech\");\n        }\n\n        switch (speechResult) {\n\n            case SaiyTextToSpeech.SUCCESS:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"doSpeech: SUCCESS\");\n                }\n                resetPendingConditions();\n                break;\n            case SaiyTextToSpeech.ERROR:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"doSpeech: ERROR\");\n                }\n                speechRequestError(queueType, utterance);\n                break;\n        }\n    }\n\n\n    /**\n     * A voice engine may have failed to initialise correctly. Use the {@link PendingTTS} class\n     * to store the temporary data we need on restart.\n     */\n    private void speechRequestError(final int queueType, @NonNull final String utterance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"speechRequestError\");\n        }\n\n        pendingTTS = new PendingTTS();\n        pendingTTS.setPendingUtterance(utterance);\n        pendingTTS.setPendingQueueType(queueType);\n\n        initTTS();\n    }\n\n    /**\n     * The TTS engine has failed to initialise correctly or has falsely reported that it has. Here we\n     * give it a chance to recover, up to a certain amount of times, before toasting an error to the user.\n     */\n    private void initTTS() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"initTTS: pending: \" + initPending.get() + \" ~ count: \" + (initCount + 1));\n        }\n\n        initCount++;\n\n        if (initPending.get()) {\n            return;\n        }\n\n        if (initCount <= MAX_INIT_ATTEMPTS) {\n            initPending.set(true);\n            initSaiyTTS();\n        } else {\n            engineError();\n        }\n    }\n\n    /**\n     * In order to combat lag, initialise the Engine on a background thread. In isolation, the\n     * performance of doing this should make no difference, however:\n     * <p/>\n     * <a href=\"http://stackoverflow.com/q/36013611/1256219\">Initialising the TextToSpeech object on a worker thread/a>\n     */\n    private void initSaiyTTS() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"initSaiyTTS\");\n        }\n\n        new Thread() {\n            public void run() {\n                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);\n                tts = new SaiyTextToSpeech(SelfAware.this, onInitListener);\n            }\n        }.start();\n    }\n\n    /**\n     * The TTS engine has failed to initialise.\n     */\n    private void engineError() {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"engineError\");\n        }\n\n        conditions.showToast(getApplicationContext().getString(ai.saiy.android.R.string.error_tts_initialisation),\n                Toast.LENGTH_LONG);\n        conditions.removeRunnableCallback(engineMonitor);\n        releaseVoiceEngine();\n    }\n\n    /**\n     * The TTS engine recovered from a failed initialisation and we\n     * can reset the {@link PendingTTS} parameters\n     */\n    private void resetPendingConditions() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resetPendingConditions\");\n        }\n\n        pendingTTS = null;\n        initPending.set(false);\n        initCount = 0;\n    }\n\n    /**\n     * Make sure Android knows we are important.\n     */\n    private void startForeground(final int notificationConstant) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startForeground\");\n        }\n\n        final Notification not = NotificationHelper.getForegroundNotification(SelfAware.this, notificationConstant);\n\n        try {\n            startForeground(NotificationService.NOTIFICATION_FOREGROUND, not);\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"beginForeground failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Our {@link OnInitListener} to monitor the tts engine. Can't abstract without\n     * unnecessarily messing with handlers.\n     */\n    private final OnInitListener onInitListener = new OnInitListener() {\n\n        @SuppressWarnings(\"deprecation\")\n        @Override\n        public void onInit(final int status) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onInit\");\n            }\n\n            switch (status) {\n\n                case SaiyTextToSpeech.SUCCESS:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onInit: SUCCESS\");\n                    }\n\n                    if (tts == null) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"onInit: SUCCESS: tts null. Threading error\");\n                        }\n                        return;\n                    }\n\n                    tts.initialised();\n                    tts.setOnUtteranceProgressListener(progressListener);\n                    conditions.setVoice(tts, params);\n\n                    if (pendingTTS != null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onInit: pending speech true\");\n                        }\n\n                        if (!conditions.isSilentUtterance()) {\n                            doSpeech(tts.speak(pendingTTS.getPendingUtterance(),\n                                    pendingTTS.getPendingQueueType(), params, params.getUtteranceId()),\n                                    pendingTTS.getPendingQueueType(), pendingTTS.getPendingUtterance());\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onInit: pending speech true: silence\");\n                            }\n                            conditions.removeRunnableCallback(engineMonitor);\n                            progressListener.onDone(params.getUtteranceId());\n                        }\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onInit: pending speech false\");\n                        }\n\n                        resetPendingConditions();\n                    }\n\n                    break;\n                case SaiyTextToSpeech.ERROR:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onInit: ERROR\");\n                    }\n                    initTTS();\n                    break;\n            }\n        }\n    };\n\n    /**\n     * Our {@link SaiyHotwordListener} to monitor the hotword detection.\n     */\n    private final SaiyHotwordListener hotwordListener = new SaiyHotwordListener() {\n\n        @Override\n        public void onHotwordStarted() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"hotwordListener: onHotwordStarted\");\n            }\n            conditions.acquireWakeLock();\n            conditions.getSaiySoundPool().play(conditions.getSaiySoundPool().getBeepStart());\n            startForeground(NotificationHelper.NOTIFICATION_HOTWORD);\n        }\n\n        @Override\n        public void onHotwordDetected(@NonNull final String hotword) {\n            conditions.vibrate(SelfAwareConditions.VIBRATE_MIN);\n\n            switch (hotword) {\n\n                case SaiyHotwordListener.OKAY_GOOGLE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"hotwordListener: onHotwordDetected: OKAY_GOOGLE\");\n                    }\n\n                    stopListening(false);\n\n                    if (SPH.getOkayGoogleFix(getApplicationContext())) {\n                        releaseRecognition();\n                    }\n\n                    conditions.acquireDisplayWakeLock();\n\n                    new Handler().postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            ExecuteIntent.googleNowListen(getApplicationContext(), conditions.isSecure());\n                            new GoogleNowMonitor().start(getApplicationContext());\n                        }\n                    }, OKAY_GOOGLE_DELAY);\n\n                    break;\n                case SaiyHotwordListener.WAKEUP_SAIY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"hotwordListener: onHotwordDetected: WAKEUP_SAIY\");\n                    }\n\n                    stopListening(false);\n                    conditions.acquireDisplayWakeLock();\n                    final LocalRequest lr = new LocalRequest(getApplicationContext());\n                    lr.prepareIntro();\n                    lr.setSecure(conditions.isSecure());\n                    lr.execute();\n\n                    break;\n                case SaiyHotwordListener.STOP_LISTENING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"hotwordListener: onHotwordDetected: STOP_LISTENING\");\n                    }\n                    stopListening(true);\n                    conditions.getSaiySoundPool().play(conditions.getSaiySoundPool().getBeepStop());\n                    break;\n            }\n        }\n\n        @Override\n        public void onHotwordError(final int errorCode) {\n\n            switch (errorCode) {\n\n                case SaiyHotwordListener.ERROR_NULL:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"hotwordListener: onHotwordError: ERROR_NULL\");\n                    }\n                    break;\n                case SaiyHotwordListener.ERROR_INITIALISE:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"hotwordListener: onHotwordError: ERROR_INITIALISE\");\n                    }\n                    break;\n                case SaiyHotwordListener.ERROR_PERMISSIONS:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"hotwordListener: onHotwordError: ERROR_PERMISSIONS\");\n                    }\n                    break;\n            }\n\n            conditions.showToast(getApplicationContext().getString(R.string.error_hotword),\n                    Toast.LENGTH_LONG);\n        }\n\n        @Override\n        public void onHotwordShutdown() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"hotwordListener: onHotwordShutdown\");\n            }\n\n            conditions.releaseWakeLock();\n            startForeground(NotificationHelper.NOTIFICATION_SELF_AWARE);\n        }\n    };\n\n    /**\n     * Our {@link SaiyRecognitionListener} to monitor the voice recognition.\n     */\n    private final SaiyRecognitionListener recognitionListener = new SaiyRecognitionListener() {\n\n        @Override\n        public void onReadyForSpeech(final Bundle params) {\n            super.onReadyForSpeech(params);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onReadyForSpeech\");\n            }\n\n            conditions.onVRStarted();\n        }\n\n        @Override\n        public void onBeginningOfRecognition() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onBeginningOfRecognition\");\n            }\n        }\n\n        @Override\n        public void onRmsChanged(final float rmsdB) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onRmsChanged\");\n            }\n        }\n\n        @Override\n        public void onEvent(final int eventType, final Bundle params) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onEvent: \" + eventType);\n                MyLog.i(CLS_NAME, \"recognitionListener: onEvent bundle: \" + String.valueOf(params != null && !params.isEmpty()));\n            }\n        }\n\n        @Override\n        public void onBufferReceived(final byte[] buffer) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onBufferReceived\");\n            }\n        }\n\n        @Override\n        public void onEndOfRecognition() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onEndOfRecognition\");\n            }\n\n            conditions.onVREnded();\n        }\n\n        @Override\n        public void onRecognitionError(final int error) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onRecognitionError: \" + error);\n            }\n\n            conditions.onVRError();\n\n            switch (conditions.getDefaultRecognition()) {\n\n                case GOOGLE_CLOUD:\n                    recogGoogleCloud = null;\n                    recogMic = null;\n                    break;\n                case IBM:\n                    recogIBM = null;\n                    recogMic = null;\n                    break;\n                case MICROSOFT:\n                    recogOxford = null;\n                    break;\n            }\n\n            if (conditions.isUserInterrupted()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recognitionListener: onError: isUserInterrupted: true\");\n                }\n                conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"recognitionListener: onError: isUserInterrupted: false\");\n                }\n\n                switch (error) {\n\n                    case SpeechRecognizer.ERROR_AUDIO:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_AUDIO\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                    case SpeechRecognizer.ERROR_CLIENT:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_CLIENT\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                    case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_INSUFFICIENT_PERMISSIONS\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                    case SpeechRecognizer.ERROR_NETWORK:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_NETWORK\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                        break;\n                    case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_NETWORK_TIMEOUT\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                        break;\n                    case SpeechRecognizer.ERROR_NO_MATCH:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_NO_MATCH\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_NO_MATCH, null);\n                        break;\n                    case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_RECOGNIZER_BUSY\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_BUSY, null);\n                        break;\n                    case SpeechRecognizer.ERROR_SERVER:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_SERVER\");\n                        }\n\n                        // TODO - Google install offline files\n\n                        conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                        break;\n                    case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_SPEECH_TIMEOUT\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_NO_MATCH, null);\n                        break;\n                    case JB_TIMEOUT_ERROR:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: JB_TIMEOUT_ERROR\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                    default:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"recognitionListener: onError: ERROR_UNKNOWN\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                }\n\n                conditions.handleRecognitionError(error, conditions.getBundle(), conditions.getDefaultRecognition());\n            }\n\n            releaseRecognition();\n\n            System.gc();\n\n            if (conditions.restartHotword()) {\n                startHotwordDetection(conditions.getBundle());\n            }\n        }\n\n        @Override\n        public void onPartialResults(final Bundle partialResults) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onPartialResults\");\n            }\n\n            if (Recognition.getState() == Recognition.State.LISTENING) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onPartialResults: state == LISTENING\");\n                    SelfAwareVerbose.logSpeechResults(partialResults);\n                }\n\n                final boolean servingRemote = conditions.servingRemote();\n\n                if (!conditions.isCancelled()) {\n                    if (partialHelper == null || partialHelper.isShutdown()) {\n                        partialHelper = new PartialHelper(getApplicationContext(),\n                                conditions.getSupportedLanguage(servingRemote), this);\n                    }\n\n                    partialHelper.isPartial(partialResults);\n                }\n            }\n        }\n\n        @Override\n        public void onCancelDetected() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onCancelDetected\");\n            }\n\n            conditions.setCancelled();\n            new Handler(Looper.getMainLooper()).post(\n                    new Runnable() {\n                        @Override\n                        public void run() {\n                            if (Recognition.getState() == Recognition.State.LISTENING) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onCancelDetected: stopping recognition: true\");\n                                }\n                                stopListening(false);\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onCancelDetected: stopping recognition: false\");\n                                }\n                            }\n                        }\n                    }\n            );\n        }\n\n        @Override\n        public void onTranslateDetected() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onTranslateDetected\");\n            }\n            BingCredentials.refreshTokenIfRequired(getApplicationContext());\n        }\n\n        @Override\n        public void onResults(final Bundle results) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onResults\");\n                SelfAwareVerbose.logSpeechResults(results);\n            }\n\n            conditions.onVRComplete();\n\n            switch (conditions.getDefaultRecognition()) {\n\n                case GOOGLE_CLOUD:\n                    recogGoogleCloud = null;\n                    recogMic = null;\n                    break;\n                case IBM:\n                    recogIBM = null;\n                    recogMic = null;\n                    break;\n                case MICROSOFT:\n                    recogOxford = null;\n                    break;\n            }\n\n            final boolean servingRemote = conditions.servingRemote();\n            conditions.putResults(results);\n\n            switch (conditions.getDefaultLanguageModel(servingRemote)) {\n\n                case API_AI:\n\n                    AsyncTask.execute(new Runnable() {\n\n                        @SuppressWarnings(\"ConstantConditions\")\n                        @Override\n                        public void run() {\n\n                            final Pair<Boolean, String> remoteAPIPair = conditions.getAPIAIRemote(results);\n\n                            if (remoteAPIPair.first) {\n\n                                if (servingRemote) {\n                                    results.putString(Request.RESULTS_NLU, remoteAPIPair.second);\n                                    conditions.manageCallback(CallbackType.CB_RESULTS_RECOGNITION, results);\n                                } else {\n                                    new ResolveAPIAI(getApplicationContext(),\n                                            conditions.getSupportedLanguage(servingRemote),\n                                            conditions.getVRLocale(servingRemote),\n                                            conditions.getTTSLocale(servingRemote),\n                                            results.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES),\n                                            results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION))\n                                            .unpack(remoteAPIPair.second);\n                                }\n                            } else {\n                                if (servingRemote) {\n                                    conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                                } else {\n\n                                    if (!conditions.isCancelled()) {\n                                        new RecognitionAction(getApplicationContext(), conditions.getVRLocale(false),\n                                                conditions.getTTSLocale(false), conditions.getSupportedLanguage(false),\n                                                conditions.getBundle());\n                                    } else {\n                                        final LocalRequest lr = new LocalRequest(getApplicationContext());\n                                        lr.prepareCancelled(conditions.getSupportedLanguage(false), conditions.getVRLocale(false),\n                                                conditions.getTTSLocale(false));\n                                        lr.execute();\n                                    }\n                                }\n                            }\n                        }\n                    });\n\n                    break;\n                default:\n\n                    if (servingRemote) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onResults: serving remote\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_RESULTS_RECOGNITION, results);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onResults: handle internal\");\n                        }\n\n                        if (!conditions.isCancelled()) {\n                            new RecognitionAction(getApplicationContext(), conditions.getVRLocale(false),\n                                    conditions.getTTSLocale(false), conditions.getSupportedLanguage(false),\n                                    conditions.getBundle());\n                        } else {\n                            final LocalRequest lr = new LocalRequest(getApplicationContext());\n                            lr.prepareCancelled(conditions.getSupportedLanguage(false), conditions.getVRLocale(false),\n                                    conditions.getTTSLocale(false));\n                            lr.execute();\n                        }\n                    }\n\n                    break;\n            }\n        }\n\n        @Override\n        public void onComplete() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"recognitionListener: onComplete\");\n            }\n            conditions.onVRComplete();\n        }\n    };\n\n    /**\n     * Our {@link SaiyProgressListener} to monitor the speech. Can't abstract without\n     * unnecessarily messing with handlers and remote callbacks.\n     */\n    private final SaiyProgressListener progressListener = new SaiyProgressListener() {\n\n        private long then;\n\n        @Override\n        public void onError(final String utteranceId, final int errorCode) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"progressListener: onError: errorCode\" + errorCode);\n            }\n            super.onError(utteranceId, errorCode);\n        }\n\n        @Override\n        public void onStop(final String utteranceId, final boolean interrupted) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"progressListener: onStop: interrupted \" + interrupted);\n            }\n\n            if (interrupted) {\n                onDone(utteranceId);\n            }\n\n            super.onStop(utteranceId, interrupted);\n        }\n\n        @Override\n        public void onStart(final String utteranceId) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"progressListener: onStart: \" + utteranceId);\n            }\n\n            then = System.nanoTime();\n\n            if (params.validateOnStart(utteranceId)) {\n                conditions.onTTSStarted();\n                conditions.removeRunnableCallback(engineMonitor);\n            } else {\n                restartStatusMonitor();\n            }\n        }\n\n        @Override\n        public void onError(final String utteranceId) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"progressListener: onError\");\n            }\n\n            if (conditions.isUserInterrupted()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"progressListener: onError: isUserInterrupted\");\n                }\n                onDone(utteranceId);\n            } else {\n\n                conditions.onTTSError();\n                conditions.handleTTSError(conditions.getDefaultTTS(), conditions.getBundle());\n\n                switch (conditions.getDefaultTTS()) {\n\n                    case LOCAL:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"progressListener: onError: SpeechDefault.LOCAL\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                    case NETWORK_NUANCE:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"progressListener: onError: SpeechDefault.NETWORK_NUANCE\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_NETWORK, null);\n                        break;\n                    default:\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"progressListener: onError: SpeechDefault.default\");\n                        }\n                        conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n                        break;\n                }\n            }\n\n            releaseVoiceEngine();\n        }\n\n        @Override\n        public void onDone(final String utteranceId) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"progressListener: onDone\");\n                MyLog.getElapsed(\"onDone\", then);\n            }\n\n            if (conditions.getElapsed(then) < SPEECH_ELAPSED_WARNING\n                    && !conditions.getUtterance().matches(SaiyRequestParams.SILENCE)) {\n                conditions.handleTTSError(conditions.getDefaultTTS(), conditions.getBundle());\n            }\n\n            final Pair<Boolean, String> onDonePair = params.validateOnDone(utteranceId);\n\n            if (onDonePair.first) {\n\n                conditions.onTTSEnded(cache, tts, params);\n\n                switch (Integer.valueOf(onDonePair.second)) {\n\n                    case LocalRequest.ACTION_SPEAK_ONLY:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"progressListener: ACTION_SPEAK_ONLY\");\n                        }\n\n                        conditions.getHandler().post(soRun);\n                        break;\n                    case LocalRequest.ACTION_SPEAK_LISTEN:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"progressListener: ACTION_SPEAK_LISTEN\");\n                        }\n\n                        conditions.getHandler().post(slRun);\n                        break;\n                }\n\n                conditions.manageCallback(CallbackType.CB_UTTERANCE_COMPLETED, null);\n            }\n        }\n    };\n\n    /**\n     * Our {@link PhoneStateListener} to monitor the device radio and handle situations where the\n     * speech or recognition would need to be stopped.\n     */\n    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {\n\n        @Override\n        public void onCallStateChanged(final int state, final String incomingNumber) {\n            super.onCallStateChanged(state, incomingNumber);\n\n            switch (state) {\n\n                case TelephonyManager.CALL_STATE_OFFHOOK:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"PhoneStateListener: TelephonyManager.CALL_STATE_OFFHOOK\");\n                    }\n                    interrupt();\n                    break;\n                case TelephonyManager.CALL_STATE_RINGING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"PhoneStateListener: TelephonyManager.CALL_STATE_RINGING: \" + incomingNumber);\n                    }\n                    interrupt();\n                    break;\n                case TelephonyManager.CALL_STATE_IDLE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"PhoneStateListener: TelephonyManager.CALL_STATE_IDLE\");\n                    }\n\n                    if (conditions.restartHotword()) {\n                        startHotwordDetection(conditions.getBundle());\n                    }\n\n                    conditions.removeInterrupted(params);\n                    break;\n            }\n        }\n    };\n\n    @WorkerThread\n    private boolean waitGoogleCloud() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"waitGoogleCloud\");\n        }\n\n        if (recogMic == null || recogGoogleCloud == null) {\n\n            int sleepCount = 0;\n\n            while (sleepCount < WARM_UP_LOOP) {\n                try {\n                    Thread.sleep(WARM_UP_SLEEP);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (recogMic == null || recogGoogleCloud == null) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"recogMic/Google null: \" + sleepCount);\n                    }\n                    sleepCount++;\n                } else {\n                    break;\n                }\n            }\n        }\n\n        return recogGoogleCloud != null && recogMic != null;\n\n    }\n\n    @WorkerThread\n    private boolean waitIBM() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"waitIBM\");\n        }\n\n        if (recogMic == null || recogIBM == null) {\n\n            int sleepCount = 0;\n\n            while (sleepCount < WARM_UP_LOOP) {\n                try {\n                    Thread.sleep(WARM_UP_SLEEP);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (recogMic == null || recogIBM == null) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"recogIBM null: \" + sleepCount);\n                    }\n                    sleepCount++;\n                } else {\n                    break;\n                }\n            }\n        }\n\n        return recogMic == null && recogIBM != null;\n    }\n\n    @WorkerThread\n    private boolean waitMicrosoft() {\n        if (DEBUG) {\n            MyLog.v(CLS_NAME, \"waitMicrosoft\");\n        }\n\n        if (recogOxford == null) {\n\n            int sleepCount = 0;\n\n            while (sleepCount < WARM_UP_LOOP) {\n                try {\n                    Thread.sleep(WARM_UP_SLEEP);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (recogOxford == null) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"recogOxford null: \" + sleepCount);\n                    }\n                    sleepCount++;\n                } else {\n                    break;\n                }\n            }\n        }\n\n        return recogOxford != null;\n    }\n\n\n    /**\n     * The TTS playback and/or voice recognition needs to be cancelled.\n     */\n    private void interrupt() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"interrupt\");\n        }\n\n        stopListening(false);\n        stopSpeech(true);\n    }\n\n\n    /**\n     * We don't need the voice engine any more. The surrounding try catch blocks may seem like\n     * noob overkill, but based on crash reports they safely handle misbehaving voice engines.\n     */\n    private void releaseVoiceEngine() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"releaseVoiceEngine\");\n        }\n\n        VolumeHelper.abandonAudioMedia(getApplicationContext());\n        NotificationHelper.cancelSpeakingNotification(getApplicationContext());\n        NotificationHelper.cancelFetchingNotification(getApplicationContext());\n        NotificationHelper.cancelInitialisingNotification(getApplicationContext());\n\n        if (tts != null) {\n\n            try {\n                tts.stop();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"releaseVoiceEngine: TTS stop Exception\");\n                    e.getStackTrace();\n                }\n            }\n\n            try {\n                tts.shutdown();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"releaseVoiceEngine: TTS shutdown Exception\");\n                    e.getStackTrace();\n                }\n            }\n\n            try {\n                tts = null;\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"releaseVoiceEngine: TTS to null Exception!!??!!\");\n                    e.printStackTrace();\n                }\n            }\n\n            resetPendingConditions();\n        }\n\n        // hint\n        System.gc();\n    }\n\n    /**\n     * Release the native voice recognition service\n     */\n    private void releaseRecognition() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"releaseRecognition\");\n            MyLog.i(CLS_NAME, \"releaseRecognition: isMain thread: \" + (Looper.myLooper() == Looper.getMainLooper()));\n            MyLog.i(CLS_NAME, \"releaseRecognition: threadTid: \" + android.os.Process.getThreadPriority(android.os.Process.myTid()));\n        }\n\n        if (recogNative != null) {\n\n            try {\n                // recogNative.cancel();\n                recogNative.destroy();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"releaseRecognition: Exception\");\n                    e.printStackTrace();\n                }\n            } finally {\n                recogNative = null;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"releaseRecognition: null\");\n            }\n        }\n    }\n\n    /**\n     * Runnable to monitor the text to speech object so we can check for weird circumstances, such\n     * as it reporting that it is attempting to speak, but doing nothing. After {@link #MONITOR_ENGINE}\n     * amount of time, we'll shut it down and interact with the user.\n     */\n    private final Runnable engineMonitor = new Runnable() {\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"engineMonitor: notifying error\");\n            }\n\n            conditions.showToast(getApplicationContext().getString(ai.saiy.android.R.string.error_tts_initialisation),\n                    Toast.LENGTH_LONG);\n            TTS.setState(TTS.State.IDLE);\n            resetPendingConditions();\n            releaseVoiceEngine();\n            conditions.manageCallback(CallbackType.CB_ERROR_SAIY, null);\n\n            // hint\n            System.gc();\n        }\n    };\n\n    /**\n     * Restart the engine monitor for {@link SelfAwareConditions#getHandler()}\n     */\n    private void restartEngineMonitor() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"restartEngineMonitor\");\n        }\n        conditions.removeRunnableCallback(engineMonitor);\n        conditions.getHandler().postDelayed(engineMonitor, MONITOR_ENGINE);\n    }\n\n    /**\n     * Runnable to reset all global status that could prevent the application from functioning.\n     */\n    private final Runnable statusMonitor = new Runnable() {\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"statusMonitor: resetting conditions\");\n            }\n\n            TTS.setState(TTS.State.IDLE);\n            Recognition.setState(Recognition.State.IDLE);\n            resetPendingConditions();\n            releaseVoiceEngine();\n            releaseRecognition();\n            conditions.removeRunnableCallback(null);\n            releasePartialHelper();\n            System.gc();\n        }\n    };\n\n\n    /**\n     * Restart the status monitor for {@link SelfAwareConditions#getHandler()}\n     */\n    private void restartStatusMonitor() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"restartStatusMonitor\");\n        }\n        conditions.removeRunnableCallback(statusMonitor);\n        conditions.getHandler().postDelayed(statusMonitor, SPH.getInactivityTimeout(getApplicationContext()));\n    }\n\n    /**\n     * Shutdown the partial executor service\n     */\n    private void releasePartialHelper() {\n        if (partialHelper != null && !partialHelper.isShutdown()) {\n            partialHelper.shutdown();\n        }\n    }\n\n    /**\n     * Instantiate a {@link TelephonyManager} instance whilst setting the Class listener\n     *\n     * @return a {@link TelephonyManager} instance\n     */\n    private TelephonyManager getTelephonyManager() {\n\n        if (telephonyManager == null) {\n            telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);\n            telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);\n        }\n\n        return telephonyManager;\n    }\n\n    /**\n     * Set the instance to help ascertain if this service is running\n     */\n    private void setInstance() {\n        SelfAware.instance = this;\n    }\n\n    /**\n     * Destroy the static instance. Called from {@link #tidyUp()}\n     */\n    private void destroyInstance() {\n        SelfAware.instance = null;\n    }\n\n    /**\n     * Housekeeping called from {@link #onDestroy()}\n     */\n    private void tidyUp() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"tidyUp\");\n        }\n\n        VolumeHelper.abandonAudioMedia(getApplicationContext());\n        stopListening(true);\n        stopSpeech(true);\n        releaseVoiceEngine();\n        resetPendingConditions();\n        conditions.killCallbacks();\n        conditions.removeRunnableCallback(null);\n        conditions.getSaiySoundPool().release();\n        TTS.setState(TTS.State.IDLE);\n        Recognition.setState(Recognition.State.IDLE);\n        releasePartialHelper();\n        motionRecognition.destroy();\n        conditions.releaseWakeLock();\n\n        if (telephonyManager != null) {\n            telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);\n        }\n\n        destroyInstance();\n    }\n\n    /**\n     * This hint is generally seen on devices with hardware limitations and so is currently ignored.\n     * <p>\n     * Checking and releasing resources could potentially compound the situation, as such resources\n     * will need to be restarted and the devices that call this regularly take a longer time to initialise,\n     * reducing the user experience.\n     * <p>\n     * The native voice recognition and Text to Speech objects do take up a substantial amount of\n     * memory, but they are already released after a period of {@link SPH#getInactivityTimeout(Context)}.\n     * Changing the behaviour here for the sake of lower-end or poorly performing devices (for a myriad of\n     * unidentifiable reasons) is not currently the way forward.\n     *\n     * @param level a hint to the amount of trimming the application may like to perform.\n     */\n    @Override\n    public void onTrimMemory(final int level) {\n        super.onTrimMemory(level);\n        if (DEBUG) {\n            SelfAwareVerbose.memoryVerbose(level);\n        }\n    }\n\n    /**\n     * See above.\n     */\n    @Override\n    public void onLowMemory() {\n        super.onLowMemory();\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onLowMemory\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n        tidyUp();\n        super.onDestroy();\n        this.stopSelf();\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/ServiceConnector.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.IBinder;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * All requests to the {@link SelfAware} service should be directed here. Locally accessing the service\n * in this way is better for performance that statically using any speech or recognition methods directly.\n * <p/>\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic class ServiceConnector {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ServiceConnector.class.getSimpleName();\n\n    private SelfAware selfAwareService;\n\n    private boolean bound = false;\n\n    private long then;\n\n    private final Context mContext;\n    private final LocalRequest request;\n\n    /**\n     * Constructor.\n     *\n     * @param mContext the application context\n     * @param request  the {@link LocalRequest} parameters\n     */\n    public ServiceConnector(@NonNull final Context mContext, @NonNull final LocalRequest request) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Constructor\");\n        }\n\n        this.mContext = mContext.getApplicationContext();\n        this.request = request;\n\n        SelfAwareHelper.startSelfAwareIfRequired(this.mContext);\n    }\n\n    /**\n     * Create the connection\n     */\n    public void createConnection() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createConnection\");\n        }\n\n        then = System.nanoTime();\n\n        final Intent intent = request.getRequestIntent();\n        doBindService(intent);\n    }\n\n    /**\n     * Bind to the service\n     *\n     * @param intent to send\n     */\n    private void doBindService(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"doBindService\");\n        }\n\n        bound = mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);\n\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"doBindService: bound: \" + bound);\n        }\n    }\n\n    /**\n     * Our {@link ServiceConnection} object\n     */\n    private final ServiceConnection mConnection = new ServiceConnection() {\n\n        @Override\n        public void onServiceConnected(final ComponentName className, final IBinder iBinder) {\n\n            if (iBinder != null) {\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected\");\n                    MyLog.i(CLS_NAME, \"onServiceConnected: CLS: \" + iBinder.getClass().getSimpleName());\n                    MyLog.v(CLS_NAME, \"onServiceConnected: binder alive: \" + iBinder.isBinderAlive());\n                }\n\n                selfAwareService = ((SelfAware.BoundSA) iBinder).getService();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected: TTS Locale: \" + request.getTTSLocale().toString());\n                    MyLog.i(CLS_NAME, \"onServiceConnected: Recognition Locale: \" + request.getVRLocale().toString());\n                }\n\n                final Pair<Boolean, Integer> isSpeakingPair = selfAwareService.isSpeaking();\n                final boolean isListening = selfAwareService.isListening();\n                final Pair<Boolean, Boolean> isHotwordActive = selfAwareService.isHotwordActive();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"isSpeaking: \" + isSpeakingPair.first);\n                    MyLog.i(CLS_NAME, \"isListening:\" + isListening);\n                    MyLog.i(CLS_NAME, \"isHotwordActive:\" + isHotwordActive.first);\n                    MyLog.i(CLS_NAME, \"isHotwordRestartScheduled:\" + isHotwordActive.second);\n                }\n\n                if (request.getAction() == LocalRequest.ACTION_TOGGLE_HOTWORD) {\n\n                    if (isHotwordActive.first || isHotwordActive.second) {\n                        request.setAction(LocalRequest.ACTION_STOP_HOTWORD);\n                        request.setShutdownHotword();\n                    } else {\n                        request.setAction(LocalRequest.ACTION_START_HOTWORD);\n                    }\n                }\n\n                if (isHotwordActive.first && request.getAction() == LocalRequest.ACTION_START_HOTWORD) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"ACTION_START_HOTWORD: already running\");\n                    }\n                } else if (isListening) {\n                    selfAwareService.stopListening(request.getShutdownHotword());\n                } else if (isSpeakingPair.first) {\n\n                    final int currentPriority = isSpeakingPair.second;\n                    final int requestPriority = request.getSpeechPriority();\n\n                    if (requestPriority == currentPriority && request.getQueueType() != TextToSpeech.QUEUE_ADD) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"priorities match - stopping speech\");\n                        }\n                        selfAwareService.stopSpeech(request.shouldPreventRecognition());\n                    } else {\n\n                        if (request.getAction() == LocalRequest.ACTION_UNKNOWN) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"priority mismatch - ACTION_UNKNOWN - stopping speech\");\n                            }\n                            selfAwareService.stopSpeech(request.shouldPreventRecognition());\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"priority mismatch - completing request\");\n                            }\n                            completeRequest();\n                        }\n                    }\n\n                } else {\n                    completeRequest();\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onServiceConnected: iBinder null\");\n                }\n            }\n\n            doUnbindService();\n        }\n\n        @Override\n        public void onServiceDisconnected(final ComponentName className) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onServiceDisconnected\");\n            }\n            selfAwareService = null;\n        }\n    };\n\n    private void completeRequest() {\n\n        switch (request.getAction()) {\n\n            case LocalRequest.ACTION_SPEAK_ONLY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected: ACTION_SPEAK_ONLY\");\n                }\n                selfAwareService.speakOnly(request.getBundle());\n                break;\n            case LocalRequest.ACTION_SPEAK_LISTEN:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected: ACTION_SPEAK_LISTEN\");\n                }\n                selfAwareService.speakListen(request.getBundle());\n                break;\n            case LocalRequest.ACTION_START_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected: ACTION_START_HOTWORD\");\n                }\n                selfAwareService.startHotwordDetection(request.getBundle());\n                break;\n            case LocalRequest.ACTION_STOP_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onServiceConnected: ACTION_STOP_HOTWORD\");\n                }\n                selfAwareService.stopListening(true);\n                break;\n            case LocalRequest.ACTION_UNKNOWN:\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onServiceConnected: ACTION_UNKNOWN\");\n                }\n\n                request.setUtterance(PersonalityResponse.getErrorActionUnknown(mContext,\n                        request.getSupportedLanguage()));\n                selfAwareService.speakOnly(request.getBundle());\n                break;\n        }\n    }\n\n\n    /**\n     * Unbind the service, being careful to handle any unwanted behaviour\n     */\n    private void doUnbindService() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"doUnbindService\");\n        }\n\n        if (bound) {\n            if (selfAwareService != null) {\n                if (mContext != null) {\n                    if (mConnection != null) {\n                        mContext.unbindService(mConnection);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"doUnbindService: mConnection: null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"doUnbindService: mContext: null\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"doUnbindService: selfAwareService: null\");\n                }\n            }\n\n            bound = false;\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"doUnbindService: bound: false\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/AssistantIntentService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.app.IntentService;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.speech.RecognizerIntent;\nimport android.support.annotation.Nullable;\n\nimport java.util.Set;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Class to handle the various available voice interaction intents:\n * <p>\n * {@link RecognizerIntent#ACTION_VOICE_SEARCH_HANDS_FREE}\n * {@link Intent#ACTION_VOICE_COMMAND}\n * {@link Intent#ACTION_SEARCH_LONG_PRESS}\n * {@link Intent#ACTION_VOICE_COMMAND}\n * {@link Intent#ACTION_ASSIST}\n * <p>\n * Created by benrandall76@gmail.com on 15/08/2016.\n */\n\npublic class AssistantIntentService extends IntentService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = AssistantIntentService.class.getSimpleName();\n\n    private final String EXTRA_ASSIST_CONTEXT = \"android.intent.extra.ASSIST_CONTEXT\";\n\n    private long then;\n\n    public AssistantIntentService() {\n        super(AssistantIntentService.class.getSimpleName());\n    }\n\n    @Override\n    public void onCreate() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        then = System.nanoTime();\n        super.onCreate();\n    }\n\n    @Override\n    protected void onHandleIntent(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onHandleIntent\");\n            examineIntent(intent);\n        }\n\n        final Bundle actionBundle = new Bundle();\n        actionBundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_LISTEN);\n\n        if (intent != null) {\n            final Bundle bundle = intent.getExtras();\n            if (bundle != null) {\n\n                final String action = intent.getAction();\n\n                if (UtilsString.notNaked(action)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: action: \" + action);\n                    }\n\n                    if (intent.getAction().equals(Intent.ACTION_ASSIST)) {\n                        if (DEBUG) {\n                            if (bundle.containsKey(EXTRA_ASSIST_CONTEXT)) {\n                                final Bundle assistBundle = bundle.getBundle(EXTRA_ASSIST_CONTEXT);\n\n                                MyLog.i(CLS_NAME, \"onHandleIntent checking assistBundle\");\n                                examineBundle(assistBundle);\n                            }\n                        }\n                    }\n                }\n\n                if (bundle.containsKey(LocalRequest.EXTRA_RECOGNITION_LANGUAGE)) {\n\n                    final String vrLocale = bundle.getString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE);\n\n                    if (UtilsString.notNaked(vrLocale)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_RECOGNITION_LANGUAGE: \" + vrLocale);\n                        }\n                        actionBundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, vrLocale);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_RECOGNITION_LANGUAGE naked\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: no EXTRA_RECOGNITION_LANGUAGE\");\n                    }\n                }\n\n                if (bundle.containsKey(LocalRequest.EXTRA_TTS_LANGUAGE)) {\n\n                    final String ttsLocale = bundle.getString(LocalRequest.EXTRA_TTS_LANGUAGE);\n\n                    if (UtilsString.notNaked(ttsLocale)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_TTS_LANGUAGE: \" + ttsLocale);\n                        }\n                        actionBundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, ttsLocale);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_TTS_LANGUAGE naked\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: no EXTRA_TTS_LANGUAGE\");\n                    }\n                }\n\n                if (bundle.containsKey(LocalRequest.EXTRA_SUPPORTED_LANGUAGE)) {\n\n                    final SupportedLanguage supportedLanguage = (SupportedLanguage) bundle.getSerializable(\n                            LocalRequest.EXTRA_SUPPORTED_LANGUAGE);\n\n                    if (supportedLanguage != null) {\n                        actionBundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, supportedLanguage);\n                        actionBundle.putString(LocalRequest.EXTRA_UTTERANCE,\n                                PersonalityHelper.getIntro(getApplicationContext(), supportedLanguage));\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_SUPPORTED_LANGUAGE null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: no EXTRA_SUPPORTED_LANGUAGE\");\n                    }\n                }\n\n                if (bundle.containsKey(RecognizerIntent.EXTRA_SECURE)) {\n\n                    final Object secureObject = bundle.get(RecognizerIntent.EXTRA_SECURE);\n\n                    if (secureObject != null) {\n\n                        boolean secure = false;\n\n                        if (secureObject instanceof Boolean) {\n                            secure = (boolean) secureObject;\n                        } else if (secureObject instanceof String) {\n                            secure = Boolean.parseBoolean((String) secureObject);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_SECURE of unknown type ignoring\");\n                            }\n                        }\n\n                        if (secure) {\n                            actionBundle.putInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_SECURE);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onHandleIntent: EXTRA_SECURE false ignoring\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onHandleIntent: secureObject null\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onHandleIntent: no EXTRA_SECURE\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onHandleIntent: bundle null ignoring\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onHandleIntent: intent null ignoring\");\n            }\n        }\n\n        if (!actionBundle.containsKey(LocalRequest.EXTRA_RECOGNITION_LANGUAGE)) {\n            actionBundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, SPH.getVRLocale(getApplicationContext()).toString());\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onHandleIntent: actionBundle: auto adding EXTRA_RECOGNITION_LANGUAGE\");\n            }\n        }\n\n        if (!actionBundle.containsKey(LocalRequest.EXTRA_TTS_LANGUAGE)) {\n            actionBundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(getApplicationContext()).toString());\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onHandleIntent: actionBundle: auto adding EXTRA_TTS_LANGUAGE\");\n            }\n        }\n\n        if (!actionBundle.containsKey(LocalRequest.EXTRA_SUPPORTED_LANGUAGE)) {\n            @SuppressWarnings(\"ConstantConditions\") final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(\n                    UtilsLocale.stringToLocale(actionBundle.getString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE)));\n            actionBundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n            actionBundle.putString(LocalRequest.EXTRA_UTTERANCE, PersonalityHelper.getIntro(getApplicationContext(), sl));\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onHandleIntent: actionBundle: auto adding EXTRA_SUPPORTED_LANGUAGE\");\n            }\n        }\n\n        new LocalRequest(getApplicationContext(), actionBundle).execute();\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param intent containing potential extras\n     */\n    private void examineIntent(@Nullable final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineIntent\");\n        }\n\n        if (intent != null) {\n            final Bundle bundle = intent.getExtras();\n            if (bundle != null) {\n                final Set<String> keys = bundle.keySet();\n                for (final String key : keys) {\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"examineIntent: \" + key + \" ~ \" + bundle.get(key));\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param bundle containing potential extras\n     */\n    private void examineBundle(@Nullable final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"examineBundle\");\n        }\n\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            for (final String key : keys) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"examineBundle: \" + key + \" ~ \" + bundle.get(key));\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/IConditionListener.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\n\n/**\n * Interface to notify of haptic feedback, notification and audio adjustments. As with many other\n * classes, this is just to remove clutter from {@link SelfAware}\n * <p/>\n * Created by benrandall76@gmail.com on 14/04/2016.\n */\npublic interface IConditionListener {\n\n    void onTTSStarted();\n\n    void onTTSEnded(@NonNull final SelfAwareCache cache, @NonNull final SaiyTextToSpeech tts,\n                    @NonNull final SelfAwareParameters params);\n\n    void onTTSError();\n\n    void onVRStarted();\n\n    void onVREnded();\n\n    void onVRComplete();\n\n    void onVRError();\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/LocalRequest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.speech.RecognizerIntent;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.recognition.helper.RecognitionDefaults;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.service.ServiceConnector;\nimport ai.saiy.android.tts.helper.SpeechPriority;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Helper Class to prepare our request to the {@link ServiceConnector}\n * <p/>\n * Created by benrandall76@gmail.com on 08/02/2016.\n */\npublic class LocalRequest {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = LocalRequest.class.getSimpleName();\n\n    public static final int ACTION_UNKNOWN = 0;\n    public static final int ACTION_SPEAK_ONLY = 1;\n    public static final int ACTION_SPEAK_LISTEN = 2;\n    public static final int ACTION_START_HOTWORD = 3;\n    public static final int ACTION_STOP_HOTWORD = 4;\n    public static final int ACTION_TOGGLE_HOTWORD = 5;\n\n    public static final String EXTRA_RECOGNITION_LANGUAGE = \"extra_recognition_language\";\n    public static final String EXTRA_TTS_LANGUAGE = \"extra_tts_language\";\n    public static final String EXTRA_UTTERANCE = \"extra_utterance\";\n    public static final String EXTRA_UTTERANCE_ARRAY = \"extra_utterance_array\";\n    public static final String EXTRA_ACTION = \"extra_action\";\n    public static final String EXTRA_COMMAND = \"extra_command\";\n    public static final String EXTRA_CONDITION = \"extra_condition\";\n    public static final String EXTRA_SUPPORTED_LANGUAGE = \"extra_supported_language\";\n    public static final String EXTRA_QUEUE_TYPE = \"extra_queue_type\";\n    public static final String EXTRA_PROFILE_ID = \"extra_profile_id\";\n    public static final String EXTRA_SPEECH_PRIORITY = \"extra_speech_priority\";\n    public static final String EXTRA_PREVENT_RECOGNITION = \"extra_prevent_recognition\";\n\n    private final ServiceConnector sc;\n    private final WeakReference<Context> weakContext;\n    private final Bundle bundle;\n\n    /**\n     * Constructor.\n     *\n     * @param mContext the application context\n     * @param bundle   contains additional parameters\n     */\n    public LocalRequest(@NonNull final Context mContext, @NonNull final Bundle bundle) {\n        this.weakContext = new WeakReference<>(mContext.getApplicationContext());\n        this.bundle = bundle;\n\n        sc = new ServiceConnector(this.weakContext.get(), this);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param mContext the application context\n     */\n    public LocalRequest(@NonNull final Context mContext) {\n        this.weakContext = new WeakReference<>(mContext.getApplicationContext());\n        this.bundle = new Bundle();\n\n        sc = new ServiceConnector(this.weakContext.get(), this);\n    }\n\n    /**\n     * Start the service connection\n     */\n    public void execute() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"execute\");\n        }\n\n        sc.createConnection();\n    }\n\n    /**\n     * Utility method to cut down on boiler plate requests\n     */\n    public void prepareIntro() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareIntro\");\n        }\n\n        final Locale vrLocale = SPH.getVRLocale(weakContext.get());\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(vrLocale);\n\n        bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_LISTEN);\n        bundle.putString(LocalRequest.EXTRA_UTTERANCE, PersonalityHelper.getIntro(weakContext.get(), sl));\n        bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, vrLocale.toString());\n        bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(weakContext.get()).toString());\n        bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n    }\n\n    /**\n     * Utility method to cut down on boiler plate requests\n     *\n     * @param sl        the {@link SupportedLanguage} object\n     * @param vrLocale  the voice recognition {@link Locale}\n     * @param ttsLocale the text to speech {@link Locale}\n     */\n    public void prepareCancelled(@NonNull final SupportedLanguage sl, @NonNull final Locale vrLocale,\n                                 @NonNull final Locale ttsLocale) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareCancelled\");\n        }\n\n        bundle.putInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_SPEAK_ONLY);\n        bundle.putString(LocalRequest.EXTRA_UTTERANCE, PersonalityResponse.getCancelled(weakContext.get(), sl));\n        bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, vrLocale.toString());\n        bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, ttsLocale.toString());\n        bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n    }\n\n    /**\n     * Utility method to cut down on boiler plate requests\n     *\n     * @param action    to perform\n     * @param utterance to speak\n     */\n    public void prepareDefault(final int action, @Nullable final String utterance) {\n\n        final Locale vrLocale = SPH.getVRLocale(weakContext.get());\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(vrLocale);\n\n        bundle.putInt(LocalRequest.EXTRA_ACTION, action);\n        bundle.putString(LocalRequest.EXTRA_UTTERANCE, utterance);\n        bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, vrLocale.toString());\n        bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, SPH.getTTSLocale(weakContext.get()).toString());\n        bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n    }\n\n    /**\n     * Utility method to cut down on boiler plate requests\n     *\n     * @param action    to perform\n     * @param utterance to speak\n     */\n    public void prepareDefault(final int action, @NonNull final SupportedLanguage sl, @NonNull final Locale vrLocale,\n                               @NonNull final Locale ttsLocale, @Nullable final String utterance) {\n\n        bundle.putInt(LocalRequest.EXTRA_ACTION, action);\n        bundle.putString(LocalRequest.EXTRA_UTTERANCE, utterance);\n        bundle.putString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE, vrLocale.toString());\n        bundle.putString(LocalRequest.EXTRA_TTS_LANGUAGE, ttsLocale.toString());\n        bundle.putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n    }\n\n    /**\n     * Get the standard request intent\n     *\n     * @return the standard {@link Intent}\n     */\n    public Intent getRequestIntent() {\n        final Intent intent = new Intent(weakContext.get(), SelfAware.class);\n        intent.setAction(weakContext.get().getPackageName());\n        return intent;\n    }\n\n    /**\n     * Get the required Locale of the Recognition Engine\n     *\n     * @return the required {@link Locale}\n     */\n    public Locale getVRLocale() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_RECOGNITION_LANGUAGE)) {\n                final String language = bundle.getString(EXTRA_RECOGNITION_LANGUAGE);\n                if (UtilsString.notNaked(language)) {\n                    return UtilsLocale.stringToLocale(language);\n                }\n            }\n        }\n\n        return SPH.getVRLocale(weakContext.get());\n    }\n\n    /**\n     * Set the request Voice Recognition language\n     *\n     * @param loc the {@link Locale} of the request\n     */\n    public void setVRLocale(final Locale loc) {\n        bundle.putString(EXTRA_RECOGNITION_LANGUAGE, loc.toString());\n\n    }\n\n    /**\n     * Get the required Locale of the Text to Speech Engine\n     *\n     * @return the required {@link Locale}\n     */\n    public Locale getTTSLocale() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_TTS_LANGUAGE)) {\n                final String language = bundle.getString(EXTRA_TTS_LANGUAGE);\n                if (UtilsString.notNaked(language)) {\n                    return UtilsLocale.stringToLocale(language);\n                }\n            }\n        }\n\n        return SPH.getVRLocale(weakContext.get());\n    }\n\n    /**\n     * Set the request Text to Speech language\n     *\n     * @param loc the {@link Locale} of the request\n     */\n    public void setTTSLocale(final Locale loc) {\n        bundle.putString(EXTRA_TTS_LANGUAGE, loc.toString());\n\n    }\n\n    /**\n     * Set the request action\n     *\n     * @param action one of {@link #ACTION_SPEAK_ONLY} {@link #ACTION_SPEAK_LISTEN}\n     */\n    public void setAction(final int action) {\n        bundle.putInt(EXTRA_ACTION, action);\n    }\n\n    /**\n     * Get the request action\n     *\n     * @return one of {@link #ACTION_SPEAK_ONLY} {@link #ACTION_SPEAK_LISTEN} or the error default\n     * of {@link #ACTION_UNKNOWN}\n     */\n    public int getAction() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_ACTION)) {\n                return bundle.getInt(EXTRA_ACTION, ACTION_UNKNOWN);\n            }\n        }\n\n        return ACTION_UNKNOWN;\n    }\n\n    /**\n     * Set the request condition\n     *\n     * @param condition one of {@link Condition}\n     */\n    public void setCondition(final int condition) {\n        bundle.putInt(EXTRA_CONDITION, condition);\n    }\n\n    /**\n     * Get the request action\n     *\n     * @return one of {@link Condition}\n     */\n    public int getCondition() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_CONDITION)) {\n                return bundle.getInt(EXTRA_CONDITION, Condition.CONDITION_NONE);\n            }\n        }\n\n        return Condition.CONDITION_NONE;\n    }\n\n    /**\n     * Set the resolved command type\n     *\n     * @param command one of {@link CC}\n     */\n    public void setCommand(final CC command) {\n        bundle.putSerializable(EXTRA_COMMAND, command);\n    }\n\n    /**\n     * Get the command type\n     *\n     * @return one of {@link CC} or the unknown default\n     * of {@link CC#COMMAND_UNKNOWN}\n     */\n    public CC getCommand() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_COMMAND)) {\n                return (CC) bundle.getSerializable(EXTRA_COMMAND);\n            }\n        }\n\n        return CC.COMMAND_UNKNOWN;\n    }\n\n    /**\n     * Set the {@link ArrayList<String>} of voice data\n     *\n     * @param voiceData array\n     */\n    public void setUtteranceArray(@NonNull final ArrayList<String> voiceData) {\n        bundle.putStringArrayList(EXTRA_UTTERANCE_ARRAY, voiceData);\n    }\n\n    /**\n     * Get the {@link ArrayList<String>} of voice data\n     *\n     * @return the voice data array or an default empty one\n     */\n    public ArrayList<String> getUtteranceArray() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_UTTERANCE_ARRAY)) {\n                final ArrayList<String> wordsArray = bundle.getStringArrayList(EXTRA_UTTERANCE_ARRAY);\n                if (wordsArray != null) {\n                    return wordsArray;\n                }\n            }\n        }\n\n        return new ArrayList<>();\n    }\n\n    /**\n     * Set the utterance inside the instruction {@link Bundle}\n     *\n     * @param utterance to set\n     */\n    public void setUtterance(@NonNull final String utterance) {\n        bundle.putString(EXTRA_UTTERANCE, utterance);\n    }\n\n    /**\n     * Get the speech utterance from the request\n     *\n     * @return the {@link String} utterance or the default of {@link SaiyRequestParams#SILENCE}\n     */\n    public String getUtterance() {\n        if (bundle != null) {\n            if (bundle.containsKey(EXTRA_UTTERANCE)) {\n                final String utterance = bundle.getString(EXTRA_UTTERANCE);\n                if (UtilsString.notNaked(utterance)) {\n                    return utterance;\n                }\n            }\n        }\n\n        return SaiyRequestParams.SILENCE;\n    }\n\n    /**\n     * Set that the hotword detection should shutdown\n     */\n    public void setShutdownHotword() {\n        bundle.putInt(RecognitionDefaults.HOTWORD_CONDITION, RecognitionDefaults.SHUTDOWN_HOTWORD);\n    }\n\n    /**\n     * Get if the hotword detection should shutdown\n     *\n     * @return true if the hotword detection should be shutdown, false otherwise\n     */\n    public boolean getShutdownHotword() {\n        return bundle != null && (bundle.getInt(RecognitionDefaults.HOTWORD_CONDITION,\n                Condition.CONDITION_NONE) == RecognitionDefaults.SHUTDOWN_HOTWORD);\n\n    }\n\n    /**\n     * Set that the request must be handle in secure mode\n     *\n     * @param secure true if the request should be handled securely, false otherwise\n     */\n    public void setSecure(final boolean secure) {\n        bundle.putBoolean(RecognizerIntent.EXTRA_SECURE, secure);\n    }\n\n    /**\n     * Get if the request has been made in secure mode\n     *\n     * @return true if the request was made in secure mode, false otherwise\n     */\n    public boolean isSecure() {\n        return bundle != null && bundle.getBoolean(RecognizerIntent.EXTRA_SECURE, false);\n    }\n\n    /**\n     * Set the profile id for speaker recognition usage\n     *\n     * @param profileId the id of the enrolled profile\n     */\n    public void setIdentityProfile(@NonNull final String profileId) {\n        bundle.putString(EXTRA_PROFILE_ID, profileId);\n    }\n\n    /**\n     * Get the enrolled profile id\n     *\n     * @return the profile id or an empty string is one is not set\n     */\n    public String getIdentityProfile() {\n        if (bundle != null) {\n            return bundle.getString(EXTRA_PROFILE_ID, \"\");\n        }\n        return \"\";\n    }\n\n    /**\n     * Get the {@link Bundle} of instructions\n     *\n     * @return the {@link Bundle} of instructions\n     */\n    public Bundle getBundle() {\n        return this.bundle;\n    }\n\n    /**\n     * Get the {@link SupportedLanguage}\n     *\n     * @return the {@link SupportedLanguage}\n     */\n    public SupportedLanguage getSupportedLanguage() {\n        if (bundle.containsKey(EXTRA_SUPPORTED_LANGUAGE)) {\n            return (SupportedLanguage) bundle.getSerializable(EXTRA_SUPPORTED_LANGUAGE);\n        }\n\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(getVRLocale());\n        bundle.putSerializable(EXTRA_SUPPORTED_LANGUAGE, sl);\n\n        return sl;\n    }\n\n    public void setSupportedLanguage(@NonNull final SupportedLanguage sl) {\n        bundle.putSerializable(EXTRA_SUPPORTED_LANGUAGE, sl);\n    }\n\n    /**\n     * Set the Text to Speech queue type\n     *\n     * @param queueType one of {@link TextToSpeech#QUEUE_ADD} or {@link TextToSpeech#QUEUE_FLUSH}\n     */\n    public void setQueueType(final int queueType) {\n        bundle.putInt(EXTRA_QUEUE_TYPE, queueType);\n    }\n\n    /**\n     * Get the Text to Speech queue type\n     *\n     * @return one of {@link TextToSpeech#QUEUE_ADD} or {@link TextToSpeech#QUEUE_FLUSH}\n     */\n    public int getQueueType() {\n        return bundle.getInt(EXTRA_QUEUE_TYPE, TextToSpeech.QUEUE_FLUSH);\n    }\n\n    public void setSpeechPriority(final int priority) {\n        bundle.putInt(EXTRA_SPEECH_PRIORITY, priority);\n    }\n\n    public int getSpeechPriority() {\n        return bundle == null ? SpeechPriority.PRIORITY_NORMAL\n                : bundle.getInt(EXTRA_SPEECH_PRIORITY, SpeechPriority.PRIORITY_NORMAL);\n    }\n\n    public void setPreventRecognition() {\n        bundle.putBoolean(EXTRA_PREVENT_RECOGNITION, true);\n    }\n\n    public boolean shouldPreventRecognition() {\n        return bundle != null && bundle.getBoolean(EXTRA_PREVENT_RECOGNITION, false);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareCache.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Process;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Locale;\n\nimport ai.saiy.android.cache.speech.SpeechCachePrepare;\nimport ai.saiy.android.database.DBSpeech;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.tts.helper.SaiyVoice;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsFile;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Utility class to manage the caching of data associated with the\n * {@link SelfAware} Service.\n * <p/>\n * Created by benrandall76@gmail.com on 28/04/2016.\n */\npublic class SelfAwareCache extends SaiyProgressListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SelfAwareCache.class.getSimpleName();\n\n    public static final int MAX_UTTERANCE_CHARS = 150;\n    private static final long DBS_MAINTENANCE_INCREMENT = 40L;\n\n    private final Context mContext;\n\n    private volatile TextToSpeech ttsCache;\n    private volatile SpeechCachePrepare scp;\n    private volatile File tempFile;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public SelfAwareCache(@NonNull final Context mContext) {\n        this.mContext = mContext;\n    }\n\n    /**\n     * Check if the current synthesis should be cached.\n     *\n     * @param params     the {@link SelfAwareParameters}\n     * @param ttsLocale  the {@link TextToSpeech} {@link Locale}\n     * @param utterance  the utterance\n     * @param initEngine the initialised {@link TextToSpeech} engine\n     * @param voice      the {@link SaiyVoice}\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    protected void shouldCache(@NonNull final SelfAwareParameters params, @NonNull final Locale ttsLocale,\n                               @NonNull final String utterance, @NonNull final String initEngine,\n                               @NonNull final SaiyVoice voice) {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);\n\n                if (UtilsString.notNaked(initEngine)) {\n                    if (voice.isNetworkConnectionRequired()) {\n                        if (params.shouldNetwork()) {\n\n                            final DBSpeech dbSpeech = new DBSpeech(mContext);\n                            if (!dbSpeech.entryExists(initEngine, voice.getName(), utterance)) {\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"shouldCache: proceeding\");\n                                }\n\n                                final SpeechCachePrepare scp1 = new SpeechCachePrepare(mContext);\n                                scp1.setVoice(voice);\n                                scp1.setEngine(initEngine);\n                                scp1.setUtterance(utterance);\n                                scp1.setLocale(ttsLocale.toString());\n                                SelfAwareCache.this.doAudioCache(scp1, params);\n\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"shouldCache: entry already exists\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"shouldCache: network not requested\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"shouldCache: not network voice\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"shouldCache: initEngine naked\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Synthesise the audio to a temporary file in the application's internal storage cache, using\n     * a temporary and short-lived {@link TextToSpeech} object. The result of this will report\n     * to {@link SaiyProgressListener#onDone(String)} from where the raw pcm will be stored in {@link DBSpeech}\n     *\n     * @param scp    the populated {@link SpeechCachePrepare}\n     * @param params the {@link SelfAwareParameters}\n     */\n    @SuppressWarnings(\"deprecation\")\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private void doAudioCache(@NonNull final SpeechCachePrepare scp, @NonNull final SelfAwareParameters params) {\n        this.scp = scp;\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);\n\n                ttsCache = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {\n                    @Override\n                    public void onInit(final int status) {\n                        switch (status) {\n                            case TextToSpeech.SUCCESS:\n\n                                ttsCache.setVoice(scp.getVoice());\n                                ttsCache.setOnUtteranceProgressListener(SelfAwareCache.this);\n\n                                tempFile = UtilsFile.getTempAudioFile(mContext);\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"doAudioCache: \" + tempFile.getAbsolutePath());\n                                }\n\n                                ttsCache.synthesizeToFile(scp.getUtterance(), params.getBundle(), tempFile, params.getUtteranceId());\n\n                                break;\n                            case TextToSpeech.ERROR:\n                                break;\n                        }\n                    }\n                }, scp.getEngine());\n            }\n        });\n    }\n\n    @Override\n    public void onAudioAvailable(final String utteranceId, final byte[] audio) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onAudioAvailable\");\n        }\n    }\n\n    @Override\n    public void onBeginSynthesis(final String utteranceId, final int sampleRateInHz, final int audioFormat,\n                                 final int channelCount) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBeginSynthesis\");\n        }\n    }\n\n    @Override\n    public void onStart(final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n    }\n\n    @Override\n    public void onDone(final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDone\");\n        }\n\n        try {\n            scp.setUncompressedAudio(FileUtils.readFileToByteArray(tempFile));\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"onDone: IOException\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            if (tempFile != null) {\n                final boolean success = tempFile.delete();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onDone: tempFile deleted: \" + success);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onDone: tempFile null\");\n                }\n            }\n        }\n\n        shutdownTTS();\n        speechMaintenance();\n    }\n\n    @Override\n    public void onError(final String utteranceId) {\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"onError\");\n        }\n\n        shutdownTTS();\n    }\n\n    /**\n     * Check the {@link DBSpeech} size to see if we need to reduce it.\n     */\n\n    private void speechMaintenance() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"speechMaintenance\");\n        }\n\n        if (SPH.getUsedIncrement(mContext) % DBS_MAINTENANCE_INCREMENT == 0) {\n            new Thread() {\n                public void run() {\n                    Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);\n\n                    final DBSpeech dbSpeech = new DBSpeech(mContext);\n                    if (dbSpeech.shouldRunMaintenance(mContext)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"shouldRunMaintenance: true\");\n                        }\n                        dbSpeech.runMaintenance(mContext);\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"shouldRunMaintenance: false\");\n                        }\n                    }\n                }\n            }.start();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"shouldRunMaintenance: false: \" + SPH.getUsedIncrement(mContext));\n            }\n        }\n    }\n\n    /**\n     * Shutdown the temporary {@link TextToSpeech} object\n     */\n    private void shutdownTTS() {\n        if (ttsCache != null) {\n            try {\n                ttsCache.shutdown();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"shutdownTTS: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareConditions.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.net.Uri;\nimport android.os.Binder;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.PowerManager;\nimport android.os.RemoteCallbackList;\nimport android.os.RemoteException;\nimport android.os.Vibrator;\nimport android.speech.RecognitionListener;\nimport android.speech.RecognitionService;\nimport android.speech.RecognizerIntent;\nimport android.speech.SpeechRecognizer;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.util.Pair;\nimport android.widget.Toast;\n\nimport com.google.auth.oauth2.AccessToken;\nimport com.google.common.util.concurrent.RateLimiter;\nimport com.nuance.speechkit.DetectionType;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.Defaults;\nimport ai.saiy.android.api.RequestParcel;\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.helper.BlackList;\nimport ai.saiy.android.api.helper.BlackListHelper;\nimport ai.saiy.android.api.helper.Callback;\nimport ai.saiy.android.api.helper.CallbackType;\nimport ai.saiy.android.api.helper.Validation;\nimport ai.saiy.android.api.language.nlu.NLULanguageMicrosoft;\nimport ai.saiy.android.api.language.tts.TTSLanguageNuance;\nimport ai.saiy.android.api.language.vr.VRLanguageGoogle;\nimport ai.saiy.android.api.language.vr.VRLanguageIBM;\nimport ai.saiy.android.api.language.vr.VRLanguageMicrosoft;\nimport ai.saiy.android.api.language.vr.VRLanguageNuance;\nimport ai.saiy.android.api.language.vr.VRLanguageWit;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.audio.AudioParameters;\nimport ai.saiy.android.audio.RecognitionMic;\nimport ai.saiy.android.audio.SaiySoundPool;\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.configuration.BluemixConfiguration;\nimport ai.saiy.android.configuration.GoogleConfiguration;\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.configuration.NuanceConfiguration;\nimport ai.saiy.android.configuration.WitConfiguration;\nimport ai.saiy.android.device.UtilsDevice;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.memory.MemoryPrepare;\nimport ai.saiy.android.nlu.apiai.RemoteAPIAI;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.recognition.Recognition;\nimport ai.saiy.android.recognition.SaiyHotwordListener;\nimport ai.saiy.android.recognition.SaiyRecognitionListener;\nimport ai.saiy.android.recognition.helper.RecognitionDefaults;\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.recognition.provider.bluemix.RecognitionBluemix;\nimport ai.saiy.android.recognition.provider.google.chromium.RecognitionGoogleChromium;\nimport ai.saiy.android.recognition.provider.google.cloud.RecognitionGoogleCloud;\nimport ai.saiy.android.recognition.provider.microsoft.RecognitionMicrosoft;\nimport ai.saiy.android.recognition.provider.nuance.RecognitionNuance;\nimport ai.saiy.android.recognition.provider.remote.RecognitionRemote;\nimport ai.saiy.android.recognition.provider.sphinx.RecognitionSphinx;\nimport ai.saiy.android.recognition.provider.wit.RecognitionWit;\nimport ai.saiy.android.service.ISaiyListener;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.sound.VolumeHelper;\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\nimport ai.saiy.android.tts.TTS;\nimport ai.saiy.android.tts.engine.EngineNuance;\nimport ai.saiy.android.tts.helper.SaiyVoice;\nimport ai.saiy.android.tts.helper.SpeechPriority;\nimport ai.saiy.android.tts.helper.TTSDefaults;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.Conditions.Network;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\nimport static ai.saiy.android.applications.Installed.PACKAGE_NAME_GOOGLE;\n\n/**\n * A utility Class that provides methods to {@link SelfAware} mainly to avoid\n * cluttering what is already a very busy Class. It extends a further utility Class {@link SelfAwareHelper}\n * created for the same reason.\n * <p/>\n * There are parameters within this class that are repeatedly checked, and it may make immediate\n * sense to assign these values to local variables - However, persistent variables can cause issues\n * when they are not correctly overwritten for each new request and invalidated upon an error or\n * failure. So to not have to deal with such eventualities, they are not persisted. Performance\n * therefore, may take a minor hit.\n * <p/>\n * Created by benrandall76@gmail.com on 20/03/2016.\n */\npublic class SelfAwareConditions extends SelfAwareHelper implements IConditionListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SelfAwareConditions.class.getSimpleName();\n\n    private static final String WAKELOCK_TAG = SelfAware.class.getSimpleName() + \"Lock\";\n    private static final String WAKELOCK_DISPLAY_TAG = WAKELOCK_TAG + \"Display\";\n\n    private static final double THROTTLE_RATE = 4;\n    private static final int THROTTLE_IGNORE = 4;\n\n    public static final long VIBRATE_MIN = 40L;\n    private static final long FETCHING_DELAY = 1000L;\n    public static final long DEFAULT_INACTIVITY_TIMEOUT = 900000L;\n    private static final long SCREEN_WAKE_TIME = 10000L;\n\n    private final PowerManager.WakeLock wakeLock;\n    private final PowerManager.WakeLock wakeLockDisplay;\n\n    private final Handler handler = new Handler();\n\n    /**\n     * Helper class to manage blacklisted applications\n     */\n    private final BlackListHelper blackListHelper = new BlackListHelper();\n\n    /**\n     * An ArrayList of {@link BlackList} data containing information for remote requests that have\n     * previously been declined due to throttling constraints. This is generated from scratch each\n     * time the Service restarts, to allow the offending application to right its wrongs.\n     */\n    private final ArrayList<BlackList> throttledArray = new ArrayList<>();\n\n    /**\n     * If an external application has misconfigured its request and for example's sake, is making\n     * them in an infinite loop, we need to throttle their ability to make Saiy perform their requests\n     * or become bogged down attempting to handle them.\n     * <p/>\n     * Eventually the remote package will be added to a blacklist array which the user\n     * can choose to manually release in the Saiy Application Settings.\n     * <p/>\n     * The rate limiting is currently applied in all conditions of a remote request, such as checking\n     * the listening or speaking state - In isolation, it would be less expensive to simply\n     * return the state, however, being overcautious in this way will eventually prevent the misbehaving\n     * remote application from binding to the service at all; which is a better outcome.\n     */\n    private final RateLimiter throttle = RateLimiter.create(THROTTLE_RATE);\n\n    /**\n     * This is a list of callbacks that have been registered with the service by remote clients.\n     * We use this list to respond accordingly.\n     */\n    private final RemoteCallbackList<ISaiyListener> remoteCallbacks = new RemoteCallbackList<>();\n\n    private volatile Callback callback;\n    private volatile Bundle bundle;\n    private final Context mContext;\n    private volatile int count;\n    private volatile boolean isCancelled;\n    private volatile boolean restartHotword;\n    private final SaiySoundPool saiySoundPool;\n\n    /**\n     * Constructor\n     * <p/>\n     * When a remote application first binds to {@link SelfAware} it may make a number of initial\n     * requests to check the state of the application (such as speaking or listening). We set the\n     * {@link #count} to zero here and allow it to make {@link #THROTTLE_IGNORE} requests before we\n     * start applying any potential throttling limit on requests.\n     *\n     * @param mContext         the application context\n     * @param telephonyManager the {@link TelephonyManager}\n     */\n    public SelfAwareConditions(@NonNull final Context mContext, @NonNull final TelephonyManager telephonyManager) {\n        super(mContext, telephonyManager);\n        this.mContext = mContext;\n        count = 0;\n\n        final PowerManager manager = (PowerManager) this.mContext.getSystemService(Context.POWER_SERVICE);\n        wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);\n\n        //noinspection deprecation\n        wakeLockDisplay = manager.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,\n                WAKELOCK_DISPLAY_TAG);\n\n        saiySoundPool = new SaiySoundPool().setUp(this.mContext, SaiySoundPool.VOICE_RECOGNITION);\n    }\n\n    /**\n     * Check if the calling remote request can be processed, due to throttling limits defined\n     * by {@link #THROTTLE_RATE} per second.\n     * <p/>\n     * The method also checks if the remote package has already been blacklisted for making repeated\n     * requests that appear to be misconfigured. Additionally, it checks if the current request is\n     * one of a number of requests that will be declined and therefore should be added to the\n     * blacklist.\n     *\n     * @param callingUid the Uid of the remote request\n     * @return true if the remote app is not blacklisted and throttling is within limits.\n     */\n    private boolean grantAcquire(final int callingUid) throws SecurityException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"grantAcquire: \" + callingUid);\n        }\n\n        final String packageName = mContext.getPackageManager().getNameForUid(callingUid);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"grantAcquire: \" + packageName);\n        }\n\n        count++;\n\n        final ArrayList<String> blacklistArray = blackListHelper.fetch(mContext);\n\n        if (UtilsString.notNaked(packageName) && callingUid > 0) {\n            for (final String name : blacklistArray) {\n                if (name.matches(packageName)) {\n                    MyLog.e(CLS_NAME, mContext.getString(R.string.error_package_blacklisted, packageName));\n                    return false;\n                }\n            }\n\n            if (count < THROTTLE_IGNORE || throttle.tryAcquire()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"grantAcquire: granted\");\n                }\n                return true;\n            } else {\n                MyLog.e(CLS_NAME, mContext.getString(R.string.error_throttle_limit));\n\n                final BlackList blackList = new BlackList(packageName, callingUid, System.currentTimeMillis());\n                throttledArray.add(blackList);\n\n                if (BlackList.shouldBlackList(throttledArray)) {\n                    MyLog.e(CLS_NAME, mContext.getString(R.string.error_package_blacklisted, packageName));\n                    blacklistArray.add(blackList.getPackageName());\n                    blackListHelper.save(mContext, blacklistArray);\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"grantAcquire: not blacklisting\");\n                    }\n                }\n\n                return false;\n            }\n        } else {\n            MyLog.e(CLS_NAME, mContext.getString(R.string.error_package_name_null));\n            return false;\n        }\n\n    }\n\n    /**\n     * Check if the calling application has the correct permission to control Saiy. This is a\n     * secondary check which guards private methods.\n     *\n     * @param callingUid of the remote request\n     * @return true if the permission has been granted.\n     */\n    public boolean checkSaiyPermission(final int callingUid) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkSaiyPermission\");\n        }\n        return PermissionHelper.checkSaiyPermission(mContext, callingUid);\n    }\n\n    /**\n     * Check if the calling application has the correct permission to control Saiy.\n     *\n     * @return true if the permission has been granted.\n     */\n    public boolean checkSaiyRemotePermission() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkSaiyRemotePermission\");\n        }\n        return PermissionHelper.checkSaiyRemotePermission(mContext);\n    }\n\n    /**\n     * Check to see if we have the audio permission\n     *\n     * @return true if the permission has been granted\n     */\n    public boolean checkAudioPermission() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkAudioPermission\");\n        }\n        return PermissionHelper.checkAudioPermissions(mContext);\n    }\n\n    /**\n     * Check to see if we have the write files permission\n     *\n     * @return true if the permission has been granted\n     */\n    public boolean checkFilePermission() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"checkFilePermission\");\n        }\n        return PermissionHelper.checkFilePermissions(mContext);\n    }\n\n    /**\n     * Check if the user deliberately requested to stop the speech or recognition. This handles errors\n     * of Text to Speech engines incorrectly throwing {@link android.speech.tts.UtteranceProgressListener#onError(String)}\n     * instead of {@link android.speech.tts.UtteranceProgressListener#onStop(String, boolean)}\n     * <p/>\n     * Additionally, it handles recognition providers throwing {@link RecognitionListener#onError(int)}\n     * instead of {@link RecognitionListener#onResults(Bundle)} regardless of whether a segment of\n     * speech has already been detected and reported.\n     *\n     * @return true if it was a deliberate request\n     */\n    public boolean isUserInterrupted() {\n        return getBundle().containsKey(TTSDefaults.EXTRA_INTERRUPTED);\n    }\n\n    /**\n     * At present, only one remote callback at a time will be allowed to register. This might\n     * seem sensible given the nature of the interaction, but when other features are exposed, this\n     * will need to be rethought see <a href=\"http://stackoverflow.com/q/35734112/1256219\">Managing remote callbacks/a>\n     * <p/>\n     * This method can be used to check if we should register a further callback, or to direct any\n     * results or interaction to a callback, rather than allowing Saiy to handle it internally.\n     *\n     * @return true if a callback is currently registered in the {@link RemoteCallbackList}\n     */\n    public boolean servingRemote() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"servingRemote\");\n        }\n\n        synchronized (remoteCallbacks) {\n\n            int count = remoteCallbacks.beginBroadcast();\n            remoteCallbacks.finishBroadcast();\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"servingRemote: size: \" + count);\n            }\n\n            return count > 0;\n        }\n    }\n\n    /**\n     * Checks all possible parameters from the remote request in overly verbose way. We need to make\n     * sure that if anything is misconfigured, it does not cause Saiy to crash.\n     *\n     * @param rl     the remote request\n     * @param bundle of the remote request\n     * @return true if the remote request should be actioned\n     * @throws RemoteException\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public boolean shouldAction(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"shouldAction\");\n        }\n\n        if (grantAcquire(Binder.getCallingUid())) {\n            if (!servingRemote()) {\n                if (validateRemote(rl) && validateRemoteBundle(rl, bundle)) {\n\n                    final RequestParcel parcel = bundle.getParcelable(RequestParcel.PARCEL_KEY);\n\n                    if (validateRemoteParcel(rl, parcel)) {\n\n                        callback = new Callback(parcel, mContext.getPackageManager().getNameForUid(Binder.getCallingUid()),\n                                Binder.getCallingUid(), System.currentTimeMillis());\n\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"remoteBinder: speakListen: words: \" + callback.getParcel().getUtterance());\n                            MyLog.d(CLS_NAME, \"remoteBinder: speakListen: getRequestId: \" + callback.getParcel().getRequestId());\n                        }\n\n                        return true;\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"remoteBinder: speakListen: parcel validation failed\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"remoteBinder: speakListen: validation failed\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"remoteBinder: speakListen: remote busy\");\n                }\n\n                manageRemoteBusy(rl, bundle);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if the hotword detection is currently active\n     *\n     * @param recogSphinx the {@link RecognitionSphinx} instance\n     * @return true if the hotword is active, false otherwise\n     */\n    public boolean isHotwordActive(@Nullable final RecognitionSphinx recogSphinx) {\n        return isListening() && recogSphinx != null && recogSphinx.isListening();\n    }\n\n    /**\n     * Check if the recognition is currently in use\n     *\n     * @return true if the state is SPEAKING or PROCESSING\n     */\n    public boolean isListening() {\n        final Recognition.State state = Recognition.getState();\n        return (state == Recognition.State.LISTENING\n                || state == Recognition.State.PROCESSING);\n    }\n\n    /**\n     * Check if the Text to Speech engine is currently in use. Try/catch due to misbehaving engines\n     *\n     * @return true if the engine is speaking\n     */\n    public Pair<Boolean, Integer> isSpeaking(final SaiyTextToSpeech tts) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isSpeaking\");\n        }\n\n        boolean speaking = false;\n\n        switch (getDefaultTTS()) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"isSpeaking: LOCAL\");\n                }\n\n                try {\n\n                    if (tts == null) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"isSpeaking: LOCAL null\");\n                        }\n                        speaking = false;\n                    } else if (tts.isSpeaking()) {\n                        speaking = true;\n                    }\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speaking: \" + speaking);\n                    }\n\n                    return new Pair<>(speaking,\n                            getBundle().getInt(LocalRequest.EXTRA_SPEECH_PRIORITY, SpeechPriority.PRIORITY_NORMAL));\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"isSpeaking: NullPointerException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"isSpeaking: Exception\");\n                        e.printStackTrace();\n                    }\n                }\n                break;\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"isSpeaking: NETWORK_NUANCE\");\n                }\n                speaking = (TTS.getState() == TTS.State.SPEAKING);\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"isSpeaking: Default?\");\n                }\n                break;\n        }\n\n        return new Pair<>(speaking,\n                getBundle().getInt(LocalRequest.EXTRA_SPEECH_PRIORITY, SpeechPriority.PRIORITY_NORMAL));\n    }\n\n    /**\n     * Check the speech request priority to see if it overrides the current process\n     *\n     * @param tts           the current bound {@link SaiyTextToSpeech} object\n     * @param requestBundle the {@link Bundle} of the request\n     * @return a pair containing two {@link Pair} with the first parameter of the first pair denoting if the\n     * request should proceed and the second if there is an overriding action. The first parameter of the\n     * second pair denotes if the Text to Speech engine is currently speaking and the second if the\n     * voice recognition is currently in use.\n     */\n    public Pair<Pair<Boolean, Boolean>, Pair<Boolean, Boolean>> proceedPriority(@NonNull final SaiyTextToSpeech tts,\n                                                                                @NonNull final Bundle requestBundle) {\n\n        final Pair<Boolean, Integer> isSpeakingPair = isSpeaking(tts);\n        final boolean isListening = isListening();\n\n        if (isSpeakingPair.first || isListening) {\n\n            final int requestPriority = requestBundle.getInt(LocalRequest.EXTRA_SPEECH_PRIORITY, SpeechPriority.PRIORITY_NORMAL);\n            final int currentPriority = isSpeakingPair.second;\n\n            if (DEBUG) {\n                switch (requestPriority) {\n\n                    case SpeechPriority.PRIORITY_LOW:\n                        MyLog.i(CLS_NAME, \"requestPriority: PRIORITY_LOW\");\n                        break;\n                    case SpeechPriority.PRIORITY_REMOTE:\n                        MyLog.i(CLS_NAME, \"requestPriority: PRIORITY_REMOTE\");\n                        break;\n                    case SpeechPriority.PRIORITY_NORMAL:\n                        MyLog.i(CLS_NAME, \"requestPriority: PRIORITY_NORMAL\");\n                        break;\n                    case SpeechPriority.PRIORITY_MAX:\n                        MyLog.i(CLS_NAME, \"requestPriority: PRIORITY_MAX\");\n                        break;\n                }\n\n                switch (currentPriority) {\n\n                    case SpeechPriority.PRIORITY_LOW:\n                        MyLog.i(CLS_NAME, \"proceedPriority: PRIORITY_LOW\");\n                        break;\n                    case SpeechPriority.PRIORITY_REMOTE:\n                        MyLog.i(CLS_NAME, \"proceedPriority: PRIORITY_REMOTE\");\n                        break;\n                    case SpeechPriority.PRIORITY_NORMAL:\n                        MyLog.i(CLS_NAME, \"proceedPriority: PRIORITY_NORMAL\");\n                        break;\n                    case SpeechPriority.PRIORITY_MAX:\n                        MyLog.i(CLS_NAME, \"proceedPriority: PRIORITY_MAX\");\n                        break;\n                }\n            }\n\n            if (requestPriority < currentPriority) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"requestPriority < currentPriority ignoring\");\n                }\n                return new Pair<>(new Pair<>(false, false), new Pair<>(isSpeakingPair.first, isListening));\n            } else if (requestPriority == currentPriority) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"requestPriority == currentPriority proceeding\");\n                }\n                return new Pair<>(new Pair<>(true, false), new Pair<>(isSpeakingPair.first, isListening));\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"requestPriority > currentPriority overriding\");\n                }\n                return new Pair<>(new Pair<>(true, true), new Pair<>(isSpeakingPair.first, isListening));\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"proceedPriority: proceed as dormant\");\n            }\n        }\n\n        return new Pair<>(new Pair<>(true, false), new Pair<>(false, false));\n    }\n\n    /**\n     * Remove any lingering interrupted parameters\n     *\n     * @param params the {@link SelfAwareParameters}\n     */\n    public void removeInterrupted(@NonNull final SelfAwareParameters params) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"removeInterrupted\");\n        }\n\n        getBundle().remove(TTSDefaults.EXTRA_INTERRUPTED);\n        params.remove(TTSDefaults.EXTRA_INTERRUPTED);\n        params.remove(TTSDefaults.EXTRA_INTERRUPTED_FORCED);\n    }\n\n    /**\n     * Called to stop speech\n     */\n    public void stopSpeech(@Nullable final SaiyTextToSpeech tts, @NonNull final SelfAwareParameters params,\n                           @Nullable final EngineNuance en, final boolean preventRecognition) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopSpeech: preventRecognition: \" + preventRecognition);\n        }\n\n        getBundle().putBoolean(TTSDefaults.EXTRA_INTERRUPTED, true);\n        params.putObject(TTSDefaults.EXTRA_INTERRUPTED, true);\n\n        if (preventRecognition) {\n            params.putObject(TTSDefaults.EXTRA_INTERRUPTED_FORCED, true);\n        }\n\n        final String utteranceId = params.getUtteranceId();\n\n        if (utteranceId.startsWith(SaiyTextToSpeech.ARRAY_FIRST)\n                || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_INTERIM)) {\n            if (preventRecognition) {\n                params.setUtteranceId(String.valueOf(LocalRequest.ACTION_SPEAK_ONLY));\n            } else {\n                params.setUtteranceId(TextUtils.split(utteranceId, SaiyTextToSpeech.ARRAY_DELIMITER)[1]);\n            }\n        }\n\n        switch (getDefaultTTS()) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"stopSpeech: SpeechDefault.LOCAL\");\n                }\n\n                if (tts != null) {\n                    tts.stop();\n                }\n                break;\n            case NETWORK_NUANCE:\n\n                if (en != null) {\n\n                    switch (TTS.getState()) {\n\n                        case SPEAKING:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"stopSpeech: NUANCE: SPEAKING\");\n                            }\n                            en.stopSpeech();\n                            break;\n                        case IDLE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"stopSpeech: NUANCE: IDLE\");\n                            }\n                            break;\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"stopSpeech: Default STATE?\");\n                            }\n                            break;\n                    }\n                    break;\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"stopSpeech: NUANCE null\");\n                    }\n                }\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stopSpeech: Default PROVIDER?\");\n                }\n                break;\n        }\n    }\n\n    /**\n     * Stop the recognition currently in use\n     */\n    public void stopListening(final RecognitionNuance recogNuance, final RecognitionGoogleCloud recogGoogle,\n                              final RecognitionGoogleChromium recogGoogleChromium,\n                              final RecognitionMicrosoft recogOxford, final RecognitionWit recogWit,\n                              final RecognitionBluemix recogIBM, final RecognitionRemote recogRemote,\n                              final RecognitionMic recogMic, final SpeechRecognizer recogNative,\n                              final RecognitionSphinx recogSphinx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopListening\");\n        }\n\n        getBundle().putBoolean(TTSDefaults.EXTRA_INTERRUPTED, true);\n\n        if (recogSphinx != null && recogSphinx.isListening()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"stopListening: hotword\");\n            }\n\n            recogSphinx.stopListening();\n        } else {\n\n            switch (getDefaultRecognition()) {\n\n                case NUANCE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.NUANCE.name());\n                    }\n\n                    if (recogNuance != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogNuance.cancelListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogNuance.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case GOOGLE_CLOUD:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.GOOGLE_CLOUD.name());\n                    }\n\n                    if (recogGoogle != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogGoogle.stopListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogGoogle.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case GOOGLE_CHROMIUM:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.GOOGLE_CHROMIUM.name());\n                    }\n\n                    if (recogGoogleChromium != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogGoogleChromium.stopListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogGoogleChromium.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case MICROSOFT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.MICROSOFT.name());\n                    }\n\n                    if (recogOxford != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogOxford.stopListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogOxford.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case WIT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.WIT.name());\n                    }\n\n                    if (recogWit != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogWit.stopListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogWit.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case IBM:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.IBM.name());\n                    }\n\n                    if (recogIBM != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogIBM.stopListening();\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: SPEAKING\");\n                                }\n                                recogIBM.stopListening();\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n                    break;\n                case NATIVE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.NATIVE.name());\n                    }\n\n                    if (recogNative != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: PROCESSING\");\n                                }\n                                recogNative.cancel();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening: LISTENING\");\n                                }\n                                recogNative.stopListening();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n\n                    break;\n                case REMOTE:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.REMOTE.name());\n                    }\n\n                    if (recogRemote != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening PROCESSING\");\n                                }\n                                recogRemote.cancel();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening LISTENING\");\n                                }\n                                recogRemote.stopListening();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n\n                    break;\n                case MIC:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stopListening: \" + SaiyDefaults.VR.MIC.name());\n                    }\n\n                    if (recogMic != null) {\n\n                        switch (Recognition.getState()) {\n\n                            case PROCESSING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening PROCESSING\");\n                                }\n                                recogMic.stopRecording();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                            case LISTENING:\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"stopListening LISTENING\");\n                                }\n                                recogMic.stopRecording();\n                                Recognition.setState(Recognition.State.IDLE);\n                                break;\n                        }\n                        break;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"stopListening null\");\n                        }\n                    }\n\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"stopListening.default?\");\n                    }\n                    break;\n            }\n        }\n    }\n\n    /**\n     * Get the current {@link Callback}\n     *\n     * @return the current {@link Callback}\n     */\n    private Callback getCallback() {\n        return callback;\n    }\n\n    /**\n     * Disable this callback list.  All registered callbacks are unregistered,\n     * and the list is disabled so that future calls to {@link RemoteCallbackList#register} will\n     * fail.  This should only be used when the Service is stopping, to prevent clients\n     * from registering callbacks after it is stopped.\n     *\n     * @see {@link RemoteCallbackList#register}\n     */\n    public void killCallbacks() {\n        remoteCallbacks.kill();\n    }\n\n    /**\n     * Check that the {@link RequestParcel} is valid, this includes the basic requirements of\n     * a speech string and a request id. If it is not valid, an error is logged and the request ignored.\n     * <p/>\n     * Otherwise, the listener is added to the remoteCallbacks list.\n     *\n     * @param rl     the remote {@link ISaiyListener}\n     * @param parcel the remote {@link RequestParcel}\n     * @return true if the parcel is configured correctly\n     */\n    private boolean validateRemoteParcel(final ISaiyListener rl, final RequestParcel parcel) throws RemoteException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateRemoteParcel\");\n        }\n\n        if (Validation.validateParcel(mContext, parcel)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateRemoteBundle: validateParcel successful\");\n            }\n            if (Validation.validateParams(mContext, parcel)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"validateRemoteBundle: validateParams successful\");\n                }\n\n                addCallback(rl);\n\n                if (DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                    MyLog.v(CLS_NAME, \"validateRemoteBundle: registered clients: \"\n                            + remoteCallbacks.getRegisteredCallbackCount());\n                }\n\n                return true;\n            } else {\n                MyLog.e(\"Remote Saiy Request\", mContext.getString(R.string.error_request_parcel_params_invalid));\n            }\n        } else {\n            MyLog.e(\"Remote Saiy Request\", mContext.getString(R.string.error_request_parcel_invalid));\n        }\n\n        rl.onError(Defaults.ERROR.ERROR_DEVELOPER.name(), Validation.ID_UNKNOWN);\n        return false;\n    }\n\n    /**\n     * Add a callback to the {@link RemoteCallbackList}\n     * <p/>\n     * With the current setup, only one registered callback should be present at a time. Having\n     * previously passed many configuration checks, if there are other callbacks present in the list,\n     * they will be removed, leaving just this request, which will prevent a bottleneck under odd\n     * circumstances.\n     *\n     * @param rl the remote {@link ISaiyListener}\n     */\n    private void addCallback(final ISaiyListener rl) throws RemoteException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"addCallback\");\n        }\n\n        synchronized (remoteCallbacks) {\n\n            int count = remoteCallbacks.beginBroadcast();\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"addCallback: current size: \" + count);\n            }\n\n            if (count > 0) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"addCallback: removing \" + count + \" objects\");\n                }\n                while (count > 0) {\n                    count--;\n                    remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(count));\n                }\n            }\n\n            remoteCallbacks.register(rl);\n            remoteCallbacks.finishBroadcast();\n        }\n    }\n\n    /**\n     * An event has occurred that may require a callback to a remote listener. Check the existence\n     * of such and remove the callback as done if required.\n     *\n     * @param callbackType the {@link CallbackType} to handle\n     * @param results      {@link Bundle} containing the recognition results\n     */\n    public void manageCallback(int callbackType, final Bundle results) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"manageCallback\");\n        }\n\n        synchronized (remoteCallbacks) {\n\n            int count = remoteCallbacks.beginBroadcast();\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"manageCallback: size: \" + count);\n            }\n\n            if (count > 0) {\n\n                try {\n\n                    if (isInterrupted()) {\n                        callbackType = CallbackType.CB_INTERRUPTED;\n                    }\n\n                    switch (callbackType) {\n\n                        case CallbackType.CB_UTTERANCE_COMPLETED:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_UTTERANCE_COMPLETED\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onUtteranceCompleted(\n                                    callback.getParcel().getRequestId());\n\n                            switch (callback.getParcel().getAction()) {\n\n                                case SPEAK_ONLY:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"manageCallback: ACTION.SPEAK_ONLY - removing\");\n                                    }\n                                    remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                                    break;\n                                case SPEAK_LISTEN:\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"manageCallback: ACTION.SPEAK_LISTEN - persisting\");\n                                    }\n                                    break;\n                                default:\n                                    if (DEBUG) {\n                                        MyLog.w(CLS_NAME, \"manageCallback: ACTION.UNKNOWN - removing\");\n                                    }\n                                    remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                                    break;\n                            }\n\n                            break;\n\n                        case CallbackType.CB_RESULTS_RECOGNITION:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_RESULTS_RECOGNITION\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onSpeechResults(results,\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_NO_MATCH:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_NO_MATCH\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_NO_MATCH.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_BUSY:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_BUSY\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_BUSY.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_NETWORK:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_NETWORK\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_NETWORK.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_DENIED:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_DENIED\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_DENIED.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_SAIY:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_GENERIC\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_SAIY.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_INTERRUPTED:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_INTERRUPTED\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_INTERRUPTED.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_ERROR_DEVELOPER:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageCallback: CallbackType.CB_ERROR_DEVELOPER\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_DEVELOPER.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n\n                        case CallbackType.CB_UNKNOWN:\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"manageCallback: CallbackType.CB_UNKNOWN\");\n                            }\n\n                            remoteCallbacks.getBroadcastItem(0).onError(Defaults.ERROR.ERROR_UNKNOWN.name(),\n                                    callback.getParcel().getRequestId());\n                            remoteCallbacks.unregister(remoteCallbacks.getBroadcastItem(0));\n                            break;\n                    }\n                } catch (final RemoteException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"manageCallback: RemoteException\");\n                        e.printStackTrace();\n                    }\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"manageCallback: NullPointerException\");\n                        e.printStackTrace();\n                    }\n                } catch (final IndexOutOfBoundsException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"manageCallback: IndexOutOfBoundsException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"manageCallback: Exception\");\n                        e.printStackTrace();\n                    }\n                } finally {\n                    System.gc();\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"manageCallback: none registered\");\n                }\n            }\n\n            remoteCallbacks.finishBroadcast();\n        }\n    }\n\n    public int checkNotificationInstruction(@Nullable final Intent startCommandIntent) {\n\n        if (startCommandIntent != null) {\n\n            final Bundle startCommandBundle = startCommandIntent.getExtras();\n\n            if (UtilsBundle.notNaked(startCommandBundle)) {\n\n                switch (startCommandBundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n                    case Condition.CONDITION_SELF_AWARE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkNotificationInstruction: CONDITION_SELF_AWARE\");\n                        }\n                        return Condition.CONDITION_SELF_AWARE;\n                    case Condition.CONDITION_NONE:\n                    default:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"checkNotificationInstruction: CONDITION_NONE\");\n                        }\n                        return Condition.CONDITION_NONE;\n                }\n\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"checkNotificationInstruction: startCommandBundle naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"checkNotificationInstruction: startCommandIntent null\");\n            }\n        }\n\n        return Condition.CONDITION_NONE;\n    }\n\n\n    /**\n     * Check if the current recognition session detected the {@link CC#COMMAND_CANCEL}\n     *\n     * @return true if the user requested to cancel\n     */\n    public boolean isCancelled() {\n        return isCancelled;\n    }\n\n    /**\n     * Reset the persistent {@link #isCancelled} at the start of each speech session.\n     */\n    private void resetCancelled() {\n        isCancelled = false;\n    }\n\n    /**\n     * If we detect a {@link CC#COMMAND_CANCEL} in the voice data,\n     * it may have been in the unstable results and therefore not always appear in the actual results.\n     * Making the (possibly incorrect) assumption that the unstable results were correct, we need\n     * to avoid Saiy not responding correctly and so we directly initiate a cancel response, without\n     * using the resource of going via {@link ai.saiy.android.processing.Quantum}\n     */\n    public void setCancelled() {\n        isCancelled = true;\n    }\n\n    /**\n     * Get the default recognition provider, either from the remote request or the user preferences.\n     *\n     * @return the default provider or ordinal equivalent.\n     */\n    public SaiyDefaults.VR getDefaultRecognition() {\n        if (servingRemote()) {\n            return SaiyDefaults.VR.remoteToLocal(callback.getParcel().getProviderVR());\n        } else {\n\n            switch (getBundle().getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n                case Condition.CONDITION_EMOTION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getDefaultRecognition: CONDITION_EMOTION\");\n                    }\n                    return SaiyDefaults.VR.MIC;\n                case Condition.CONDITION_IDENTITY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getDefaultRecognition: CONDITION_IDENTITY\");\n                    }\n                    return SaiyDefaults.VR.MIC;\n                case Condition.CONDITION_IDENTIFY:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getDefaultRecognition: CONDITION_IDENTIFY\");\n                    }\n                    return SaiyDefaults.VR.MIC;\n                case Condition.CONDITION_NONE:\n                default:\n                    break;\n            }\n\n            return SPH.getDefaultRecognition(mContext);\n        }\n    }\n\n    /**\n     * Get the default language model, either from the remote request or the user preferences.\n     *\n     * @return the default language model or ordinal equivalent.\n     */\n    private SaiyDefaults.LanguageModel getDefaultLanguageModel() {\n        if (servingRemote()) {\n            return SaiyDefaults.LanguageModel.remoteToLocal(callback.getParcel().getLanguageModel());\n        } else {\n            return SPH.getDefaultLanguageModel(mContext);\n        }\n    }\n\n    /**\n     * Get the default language model, either from the remote request or the user preferences.\n     *\n     * @param servingRemote if Saiy is currently serving a remote request\n     * @return the default language model or ordinal equivalent.\n     */\n    public SaiyDefaults.LanguageModel getDefaultLanguageModel(final boolean servingRemote) {\n        if (servingRemote) {\n            return SaiyDefaults.LanguageModel.remoteToLocal(callback.getParcel().getLanguageModel());\n        } else {\n            return SPH.getDefaultLanguageModel(mContext);\n        }\n    }\n\n    /**\n     * Get the default TTS provider, either from the remote request or the user preferences.\n     *\n     * @return the default provider or ordinal equivalent.\n     */\n    public SaiyDefaults.TTS getDefaultTTS() {\n        if (servingRemote()) {\n            return SaiyDefaults.TTS.remoteToLocal(callback.getParcel().getProviderTTS());\n        } else {\n            return SPH.getDefaultTTS(mContext);\n        }\n    }\n\n    /**\n     * Check if a network connection of any speed is available to use\n     *\n     * @return true if a connection is available\n     */\n    public boolean isNetworkAvailable() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isNetworkAvailable: \" + Network.isNetworkAvailable(mContext));\n        }\n\n        return Network.isNetworkAvailable(mContext);\n    }\n\n    /**\n     * Called from {@link SelfAware#onStartCommand(Intent, int, int)} we need to decide if the Text\n     * to Speech Engine should be initialised, so to reduce any lag when a request to speak comes in.\n     * <p/>\n     * If this is a local request we'll check the user setting in the shared preferences to see if\n     * they have decided against this.\n     *\n     * @param tts    the {@link SaiyTextToSpeech}\n     * @param intent received in {@link SelfAware#onStartCommand(Intent, int, int)}\n     * @return true if the engine should be initialised.\n     */\n    public boolean shouldWarmUp(final SaiyTextToSpeech tts, final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shouldWarmUp\");\n        }\n\n        if (tts == null) {\n            if (intent != null) {\n\n                final String callingPackage = getPackage(intent);\n\n                if (UtilsString.notNaked(callingPackage) && callingPackage.matches(mContext.getPackageName())) {\n\n                    switch (getDefaultTTS()) {\n\n                        case LOCAL:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onStartCommand: LOCAL\");\n                            }\n                            return true;\n                        case NETWORK_NUANCE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onStartCommand: NETWORK_NUANCE\");\n                            }\n                            break;\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onStartCommand: Not a local request. No warm up.\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onStartCommand: Intent is null. No warm up.\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onStartCommand: TTS bound. No warm up.\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Set the language of the Voice Engine if required\n     *\n     * @param tts the {@link SaiyTextToSpeech} instance\n     */\n    public void setVoice(@NonNull final SaiyTextToSpeech tts, @NonNull final SelfAwareParameters params) {\n\n        final Locale loc;\n\n        if (servingRemote()) {\n\n            switch (callback.getParcel().getProviderTTS()) {\n\n                case LOCAL:\n                    loc = getCallback().getParcel().getTTSLanguageLocal().getLocale();\n                    break;\n                default:\n                    loc = getTTSLocale();\n                    break;\n            }\n        } else {\n            loc = getTTSLocale();\n        }\n\n        tts.setVoice(loc.getLanguage(), loc.getCountry(), this, params);\n    }\n\n    /**\n     * Get the {@link Locale} of the Text to Speech request\n     *\n     * @param servingRemote if Saiy is currently serving a remote request\n     * @return the Text to Speech request language\n     */\n    public Locale getTTSLocale(final boolean servingRemote) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getTTSLocale: serving remote: \" + servingRemote);\n        }\n\n        if (servingRemote) {\n\n            switch (callback.getParcel().getProviderTTS()) {\n\n                case LOCAL:\n                    return getCallback().getParcel().getTTSLanguageLocal().getLocale();\n                case NETWORK_NUANCE:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getTTSLanguageNuance().getLocaleString());\n                default:\n                    return getTTSLocale();\n            }\n        }\n\n        return getTTSLocale();\n\n    }\n\n    /**\n     * Get the {@link Locale} of the Text to Speech request\n     *\n     * @return the Text to Speech request language\n     */\n    private Locale getTTSLocale() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getTTSLocale\");\n        }\n\n        if (getBundle().containsKey(LocalRequest.EXTRA_TTS_LANGUAGE)) {\n\n            final String localeString = getBundle().getString(LocalRequest.EXTRA_TTS_LANGUAGE);\n\n            if (UtilsString.notNaked(localeString)) {\n                return UtilsLocale.stringToLocale(localeString);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getTTSLocale: no value for EXTRA_TTS_LANGUAGE\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getTTSLocale: no extra for EXTRA_TTS_LANGUAGE\");\n            }\n        }\n\n        return SPH.getTTSLocale(mContext);\n    }\n\n    /**\n     * Add the recognition results to the existing {@link Bundle}\n     *\n     * @param results recognition results {@link Bundle}\n     */\n    public void putResults(@NonNull final Bundle results) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"putResults\");\n        }\n\n        final ArrayList<String> heardVoice = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n        final float[] confidence = results.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES);\n\n        if (heardVoice != null) {\n            getBundle().putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, heardVoice);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"putResults: speech empty\");\n            }\n            getBundle().putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, new ArrayList<String>());\n        }\n\n        if (confidence != null) {\n            getBundle().putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, confidence);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"putResults: confidence empty\");\n            }\n            getBundle().putFloatArray(SpeechRecognizer.CONFIDENCE_SCORES, new float[0]);\n        }\n    }\n\n    /**\n     * Get the Text to Speech queue type\n     *\n     * @return one of {@link TextToSpeech#QUEUE_ADD} or {@link TextToSpeech#QUEUE_FLUSH}\n     */\n    public int getQueueType() {\n        return getBundle().getInt(LocalRequest.EXTRA_QUEUE_TYPE, TextToSpeech.QUEUE_FLUSH);\n    }\n\n    /**\n     * Check if the queue type is {@link TextToSpeech#QUEUE_ADD}\n     *\n     * @param bundle the parameters\n     * @return true if the queue type is {@link TextToSpeech#QUEUE_ADD}. False otherwise\n     */\n    public boolean isQueueAdd(final Bundle bundle) {\n        return bundle != null && bundle.getInt(LocalRequest.EXTRA_QUEUE_TYPE,\n                TextToSpeech.QUEUE_FLUSH) == TextToSpeech.QUEUE_ADD;\n    }\n\n    /**\n     * Get the {@link Locale} of the Voice Recognition request\n     *\n     * @return the Voice Recognition request language\n     */\n    public Locale getVRLocale(final boolean servingRemote) {\n\n        if (servingRemote) {\n\n            switch (callback.getParcel().getProviderVR()) {\n\n                case GOOGLE_CHROMIUM:\n                case GOOGLE_CLOUD:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getVRLanguageGoogle().getLocaleString());\n                case NUANCE:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getVRLanguageNuance().getLocaleString());\n                case MICROSOFT:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getVRLanguageMicrosoft().getLocaleString());\n                case REMOTE:\n                    return getCallback().getParcel().getVRLanguageRemote().getLocale();\n                case NATIVE:\n                    return getCallback().getParcel().getVRLanguageNative().getLocale();\n                case IBM:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getVRLanguageIBM().getLocaleString());\n                case WIT:\n                    return UtilsLocale.stringToLocale(getCallback().getParcel().getVRLanguageWit().getLocaleString());\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"getVRLocale: unknown VRProvider\");\n                    }\n                    return getVRLocale();\n            }\n        }\n\n        return getVRLocale();\n    }\n\n    /**\n     * Get the {@link Locale} of the Voice Recognition request\n     *\n     * @return the Voice Recognition request language\n     */\n    private Locale getVRLocale() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getVRLocale\");\n        }\n\n        if (getBundle().containsKey(LocalRequest.EXTRA_RECOGNITION_LANGUAGE)) {\n\n            final String localeString = getBundle().getString(LocalRequest.EXTRA_RECOGNITION_LANGUAGE);\n\n            if (UtilsString.notNaked(localeString)) {\n                return UtilsLocale.stringToLocale(localeString);\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getVRLocale: no value for EXTRA_RECOGNITION_LANGUAGE\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getVRLocale: no extra for EXTRA_RECOGNITION_LANGUAGE\");\n            }\n        }\n\n        return SPH.getVRLocale(mContext);\n    }\n\n    /**\n     * Get the supported language of the {@link Locale} which will enable us to apply any resource\n     * localisation when resolving a command.\n     *\n     * @param servingRemote true if the request is remote\n     * @return the {@link SupportedLanguage}\n     */\n    public SupportedLanguage getSupportedLanguage(final boolean servingRemote) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSupportedLanguage\");\n        }\n\n        SupportedLanguage sl = (SupportedLanguage) getBundle().getSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE);\n\n        if (sl != null) {\n            return sl;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getSupportedLanguage: adding to bundle\");\n            }\n\n            sl = SupportedLanguage.getSupportedLanguage(getVRLocale(servingRemote));\n            getBundle().putSerializable(LocalRequest.EXTRA_SUPPORTED_LANGUAGE, sl);\n            return sl;\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link EngineNuance} instance\n     *\n     * @param params           the {@link SelfAwareParameters}\n     * @param progressListener the {@link SaiyProgressListener}\n     * @return the {@link EngineNuance} instance\n     */\n    public EngineNuance getEngineNuance(@NonNull final SelfAwareParameters params,\n                                        @NonNull final SaiyProgressListener progressListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getEngineNuance\");\n        }\n\n        if (servingRemote()) {\n            return new EngineNuance(mContext, progressListener,\n                    getCallback().getParcel().getTTSLanguageNuance().getLocaleString(),\n                    getCallback().getParcel().getTTSLanguageNuance().getName(),\n                    params.getUtteranceId(), getCallback().getParcel().getNUANCE_SERVER_URI(),\n                    getCallback().getParcel().getNUANCE_APP_KEY());\n        } else {\n\n            final TTSLanguageNuance ttsLanguageNuance = TTSLanguageNuance.getVoice(getTTSLocale(),\n                    SPH.getDefaultTTSGender(mContext).getRemoteGender());\n\n            return new EngineNuance(mContext, progressListener,\n                    ttsLanguageNuance.getLocaleString(),\n                    ttsLanguageNuance.getName(),\n                    params.getUtteranceId(), getNuanceUri(true),\n                    NuanceConfiguration.APP_KEY);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionNuance} instance\n     *\n     * @param recognitionListener the {@link RecognitionListener}\n     * @return the {@link RecognitionNuance} instance\n     */\n    public RecognitionNuance getNuanceRecognition(@NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getNuanceRecognition\");\n        }\n\n        if (servingRemote()) {\n            return new RecognitionNuance(mContext, recognitionListener,\n                    DetectionType.Long,\n                    getCallback().getParcel().getNUANCE_SERVER_URI(),\n                    getCallback().getParcel().getNUANCE_SERVER_URI_NLU(),\n                    getCallback().getParcel().getNUANCE_APP_KEY(),\n                    getCallback().getParcel().getNUANCE_CONTEXT_TAG(), true,\n                    SaiyDefaults.LanguageModel.remoteToLocal(getCallback().getParcel().getLanguageModel()),\n                    getTTSLocale(true),\n                    getCallback().getParcel().getVRLanguageNuance(),\n                    getSupportedLanguage(true));\n        } else {\n            return new RecognitionNuance(mContext, recognitionListener,\n                    DetectionType.Long, getNuanceUri(false),\n                    getNuanceUri(false),\n                    NuanceConfiguration.APP_KEY, NuanceConfiguration.CONTEXT_TAG,\n                    false, getDefaultLanguageModel(), getTTSLocale(),\n                    VRLanguageNuance.getLanguage(getVRLocale()),\n                    getSupportedLanguage(false));\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionGoogleCloud} instance\n     *\n     * @param recognitionListener the {@link RecognitionListener}\n     * @return the {@link RecognitionGoogleCloud} instance\n     */\n    public RecognitionGoogleCloud getGoogleCloudRecognition(@NonNull final RecognitionMic recogMic,\n                                                            @NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getGoogleCloudRecognition\");\n        }\n\n        if (servingRemote()) {\n\n            return new RecognitionGoogleCloud(mContext, recognitionListener,\n                    getCallback().getParcel().getVRLanguageGoogle(),\n                    new AccessToken(getCallback().getParcel().getGOOGLE_CLOUD_ACCESS_TOKEN(),\n                            new Date(System.currentTimeMillis()\n                                    + getCallback().getParcel().getGOOGLE_CLOUD_ACCESS_EXPIRY())),\n                    recogMic);\n        } else {\n            return new RecognitionGoogleCloud(mContext, recognitionListener,\n                    VRLanguageGoogle.getLanguage(getVRLocale()), GoogleConfiguration.ACCESS_TOKEN, recogMic);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionGoogleChromium} instance\n     *\n     * @param recognitionListener the {@link RecognitionListener}\n     * @return the {@link RecognitionGoogleChromium} instance\n     */\n    public RecognitionGoogleChromium getGoogleChromiumRecognition(@NonNull final SaiyRecognitionListener\n                                                                          recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getGoogleChromiumRecognition\");\n        }\n\n        if (servingRemote()) {\n\n            return new RecognitionGoogleChromium(recognitionListener,\n                    getCallback().getParcel().getVRLanguageGoogle(),\n                    getCallback().getParcel().getGOOGLE_CHROMIUM_API_KEY(), true, saiySoundPool);\n\n        } else {\n\n            return new RecognitionGoogleChromium(recognitionListener,\n                    VRLanguageGoogle.getLanguage(getVRLocale()),\n                    GoogleConfiguration.GOOGLE_SPEECH_API_KEY, true, saiySoundPool);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionMicrosoft} instance\n     *\n     * @param recognitionListener the {@link RecognitionListener}\n     * @return the {@link RecognitionMicrosoft} instance\n     */\n    public RecognitionMicrosoft getMicrosoftRecognition(@NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMicrosoftRecognition\");\n        }\n\n        if (servingRemote()) {\n            return new RecognitionMicrosoft(mContext, recognitionListener,\n                    getCallback().getParcel().getOXFORD_KEY_1(),\n                    getCallback().getParcel().getOXFORD_KEY_2(),\n                    getCallback().getParcel().getLUIS_APP_ID(),\n                    getCallback().getParcel().getLUIS_SUBSCRIPTION_ID(),\n                    SaiyDefaults.LanguageModel.remoteToLocal(getCallback().getParcel().getLanguageModel()),\n                    getTTSLocale(true),\n                    getCallback().getParcel().getVRLanguageMicrosoft(),\n                    getCallback().getParcel().getNLULanguageMicrosoft(),\n                    getSupportedLanguage(true), true, saiySoundPool);\n        } else {\n            return new RecognitionMicrosoft(mContext, recognitionListener,\n                    MicrosoftConfiguration.OXFORD_KEY_1,\n                    MicrosoftConfiguration.OXFORD_KEY_2, MicrosoftConfiguration.LUIS_APP_ID,\n                    MicrosoftConfiguration.LUIS_SUBSCRIPTION_ID, getDefaultLanguageModel(),\n                    getTTSLocale(),\n                    VRLanguageMicrosoft.getLanguage(getVRLocale()),\n                    NLULanguageMicrosoft.getLanguage(getVRLocale()),\n                    getSupportedLanguage(false), false, saiySoundPool);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RemoteAPIAI} instance\n     *\n     * @return the {@link Pair} with the first parameter denoting success and the second the JSON response\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public Pair<Boolean, String> getAPIAIRemote(@NonNull final Bundle results) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAPIAIRemote\");\n        }\n\n        return new RemoteAPIAI(mContext, results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION).get(0),\n                getCallback().getParcel().getAPI_AI_CLIENT_ACCESS_TOKEN(),\n                getCallback().getParcel().getNLULanguageAPIAI()).fetch();\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionWit} instance\n     *\n     * @param recognitionListener the {@link SaiyRecognitionListener}\n     * @return the {@link RecognitionWit} instance\n     */\n    public RecognitionWit getWitRecognition(@NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getWitRecognition\");\n        }\n\n        if (servingRemote()) {\n            return new RecognitionWit(mContext, recognitionListener,\n                    getCallback().getParcel().getWIT_SERVER_ACCESS_TOKEN(),\n                    SaiyDefaults.LanguageModel.remoteToLocal(getCallback().getParcel().getLanguageModel()),\n                    getTTSLocale(true),\n                    getCallback().getParcel().getVRLanguageWit(),\n                    getSupportedLanguage(true), true, saiySoundPool);\n        } else {\n            return new RecognitionWit(mContext, recognitionListener,\n                    WitConfiguration.WIT_ACCESS_TOKEN,\n                    getDefaultLanguageModel(),\n                    getTTSLocale(),\n                    VRLanguageWit.getLanguage(getVRLocale()),\n                    getSupportedLanguage(false), false, saiySoundPool);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionBluemix} instance\n     *\n     * @param recogMic            the {@link RecognitionMic}\n     * @param recognitionListener the {@link SaiyRecognitionListener}\n     * @return the {@link RecognitionBluemix} instance\n     */\n    public RecognitionBluemix getIBMRecognition(@NonNull final RecognitionMic recogMic,\n                                                @NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getIBMRecognition\");\n        }\n\n        if (servingRemote()) {\n            return new RecognitionBluemix(recognitionListener,\n                    getCallback().getParcel().getIBM_SERVICE_USER_NAME(),\n                    getCallback().getParcel().getIBM_SERVICE_PASSWORD(),\n                    SaiyDefaults.LanguageModel.remoteToLocal(getCallback().getParcel().getLanguageModel()),\n                    getTTSLocale(true),\n                    getCallback().getParcel().getVRLanguageIBM(),\n                    getSupportedLanguage(true), true, recogMic);\n        } else {\n            return new RecognitionBluemix(recognitionListener,\n                    BluemixConfiguration.BLUEMIX_USERNAME,\n                    BluemixConfiguration.BLUEMIX_PASSWORD,\n                    getDefaultLanguageModel(), getTTSLocale(),\n                    VRLanguageIBM.getLanguage(getVRLocale()),\n                    getSupportedLanguage(false), false, recogMic);\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionRemote} instance\n     *\n     * @param recognitionListener the {@link SaiyRecognitionListener}\n     * @return the {@link RecognitionRemote} instance\n     */\n    public RecognitionRemote getRemoteRecognition(@NonNull final SaiyRecognitionListener recognitionListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRemoteRecognition\");\n        }\n\n        if (servingRemote()) {\n            return new RecognitionRemote(recognitionListener, getVRLocale().toString(),\n                    getCallback().getParcel().getREMOTE_SERVER_URI(),\n                    getCallback().getParcel().getREMOTE_ACCESS_TOKEN(), saiySoundPool);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionSphinx} instance\n     *\n     * @param listener the {@link SaiyHotwordListener}\n     * @return the {@link RecognitionSphinx} instance\n     */\n    public RecognitionSphinx getSphinxRecognition(@NonNull final SaiyHotwordListener listener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSphinxRecognition\");\n        }\n        return new RecognitionSphinx(mContext, listener, getSupportedLanguage(false));\n    }\n\n    /**\n     * Utility method to construct the {@link RecognitionMic} instance\n     *\n     * @param recognitionListener the {@link SaiyRecognitionListener}\n     * @param audioParameters     the defined {@link AudioParameters}\n     * @param pauseDetection      where or not to use pause detection\n     * @param pauseIgnoreTime     the delay until the pause detection will begin\n     * @param enhance             whether or not to use the Android mic enhancements\n     * @param writeToFile         where or not to write the audio to a file\n     * @return he {@link RecognitionMic} instance\n     */\n    public RecognitionMic getMicRecognition(@Nullable final SaiyRecognitionListener recognitionListener,\n                                            @NonNull final AudioParameters audioParameters,\n                                            final boolean pauseDetection, final long pauseIgnoreTime,\n                                            final boolean enhance, final boolean writeToFile) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMicRecognition\");\n        }\n\n        return new RecognitionMic(mContext, recognitionListener, audioParameters, pauseDetection, pauseIgnoreTime,\n                enhance, writeToFile, saiySoundPool);\n    }\n\n    /**\n     * Utility method to construct the {@link SpeechRecognizer} instance\n     *\n     * @param recognitionListener the {@link SaiyRecognitionListener}\n     * @return the {@link Pair} containing the {@link SpeechRecognizer} and Intent with extras\n     */\n    public Pair<SpeechRecognizer, Intent> getNativeRecognition(\n            @NonNull final SaiyRecognitionListener recognitionListener) {\n\n        final long then = System.nanoTime();\n\n        SpeechRecognizer recognizer = null;\n\n        final List<ResolveInfo> recognitionServices = mContext.getPackageManager().queryIntentServices(\n                new Intent(RecognitionService.SERVICE_INTERFACE), 0);\n\n        if (UtilsList.notNaked(recognitionServices)) {\n\n            String packageName;\n            String serviceName;\n            ServiceInfo serviceInfo;\n\n            for (final ResolveInfo info : recognitionServices) {\n\n                serviceInfo = info.serviceInfo;\n                packageName = serviceInfo.packageName;\n                serviceName = serviceInfo.name;\n\n                if (packageName != null && serviceName != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getNativeRecognition: Recognizer: \" + packageName + \" : \" + serviceName);\n                    }\n\n                    if (packageName.startsWith(PACKAGE_NAME_GOOGLE)) {\n                        recognizer = SpeechRecognizer.createSpeechRecognizer(mContext,\n                                new ComponentName(packageName, serviceName));\n                        break;\n                    }\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getNativeRecognition: recognitionServices: naked\");\n            }\n\n            return null;\n        }\n\n        if (recognizer == null) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getNativeRecognition: recognizer: null\");\n            }\n\n            return null;\n        }\n\n        recognizer.setRecognitionListener(recognitionListener);\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, \"getNativeRecognition\", then);\n        }\n\n        return new Pair<>(recognizer, getNativeIntent());\n    }\n\n    /**\n     * Add the intent extras\n     *\n     * @return the {@link Intent} with the required extras added\n     */\n    public Intent getNativeIntent() {\n\n        final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);\n        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);\n        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, mContext.getPackageName());\n        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);\n        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, mContext.getString(R.string.app_name));\n        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, RecognitionNative.MAX_RESULTS);\n        intent.putExtra(RecognitionDefaults.PREFER_OFFLINE, SPH.getUseOffline(mContext));\n        intent.putExtra(RecognitionDefaults.EXTRA_SECURE,\n                (getBundle().getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE) == Condition.CONDITION_SECURE));\n\n        final Long timeout = SPH.getPauseTimeout(mContext);\n        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, timeout);\n        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, timeout);\n        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, timeout);\n\n        if (servingRemote()) {\n            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, getCallback().getParcel()\n                    .getVRLanguageGoogle().getLocaleString());\n        } else {\n            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, getVRLocale().toString());\n        }\n\n        return intent;\n    }\n\n    /**\n     * Get the enrolled profile id\n     *\n     * @return the profile id or an empty string is one is not set\n     */\n    public String getIdentityProfile() {\n        return getBundle().getString(LocalRequest.EXTRA_PROFILE_ID, \"\");\n    }\n\n    /**\n     * Get the utterance to speak, either from the remote request or the {@link Bundle} of instructions\n     *\n     * @return the utterance to speak\n     */\n    public String getUtterance() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getInput\");\n        }\n\n        if (servingRemote()) {\n            return getCallback().getParcel().getUtterance();\n        } else {\n            return getBundle().getString(LocalRequest.EXTRA_UTTERANCE, SaiyRequestParams.SILENCE);\n        }\n    }\n\n    /**\n     * Set whether the hotword detection should be shutdown\n     *\n     * @param shutdown true if the hotword should be permanently shutdown, false otherwise\n     */\n    public void setHotwordShutdown(@Nullable final RecognitionSphinx recogSphinx, final boolean shutdown) {\n        restartHotword = !shutdown && (restartHotword || (recogSphinx != null && recogSphinx.isListening()));\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setHotwordShutdown: restartHotword \" + restartHotword);\n        }\n\n        releaseWakeLock();\n    }\n\n    /**\n     * Check if the hotword detection should restart\n     *\n     * @return true if the detection should restart, false otherwise\n     */\n    public boolean restartHotword() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"restartHotword: \" + restartHotword);\n        }\n        return restartHotword;\n    }\n\n    /**\n     * Check if no utterance is required\n     *\n     * @return if the utterance String matches {@link SaiyRequestParams#SILENCE}\n     */\n    public boolean isSilentUtterance() {\n        if (servingRemote()) {\n            return getCallback().getParcel().getUtterance().equalsIgnoreCase(SaiyRequestParams.SILENCE);\n        } else {\n            return getBundle().getString(LocalRequest.EXTRA_UTTERANCE, SaiyRequestParams.SILENCE)\n                    .equalsIgnoreCase(SaiyRequestParams.SILENCE);\n        }\n    }\n\n    /**\n     * Check if this was a deliberately secure request\n     *\n     * @return true if the request was secure or the device is in secure mode, false otherwise\n     */\n    public boolean isSecure() {\n        return getBundle().getBoolean(RecognizerIntent.EXTRA_SECURE, false)\n                || UtilsDevice.isDeviceLocked(mContext);\n    }\n\n    /**\n     * Set if this request should be handled securely\n     */\n    private void setSecure() {\n        getBundle().putBoolean(RecognizerIntent.EXTRA_SECURE, isSecure());\n    }\n\n    /**\n     * Get the instruction {@link Bundle}\n     *\n     * @return the instruction {@link Bundle}\n     */\n    public Bundle getBundle() {\n        if (this.bundle != null) {\n            return this.bundle;\n        }\n\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"getBundle: null\");\n        }\n\n        return new Bundle();\n    }\n\n    /**\n     * Helper method to set the instruction bundle and pass the parameter on to\n     * {@link SelfAwareHelper#checkTTSConditions(SaiyTextToSpeech, SaiyDefaults.TTS)} for further inspection.\n     *\n     * @param tts        the {@link SaiyTextToSpeech} object\n     * @param defaultTTS the default provider\n     * @param bundle     of instructions\n     * @return true if all conditions are met.\n     */\n    public boolean checkConditions(final SaiyTextToSpeech tts, final SaiyDefaults.TTS defaultTTS, final Bundle bundle) {\n        this.bundle = bundle;\n        setSecure();\n        handleMemory();\n        return checkTTSConditions(tts, defaultTTS);\n    }\n\n    public int getCondition() {\n        return getBundle().getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE);\n    }\n\n    /**\n     * Check to see if we need to remember this command\n     */\n    private void handleMemory() {\n        if (!servingRemote()\n                && getBundle().getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)\n                != Condition.CONDITION_IGNORE\n                && getBundle().getInt(LocalRequest.EXTRA_ACTION, LocalRequest.ACTION_UNKNOWN)\n                == LocalRequest.ACTION_SPEAK_ONLY\n                && getBundle().getSerializable(LocalRequest.EXTRA_COMMAND) != null) {\n            new MemoryPrepare(mContext, getBundle()).save();\n        }\n    }\n\n    @Override\n    public boolean checkTTSConditions(final SaiyTextToSpeech tts, final SaiyDefaults.TTS defaultTTS) {\n        return super.checkTTSConditions(tts, defaultTTS);\n    }\n\n    @Override\n    public Uri getNuanceUri(final boolean isTTS) {\n        return super.getNuanceUri(isTTS);\n    }\n\n    @Override\n    public String getPackage(final Intent intent) {\n        return super.getPackage(intent);\n    }\n\n    @Override\n    public int getSpeechLength(final String words) {\n        return super.getSpeechLength(words);\n    }\n\n    @Override\n    public boolean isInterrupted() {\n        return super.isInterrupted();\n    }\n\n    @Override\n    public void manageRemoteBusy(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n        super.manageRemoteBusy(rl, bundle);\n    }\n\n    public Pair<Boolean, Boolean> shouldBind(final Intent intent) {\n        return super.shouldBind(intent, blackListHelper.fetch(this.mContext));\n    }\n\n    @Override\n    public Pair<Boolean, Boolean> shouldBind(final Intent intent, final ArrayList<String> blacklistArray) {\n        return super.shouldBind(intent, blacklistArray);\n    }\n\n    /**\n     * We need to include the array list of blacklisted applications before requesting the response\n     * from {@link SelfAwareHelper#shouldRebind(Intent, ArrayList)}\n     *\n     * @param intent included in the remote request\n     * @return true if the service should allow the rebind.\n     */\n    public boolean shouldRebind(final Intent intent) {\n        return super.shouldRebind(intent, blackListHelper.fetch(this.mContext));\n    }\n\n    @Override\n    public void issueNoVRProvider() {\n        super.issueNoVRProvider();\n    }\n\n    @Override\n    public void issueNoTTSProvider() {\n        super.issueNoTTSProvider();\n    }\n\n    @Override\n    public boolean shouldRebind(final Intent intent, @NonNull final ArrayList<String> blacklistArray) {\n        return super.shouldRebind(intent, blacklistArray);\n    }\n\n    @Override\n    public boolean shouldSpeak() {\n        return super.shouldSpeak();\n    }\n\n    @Override\n    public boolean shouldUnbind(final Intent intent) {\n        return super.shouldUnbind(intent);\n    }\n\n    @Override\n    public boolean validateRemote(final ISaiyListener rl) throws RemoteException {\n        return super.validateRemote(rl);\n    }\n\n    @Override\n    public boolean validateRemoteBundle(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n        return super.validateRemoteBundle(rl, bundle);\n    }\n\n    @Override\n    public void onTTSStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTTSStarted\");\n        }\n        VolumeHelper.duckAudioMedia(mContext);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelFetchingNotification(mContext);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.createSpeakingNotification(mContext, getSpeechLength(getUtterance()));\n    }\n\n    @Override\n    public void onTTSEnded(@NonNull final SelfAwareCache cache, @Nullable final SaiyTextToSpeech tts,\n                           @NonNull final SelfAwareParameters params) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTTSEnded\");\n        }\n        VolumeHelper.abandonAudioMedia(mContext);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.cancelFetchingNotification(mContext);\n        NotificationHelper.cancelSpeakingNotification(mContext);\n\n        if (tts != null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                if (TTSDefaults.isApprovedVoice(tts.getInitialisedEngine())) {\n                    if (!isSilentUtterance() && getUtterance().length() < SelfAwareCache.MAX_UTTERANCE_CHARS) {\n                        if (!servingRemote()) {\n                            final SaiyVoice voice = tts.getBoundSaiyVoice();\n\n                            if (voice != null) {\n                                cache.shouldCache(params, getTTSLocale(), getUtterance(), tts.getInitialisedEngine(), voice);\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: saiyVoice null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: serving remote\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: utterance failed string checks\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: unapproved\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: < LOLLIPOP\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onTTSEnded: not caching voice: tts null\");\n            }\n        }\n    }\n\n    @Override\n    public void onTTSError() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTTSError\");\n        }\n        VolumeHelper.abandonAudioMedia(mContext);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.cancelFetchingNotification(mContext);\n        NotificationHelper.cancelSpeakingNotification(mContext);\n    }\n\n    @Override\n    public void onVRStarted() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onVRStarted\");\n        }\n        resetCancelled();\n        Recognition.setState(Recognition.State.LISTENING);\n        VolumeHelper.pauseAudioMedia(mContext);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.cancelFetchingNotification(mContext);\n        NotificationHelper.createListeningNotification(mContext);\n\n        if (getDefaultRecognition() != SaiyDefaults.VR.NATIVE) {\n            vibrate(VIBRATE_MIN);\n        }\n    }\n\n    @Override\n    public void onVREnded() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onVREnded\");\n        }\n        Recognition.setState(Recognition.State.PROCESSING);\n        VolumeHelper.abandonAudioMedia(mContext);\n        NotificationHelper.cancelListeningNotification(mContext);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        vibrate(VIBRATE_MIN);\n        setFetchingCountdown();\n    }\n\n\n    @Override\n    public void onVRComplete() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onVRComplete\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.cancelFetchingNotification(mContext);\n    }\n\n    @Override\n    public void onVRError() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onVRError\");\n        }\n        Recognition.setState(Recognition.State.IDLE);\n        VolumeHelper.abandonAudioMedia(mContext);\n        removeRunnableCallback(fetchingNotification);\n        removeRunnableCallback(initialisingNotification);\n        NotificationHelper.cancelInitialisingNotification(mContext);\n        NotificationHelper.cancelFetchingNotification(mContext);\n        NotificationHelper.cancelListeningNotification(mContext);\n        vibrate(VIBRATE_MIN);\n    }\n\n    /**\n     * Runnable to display the fetching notification under network latency conditions.\n     */\n    private final Runnable fetchingNotification = new Runnable() {\n\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"fetchingNotification: showing fetching notification\");\n            }\n\n            NotificationHelper.createFetchingNotification(mContext);\n        }\n    };\n\n    /**\n     * Runnable to display the initialising notification under text to speech\n     * lagging conditions.\n     */\n    private final Runnable initialisingNotification = new Runnable() {\n\n        @Override\n        public void run() {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"initialisingNotification: showing initialising notification\");\n            }\n\n            NotificationHelper.createInitialisingNotification(mContext);\n        }\n    };\n\n    /**\n     * Set a countdown to show the fetching notification under network latency conditions.\n     */\n    public void setFetchingCountdown() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setFetchingCountdown\");\n        }\n\n        removeRunnableCallback(fetchingNotification);\n        handler.postDelayed(fetchingNotification, FETCHING_DELAY);\n    }\n\n    /**\n     * Set a countdown to show the initialising notification under text to speech\n     * lagging conditions.\n     */\n    public void setInitialisingCountdown() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setInitialisingCountdown\");\n        }\n\n        removeRunnableCallback(initialisingNotification);\n        handler.postDelayed(initialisingNotification, FETCHING_DELAY);\n    }\n\n    public Handler getHandler() {\n        return this.handler;\n    }\n\n    /**\n     * Remove callbacks associated with the {@link Runnable}\n     *\n     * @param runnable that callbacks should be removed from, or null if all should be removed.\n     */\n    public void removeRunnableCallback(final Runnable runnable) {\n\n        if (runnable != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"removeRunnableCallback: Single\");\n            }\n\n            try {\n                handler.removeCallbacks(runnable);\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"removeRunnableCallback: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"removeRunnableCallback: Exception\");\n                    e.printStackTrace();\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"removeRunnableCallback: All\");\n            }\n\n            try {\n                handler.removeCallbacksAndMessages(null);\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"removeRunnableCallback: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"removeRunnableCallback: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Acquire a wake lock to turn on the device display for\n     * the duration of {@link #SCREEN_WAKE_TIME}\n     */\n    public void acquireDisplayWakeLock() {\n        if (UtilsDevice.isScreenOff(mContext)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"acquireDisplayWakeLock: acquired\");\n            }\n            wakeLockDisplay.acquire(SCREEN_WAKE_TIME);\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"acquireDisplayWakeLock: device active\");\n            }\n        }\n    }\n\n    /**\n     * Acquire a wake lock to prevent the device from sleeping\n     */\n    public void acquireWakeLock() {\n        if (SPH.getHotwordWakelock(mContext)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"acquireWakeLock: acquired\");\n            }\n            wakeLock.acquire();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"acquireWakeLock: user denied\");\n            }\n        }\n    }\n\n    /**\n     * Release any currently held wake lock\n     */\n    public void releaseWakeLock() {\n        if (wakeLock.isHeld()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"releaseWakeLock: released\");\n            }\n            wakeLock.release();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"releaseWakeLock: not held\");\n            }\n        }\n    }\n\n    /**\n     * Get the initialised {@link SaiySoundPool} object\n     *\n     * @return the {@link SaiySoundPool}\n     */\n    public SaiySoundPool getSaiySoundPool() {\n        return saiySoundPool;\n    }\n\n    /**\n     * The Text to Speech provider has returned an error. It may be sufficient to just let the user know\n     * something went wrong by displaying an error Toast. However, we need to check the\n     * {@link Condition} to see if the user was in the middle of a command\n     * and in such a case, we'll need to attempt to resolve the issue and continue from where we left off.\n     *\n     * @param defaultTTS one of {@link SaiyDefaults.TTS}\n     * @param bundle     containing any possible {@link Condition}\n     */\n    @Override\n    public void handleTTSError(@NonNull final SaiyDefaults.TTS defaultTTS, @NonNull final Bundle bundle) {\n        super.handleTTSError(defaultTTS, bundle);\n    }\n\n    /**\n     */\n    public void handleVRError() {\n        super.handleVRError(getDefaultRecognition(), getSupportedLanguage(false),\n                getVRLocale(), getTTSLocale(), getBundle());\n    }\n\n    /**\n     * The recognition provider has returned an error. It may be sufficient to just let the user know\n     * something went wrong by displaying an error Toast. However, we need to check the\n     * {@link Condition} to see if the user was in the middle of a command\n     * and in such a case, we'll need to attempt to resolve the issue and continue from where we left off.\n     *\n     * @param error  the {@link SpeechRecognizer} error constant\n     * @param bundle containing any possible {@link Condition}\n     */\n    public void handleRecognitionError(final int error, @NonNull final Bundle bundle,\n                                       @NonNull final SaiyDefaults.VR defaultRecognizer) {\n        super.handleRecognitionError(error, bundle, defaultRecognizer, getSupportedLanguage(false),\n                getVRLocale(), getTTSLocale());\n    }\n\n    /**\n     * Show an error toast on the main thread.\n     *\n     * @param toastWords the String to toast\n     * @param length     one of {@link Toast#LENGTH_LONG} or {@link Toast#LENGTH_SHORT}\n     */\n    @Override\n    public void showToast(final String toastWords, final int length) {\n        super.showToast(toastWords, length);\n    }\n\n    /**\n     * Add haptic feedback for the user.\n     */\n    public void vibrate(final long duration) {\n        if (SPH.getVibrateCondition(mContext)) {\n            ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(duration);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.accessibilityservice.AccessibilityServiceInfo;\nimport android.app.ActivityManager;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.RemoteException;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.telephony.TelephonyManager;\nimport android.util.Pair;\nimport android.view.accessibility.AccessibilityManager;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.accessibility.SaiyAccessibilityService;\nimport ai.saiy.android.api.Defaults;\nimport ai.saiy.android.api.RequestParcel;\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.helper.CallbackType;\nimport ai.saiy.android.api.helper.Validation;\nimport ai.saiy.android.configuration.NuanceConfiguration;\nimport ai.saiy.android.error.Issue;\nimport ai.saiy.android.error.IssueContent;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.ISaiyListener;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\nimport ai.saiy.android.ui.activity.ActivityIssue;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.Global;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * A utility Class that provides methods to {@link SelfAwareConditions} mainly to avoid\n * cluttering {@link SelfAware} that is already a very busy Class.\n * <p>\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic class SelfAwareHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SelfAwareHelper.class.getSimpleName();\n\n    private static final String FULL_STOP_SPACE = \". \";\n    private static final String QUESTION_MARK_SPACE = \"? \";\n    private static final String EXCLAMATION_MARK_SPACE = \"! \";\n    private static final String LINE_SEPARATOR = System.getProperty(\"line.separator\");\n    private static final String COMMA_SPACE = \", \";\n    private static final String JUST_A_SPACE = \" \";\n\n    private static final Pattern pSERVICE_SELF_AWARE = Pattern.compile(SelfAware.class.getName());\n\n    private final Context mContext;\n    private final TelephonyManager telephonyManager;\n\n    /**\n     * Constructor\n     *\n     * @param mContext         the application context\n     * @param telephonyManager the {@link TelephonyManager}\n     */\n    public SelfAwareHelper(@NonNull final Context mContext, @NonNull final TelephonyManager telephonyManager) {\n        this.mContext = mContext;\n        this.telephonyManager = telephonyManager;\n    }\n\n    /**\n     * The Text to Speech provider has returned an error. It may be sufficient to just let the user know\n     * something went wrong by displaying an error Toast. However, we need to check the {@link Condition}\n     * to see if the user was in the middle of a command and in such a case, we'll need to attempt\n     * to resolve the issue and continue from where we left off.\n     *\n     * @param defaultTTS one of {@link SaiyDefaults.TTS}\n     * @param bundle     containing any possible {@link Condition}\n     */\n    public void handleTTSError(@NonNull final SaiyDefaults.TTS defaultTTS, @NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"handleTTSError\");\n        }\n\n        boolean showToast = true;\n\n        switch (bundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n            case Condition.CONDITION_CONVERSATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_CONVERSATION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_ROOT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_ROOT\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_EMOTION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_GOOGLE_NOW:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_GOOGLE_NOW\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_IDENTITY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTITY\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_IGNORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IGNORE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_SECURE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_SECURE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_TRANSLATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_TRANSLATION\");\n                }\n                break;\n            case Condition.CONDITION_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_USER_CUSTOM\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_NONE:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_NONE\");\n                }\n\n                break;\n        }\n\n        if (showToast) {\n            showTTSErrorToast(defaultTTS);\n        }\n    }\n\n    /**\n     * @param defaultVR one of {@link SaiyDefaults.VR}\n     * @param bundle    containing any possible {@link Condition}\n     */\n    public void handleVRError(@NonNull final SaiyDefaults.VR defaultVR, @NonNull final SupportedLanguage sl,\n                              @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale,\n                              @NonNull final Bundle bundle) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"handleVRError\");\n        }\n\n        switch (bundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n            case Condition.CONDITION_CONVERSATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_CONVERSATION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_ROOT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_ROOT\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_EMOTION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_GOOGLE_NOW:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_GOOGLE_NOW\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_IDENTITY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTITY\");\n                }\n                announceVRError(sl, vrLocale, ttsLocale);\n                break;\n            case Condition.CONDITION_IDENTIFY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTIFY\");\n                }\n                announceVRError(sl, vrLocale, ttsLocale);\n                break;\n            case Condition.CONDITION_IGNORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IGNORE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_SECURE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_SECURE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_TRANSLATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_TRANSLATION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_USER_CUSTOM\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_NONE:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_NONE\");\n                }\n                // TODO\n                break;\n        }\n    }\n\n    private void announceVRError(@NonNull final SupportedLanguage sl, @NonNull final Locale vrLocale,\n                                 @NonNull final Locale ttsLocale) {\n\n        final LocalRequest request = new LocalRequest(mContext);\n        request.prepareDefault(LocalRequest.ACTION_SPEAK_ONLY, sl, vrLocale, ttsLocale,\n                PersonalityResponse.getEnrollmentAPIError(mContext, sl));\n        request.execute();\n\n    }\n\n    private void showTTSErrorToast(@NonNull final SaiyDefaults.TTS defaultTTS) {\n\n        switch (defaultTTS) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"handleTTSError: SpeechDefault.LOCAL\");\n                }\n                showToast(mContext.getString(ai.saiy.android.R.string.error_tts_progress), Toast.LENGTH_SHORT);\n                break;\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"handleTTSError: SpeechDefault.NETWORK_NUANCE\");\n                }\n                showToast(mContext.getString(ai.saiy.android.R.string.error_tts_progress), Toast.LENGTH_SHORT);\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"handleTTSError: SpeechDefault.default\");\n                }\n                showToast(mContext.getString(ai.saiy.android.R.string.error_tts_progress), Toast.LENGTH_SHORT);\n                break;\n        }\n    }\n\n\n    /**\n     * The recognition provider has returned an error. It may be sufficient to just let the user know\n     * something went wrong by displaying an error Toast. However, we need to check the {@link Condition}\n     * to see if the user was in the middle of a command and in such a case, , we'll need to attempt\n     * to resolve the issue and continue from where we left off.\n     *\n     * @param error  the {@link SpeechRecognizer} error constant\n     * @param bundle containing any possible {@link Condition}\n     */\n    public void handleRecognitionError(final int error, @NonNull final Bundle bundle,\n                                       @NonNull final SaiyDefaults.VR defaultRecognizer,\n                                       @NonNull final SupportedLanguage sl, @NonNull final Locale vrLocale, @NonNull final Locale ttsLocale) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"handleRecognitionError\");\n        }\n\n        switch (bundle.getInt(LocalRequest.EXTRA_CONDITION, Condition.CONDITION_NONE)) {\n\n            case Condition.CONDITION_CONVERSATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_CONVERSATION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_ROOT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_ROOT\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_EMOTION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_EMOTION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_GOOGLE_NOW:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_GOOGLE_NOW\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_IDENTITY:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IDENTITY\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_IGNORE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_IGNORE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_SECURE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_SECURE\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_TRANSLATION:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_TRANSLATION\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_USER_CUSTOM:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_USER_CUSTOM\");\n                }\n                // TODO\n                break;\n            case Condition.CONDITION_NONE:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Condition.CONDITION_NONE\");\n                }\n\n                switch (error) {\n\n                    case SpeechRecognizer.ERROR_AUDIO:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_AUDIO\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_AUDIO\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_CLIENT:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_CLIENT\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_CLIENT\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_INSUFFICIENT_PERMISSIONS\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_NETWORK:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_NETWORK\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_NETWORK\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_NETWORK_TIMEOUT\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_NETWORK_TIMEOUT\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_NO_MATCH:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_NO_MATCH\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_NO_MATCH\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_RECOGNIZER_BUSY\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_RECOGNIZER_BUSY\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_SERVER:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_SERVER\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_SERVER\", Toast.LENGTH_SHORT);\n                        break;\n                    case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: ERROR_SPEECH_TIMEOUT\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_SPEECH_TIMEOUT\", Toast.LENGTH_SHORT);\n                        break;\n                    case SelfAware.JB_TIMEOUT_ERROR:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"handleRecognitionError: JB_TIMEOUT_ERROR\");\n                        }\n                        showToast(\"SpeechRecognizer.JB_TIMEOUT_ERROR\", Toast.LENGTH_SHORT);\n                        break;\n                    default:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"handleRecognitionError: ERROR_UNKNOWN\");\n                        }\n                        showToast(\"SpeechRecognizer.ERROR_UNKNOWN\", Toast.LENGTH_SHORT);\n                        break;\n                }\n                break;\n        }\n    }\n\n    /**\n     * The user's device is missing a voice recognition provider. This method will start the\n     * {@link ActivityIssue} with the relevant details populated in\n     * {@link IssueContent}\n     */\n    public void issueNoVRProvider() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"issueNoVRProvider\");\n        }\n\n        new Issue(mContext, Issue.ISSUE_NO_VR).execute();\n    }\n\n    /**\n     * The user's device is missing a text to speech. This method will start the\n     * {@link ActivityIssue} with the relevant details populated in\n     * {@link IssueContent}\n     */\n    public void issueNoTTSProvider() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"issueNoTTSProvider\");\n        }\n\n        new Issue(mContext, Issue.ISSUE_NO_TTS_ENGINE).execute();\n    }\n\n    /**\n     * Check if the device radio is currently in use. If this is called with a pending callback, we\n     * can assume it caused an {@link CallbackType#CB_INTERRUPTED} error.\n     *\n     * @return true if the radio is in use\n     */\n    public boolean isInterrupted() {\n        return telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;\n    }\n\n    /**\n     * Check the device conditions to see if it is an appropriate time to use TTS playback.\n     *\n     * @return true if the conditions are satisfied.\n     */\n    public boolean shouldSpeak() {\n        return telephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE;\n    }\n\n    /**\n     * Helper method to extract the package name from an incoming intent. Such intents should always\n     * have included their package name in {@link Intent#setAction(String)}, which will be updated\n     * each time {@link android.app.Service#onBind(Intent)} or {@link android.app.Service#onRebind(Intent)} is called.\n     *\n     * @param intent to extract the package from\n     * @return the package name or null if the package cannot be resolved\n     */\n    public String getPackage(final Intent intent) {\n\n        if (intent != null) {\n            final String action = intent.getAction();\n\n            if (action != null) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getPackage: \" + action);\n                }\n                return action;\n            } else {\n                MyLog.w(CLS_NAME, mContext.getString(ai.saiy.android.R.string.error_package_name_null));\n            }\n        } else {\n            MyLog.w(CLS_NAME, mContext.getString(ai.saiy.android.R.string.error_intent_null));\n        }\n\n        return null;\n    }\n\n\n    /**\n     * Check the {@link Intent} received from the {@link android.app.Service#onBind(Intent)} to see\n     * if the request should be declined, or if it's a local or remote request.\n     *\n     * @param intent         received\n     * @param blacklistArray holding the currently blacklisted packages.\n     * @return a {@link Pair} with {@link Pair#first} true for permission to bind, with\n     * {@link Pair#second} true if the request is local\n     */\n    public Pair<Boolean, Boolean> shouldBind(final Intent intent, final ArrayList<String> blacklistArray) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shouldBind\");\n        }\n\n        final String packageName = getPackage(intent);\n\n        if (packageName != null) {\n            if (packageName.matches(mContext.getPackageName())) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"shouldBind: returning local\");\n                }\n                return new Pair<>(true, true);\n            } else {\n                if (blacklistArray.contains(packageName)) {\n                    MyLog.e(CLS_NAME, mContext.getString(ai.saiy.android.R.string.error_package_blacklisted, packageName));\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"shouldBind: returning remote\");\n                    }\n                    return new Pair<>(true, false);\n                }\n            }\n        }\n\n        return new Pair<>(false, false);\n    }\n\n    /**\n     * Check the {@link Intent} received from the {@link android.app.Service#onRebind(Intent)} to see\n     * if the request should be declined.\n     *\n     * @param intent         received\n     * @param blacklistArray holding the currently blacklisted packages.\n     * @return true if the rebind should be permitted.\n     */\n    public boolean shouldRebind(final Intent intent, @NonNull final ArrayList<String> blacklistArray) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shouldRebind\");\n        }\n\n        final String packageName = getPackage(intent);\n\n        if (UtilsString.notNaked(packageName)) {\n            if (blacklistArray.contains(packageName)) {\n                MyLog.e(CLS_NAME, mContext.getString(ai.saiy.android.R.string.error_package_blacklisted, packageName));\n                return false;\n            } else {\n                return true;\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"shouldRebind: packageName naked\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check the {@link Intent} received from the {@link android.app.Service#onUnbind(Intent)} to see\n     * if we should allow future binds to call {@link android.app.Service#onRebind(Intent)}.\n     *\n     * @param intent received\n     * @return true if we will permit future calls to {@link android.app.Service#onRebind(Intent)}\n     */\n    public boolean shouldUnbind(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"shouldUnbind\");\n        }\n\n        if (getPackage(intent) != null) {\n            return true;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"shouldUnbind: package name null. Returning false\");\n            }\n            return false;\n        }\n    }\n\n    /**\n     * Over-cautious check to see if the Text to Speech engine is ready to use. May seem like overkill,\n     * but the cause of many, many crash reports.\n     *\n     * @return true if the TTS Engine is ready to use\n     */\n    public boolean checkTTSConditions(final SaiyTextToSpeech tts, final SaiyDefaults.TTS defaultTTS) {\n\n        if (defaultTTS != SaiyDefaults.TTS.NETWORK_NUANCE) {\n\n            if (DEBUG) {\n                if (tts == null) {\n                    MyLog.w(CLS_NAME, \"checkTTSConditions: tts null\");\n                } else {\n                    MyLog.i(CLS_NAME, \"checkTTSConditions: tts assigned\");\n                }\n            }\n\n            return tts != null && !tts.shouldReinitialise(null);\n\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * Validate the remote request, by checking that a {@link ISaiyListener} has been attached.\n     * If it hasn't, an error is logged and the request ignored. There is no way to send an error\n     * back to the caller, as the interface is null.\n     * <p>\n     *\n     * @param rl the remote {@link ISaiyListener}\n     * @return true if the listener is present\n     */\n    public boolean validateRemote(final ISaiyListener rl) throws RemoteException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateRemote\");\n        }\n\n        if (rl != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateRemote: rl valid\");\n            }\n            return true;\n        } else {\n            MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_saiylistener_null));\n        }\n\n        return false;\n    }\n\n    /**\n     * Check that the @param bundle is valid, this includes scrubbing it for parameters that could\n     * cause the app to crash. If it is not valid, an error is logged and the request ignored.\n     * <p>\n     *\n     * @param rl     the remote {@link ISaiyListener}\n     * @param bundle the bundle to check\n     * @return true if the bundle is acceptable\n     */\n    public boolean validateRemoteBundle(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateRemoteBundle\");\n        }\n\n        if (bundle != null) {\n            bundle.setClassLoader(RequestParcel.class.getClassLoader());\n\n            if (UtilsBundle.isSuspicious(bundle)) {\n                MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_bundle_corrupt));\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"validateRemoteBundle: bundle valid\");\n                }\n                return true;\n            }\n        } else {\n            MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_bundle_null));\n        }\n\n        rl.onError(Defaults.ERROR.ERROR_DEVELOPER.name(), Validation.ID_UNKNOWN);\n        return false;\n    }\n\n    /**\n     * Another remote request is being processed and the interface will return\n     * {@link Defaults.ERROR#ERROR_BUSY}. Beforehand, check that the\n     * {@link RequestParcel} is valid, this includes the basic requirements of a request id.\n     * If it is not valid, an error is logged and the request ignored.\n     * <p>\n     * Otherwise, the busy response is sent.\n     *\n     * @param rl     the remote {@link ISaiyListener}\n     * @param bundle the bundle to check\n     */\n    public void manageRemoteBusy(final ISaiyListener rl, final Bundle bundle) throws RemoteException {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"manageRemoteBusy\");\n        }\n\n        if (rl != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"manageRemoteBusy: rl valid\");\n            }\n\n            if (bundle != null) {\n                bundle.setClassLoader(RequestParcel.class.getClassLoader());\n\n                if (UtilsBundle.isSuspicious(bundle)) {\n                    MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_bundle_corrupt));\n                } else {\n\n                    final RequestParcel parcel = bundle.getParcelable(RequestParcel.PARCEL_KEY);\n\n                    if (Validation.validateParcel(mContext, parcel)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"manageRemoteBusy: validateParcel successful\");\n                        }\n                        if (Validation.validateParams(mContext, parcel)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"manageRemoteBusy: validateParams successful: returning busy\");\n                            }\n                            rl.onError(Defaults.ERROR.ERROR_BUSY.name(), parcel.getRequestId());\n                        } else {\n                            MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_request_parcel_params_invalid));\n                            rl.onError(Defaults.ERROR.ERROR_DEVELOPER.name(), Validation.ID_UNKNOWN);\n                        }\n                    } else {\n                        MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_request_parcel_invalid));\n                        rl.onError(Defaults.ERROR.ERROR_DEVELOPER.name(), Validation.ID_UNKNOWN);\n                    }\n                }\n            } else {\n                MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_bundle_null));\n                rl.onError(Defaults.ERROR.ERROR_DEVELOPER.name(), Validation.ID_UNKNOWN);\n            }\n        } else {\n            MyLog.e(\"Remote Saiy Request\", mContext.getString(ai.saiy.android.R.string.error_saiylistener_null));\n        }\n    }\n\n    /**\n     * When using Nuance Mix.nlu we need to make sure we use the right server host.\n     *\n     * @return the corresponding Uri\n     * @see <a href=\"https://developer.nuance.com/phpbb/viewtopic.php?f=15&t=1159&sid=b03f0098fdc1cc5078929b4f39237a0f\">Which host/a>\n     */\n    public Uri getNuanceUri(final boolean isTTS) {\n\n        if (!isTTS) {\n            switch (SPH.getDefaultLanguageModel(mContext)) {\n                case NUANCE:\n                    return NuanceConfiguration.SERVER_URI_NLU;\n            }\n        }\n\n        return NuanceConfiguration.SERVER_URI;\n    }\n\n    /**\n     * Check the length of the speech.\n     *\n     * @param utterance the utterance to be spoken\n     * @return the length of the speech or zero if it is null\n     */\n    public int getSpeechLength(final String utterance) {\n\n        if (UtilsString.notNaked(utterance)) {\n            return utterance.length();\n        }\n\n        return 0;\n    }\n\n    /**\n     * Start the SelfAware service if it is not running using the {@link Global} application context.\n     * This context is used to prevent, where ever possible, Android from associating memory objects to\n     * service.\n     * <p>\n     * A double check is performed on the {@link SelfAware instance} due to this\n     * API being 'unsuitable' for production.\n     * <p>\n     * The SelfAware.instance has provided erroneous results in low memory conditions.\n     *\n     * @param ctx the application context to use to start the service\n     * @see <a href=\"http://stackoverflow.com/q/16153674/1256219\">Service MemoryPrepare Objects/a>\n     */\n    public static void startSelfAwareIfRequired(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startSelfAwareIfRequired\");\n        }\n\n        if (!selfAwareRunning(ctx)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"startSelfAwareIfRequired: not running - starting\");\n            }\n            startService(ctx);\n        }\n    }\n\n    /**\n     * Start the {@link SelfAware} service\n     *\n     * @param ctx the application context\n     */\n    public static void startService(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startService\");\n        }\n\n        NotificationHelper.createNotificationChannels(ctx);\n\n        final Intent intent = new Intent(ctx, SelfAware.class);\n        intent.setAction(ctx.getApplicationContext().getPackageName());\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            ctx.getApplicationContext().startForegroundService(intent);\n        } else {\n            ctx.getApplicationContext().startService(intent);\n        }\n    }\n\n    /**\n     * restart the {@link SelfAware} service\n     *\n     * @param ctx the application context\n     */\n    public static void restartService(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"restartService\");\n        }\n\n        if (selfAwareRunning(ctx)) {\n            stopService(ctx);\n        }\n\n        startService(ctx);\n    }\n\n    /**\n     * Toggle the {@link SelfAware} service\n     *\n     * @param ctx the application context\n     */\n    public static void toggleService(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"toggleService\");\n        }\n\n        if (selfAwareRunning(ctx)) {\n            stopService(ctx);\n        } else {\n            startService(ctx);\n        }\n    }\n\n    /**\n     * Stop the {@link SelfAware} service\n     *\n     * @param ctx the application context\n     */\n    public static void stopService(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopService\");\n        }\n\n        if (selfAwareRunning(ctx)) {\n            final Intent intent = new Intent(ctx, SelfAware.class);\n            intent.setAction(ctx.getApplicationContext().getPackageName());\n            ctx.getApplicationContext().stopService(intent);\n        }\n    }\n\n    /**\n     * Start the {@link SaiyAccessibilityService} service\n     *\n     * @param ctx the application context\n     */\n    public static void startAccessibilityService(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startAccessibilityService\");\n        }\n\n        final Intent intent = new Intent(ctx, SaiyAccessibilityService.class);\n        intent.setAction(ctx.getApplicationContext().getPackageName());\n        ctx.getApplicationContext().startService(intent);\n    }\n\n    /**\n     * Check the running condition of {@link SaiyAccessibilityService}\n     *\n     * @param ctx the application context\n     */\n    public static boolean saiyAccessibilityRunning(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"saiyAccessibilityRunning\");\n        }\n\n        final AccessibilityManager aManager = (AccessibilityManager) ctx.getSystemService(Context.ACCESSIBILITY_SERVICE);\n\n        String className;\n        for (final AccessibilityServiceInfo service : aManager.getEnabledAccessibilityServiceList(\n                AccessibilityServiceInfo.FEEDBACK_ALL_MASK)) {\n\n            try {\n\n                className = service.getId();\n\n                if (className != null) {\n                    if (className.trim().endsWith(SaiyAccessibilityService.class.getSimpleName())) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"SaiyAccessibilityService running\");\n                        }\n                        return true;\n                    }\n                }\n\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"NullPointerException\");\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Exception\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"SaiyAccessibilityService not running\");\n        }\n\n        return false;\n    }\n\n    /**\n     * Check the running condition of {@link SelfAware}\n     *\n     * @param ctx the application context\n     */\n    public static boolean selfAwareRunning(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"selfAwareRunning\");\n        }\n\n        final ActivityManager aManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);\n\n        ComponentName cName;\n        String className;\n\n        for (final ActivityManager.RunningServiceInfo service : aManager.getRunningServices(Integer.MAX_VALUE)) {\n\n            try {\n\n                cName = service.service;\n\n                if (cName != null) {\n                    className = cName.getClassName();\n\n                    if (className != null) {\n                        if (pSERVICE_SELF_AWARE.matcher(className).matches()) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"SelfAware in runningInfo\");\n                            }\n\n                            final boolean doubleCheck = SelfAware.checkInstance();\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"SelfAware double check is running: \" + doubleCheck);\n                            }\n\n                            return doubleCheck;\n                        }\n                    }\n                }\n\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"NullPointerException\");\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Exception\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"SelfAware not running\");\n        }\n\n        return false;\n    }\n\n    /**\n     * Show an error toast on the main thread.\n     *\n     * @param toastWords the String to toast\n     * @param length     one of {@link Toast#LENGTH_LONG} or {@link Toast#LENGTH_SHORT}\n     */\n    public void showToast(@Nullable final String toastWords, final int length) {\n\n        if (UtilsString.notNaked(toastWords)) {\n            new Handler(Looper.getMainLooper()).post(new Runnable() {\n                @Override\n                public void run() {\n                    Toast.makeText(mContext, toastWords, length).show();\n                }\n            });\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"showToast: naked String: ignoring\");\n            }\n        }\n    }\n\n    /**\n     * Method to time the completion of code sections.\n     *\n     * @param then the start time\n     * @return the elapsed time in milliseconds.\n     */\n    public long getElapsed(final Long then) {\n        return TimeUnit.MILLISECONDS.convert((System.nanoTime() - then), TimeUnit.NANOSECONDS);\n    }\n\n    /**\n     * Split the utterance into separate strings that are of a size that doesn't exceed the maximum\n     * amount that the initialised voice engine accepts. This amount varies between engines.\n     *\n     * @param utterance the utterance to split\n     * @param maxLength the maximum length of utterance the initialised engine allows\n     * @return an ArrayList<String> containing the multiple split utterances.\n     */\n    public static ArrayList<String> splitUtteranceRegex(@NonNull final String utterance, final int maxLength) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"splitUtteranceRegex: \");\n        }\n\n        final long then = System.nanoTime();\n\n        final ArrayList<String> speakableUtterances = new ArrayList<>();\n\n        final Pattern p = Pattern.compile(\".{\" + String.valueOf(1) + \",\" + maxLength + \"}(?:[.!?,]\\\\s+|\\\\n|$)\",\n                Pattern.DOTALL);\n\n        final Matcher matcher = p.matcher(utterance);\n        String groupUtterance;\n        int length;\n        boolean next = matcher.find();\n\n        while (next) {\n            groupUtterance = matcher.group().trim();\n            length = groupUtterance.length();\n            speakableUtterances.add(matcher.group().trim());\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Group: \" + length + \":- \" + groupUtterance);\n            }\n\n            next = matcher.find();\n\n            if (next && length < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"matcher failed to separate sufficiently: falling back \");\n                }\n\n                return splitUtteranceDeliberate(utterance);\n            }\n        }\n\n        if (speakableUtterances.isEmpty()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"matcher failed empty: falling back \");\n            }\n            return splitUtteranceDeliberate(utterance);\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME + \" splitUtteranceRegex:\", then);\n        }\n\n        return speakableUtterances;\n\n    }\n\n    /**\n     * Called if the more readable {@link #splitUtteranceRegex(String, int)} has failed.\n     *\n     * @param utterance the utterance to split\n     * @return an {@code ArrayList<String>} containing the multiple split utterances.\n     */\n    public static ArrayList<String> splitUtteranceDeliberate(@NonNull String utterance) {\n\n        final long then = System.nanoTime();\n        final ArrayList<String> speakableUtterances = new ArrayList<>();\n\n        int splitLocation;\n        String success;\n\n        while (utterance.length() > SaiyTextToSpeech.MAX_UTTERANCE_LENGTH) {\n            splitLocation = utterance.lastIndexOf(FULL_STOP_SPACE, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"(0 FULL STOP) - last index at: \" + splitLocation);\n            }\n\n            if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"(1 FULL STOP) - NOT_OK\");\n                }\n                splitLocation = utterance.lastIndexOf(QUESTION_MARK_SPACE, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"(1 QUESTION MARK) - last index at: \" + splitLocation);\n                }\n\n                if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"(2 QUESTION MARK) - NOT_OK\");\n                    }\n                    splitLocation = utterance.lastIndexOf(EXCLAMATION_MARK_SPACE, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"(2 EXCLAMATION MARK) - last index at: \" + splitLocation);\n                    }\n\n                    if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"(3 EXCLAMATION MARK) - NOT_OK\");\n                        }\n                        splitLocation = utterance.lastIndexOf(LINE_SEPARATOR, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"(3 SEPARATOR) - last index at: \" + splitLocation);\n                        }\n\n                        if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"(4 SEPARATOR) - NOT_OK\");\n                            }\n                            splitLocation = utterance.lastIndexOf(COMMA_SPACE, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"(4 COMMA) - last index at: \" + splitLocation);\n                            }\n\n                            if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"(5 COMMA) - NOT_OK\");\n                                }\n                                splitLocation = utterance.lastIndexOf(JUST_A_SPACE, SaiyTextToSpeech.MAX_UTTERANCE_LENGTH);\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"(5 SPACE) - last index at: \" + splitLocation);\n                                }\n\n                                if (splitLocation < SaiyTextToSpeech.MIN_UTTERANCE_LENGTH) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"(6 SPACE) - NOT_OK\");\n                                    }\n                                    splitLocation = SaiyTextToSpeech.MAX_UTTERANCE_LENGTH;\n\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"(6 MAX_UTTERANCE_LENGTH) - last index at: \" + splitLocation);\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"Accepted\");\n                                    }\n                                    splitLocation -= 1;\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Accepted\");\n                            }\n                            splitLocation -= 1;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Accepted\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"Accepted\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Accepted\");\n                }\n            }\n\n            success = utterance.substring(0, (splitLocation + 2));\n            speakableUtterances.add(success.trim());\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Split - Length: \" + success.length() + \" -:- \" + success);\n                MyLog.i(CLS_NAME, \"------------------------------\");\n            }\n\n            utterance = utterance.substring((splitLocation + 2)).trim();\n        }\n\n        speakableUtterances.add(utterance);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Split - Length: \" + utterance.length() + \" -:- \" + utterance);\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n\n        return speakableUtterances;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareParameters.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\nimport android.text.TextUtils;\nimport android.util.Pair;\nimport android.widget.Toast;\n\nimport java.util.HashMap;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.sound.VolumeHelper;\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.tts.SaiyTextToSpeech;\nimport ai.saiy.android.tts.helper.TTSDefaults;\nimport ai.saiy.android.utils.Conditions.Network;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Helper class to manage the ({@link SaiyTextToSpeech} parameters.\n * <p/>\n * Created by benrandall76@gmail.com on 20/03/2016.\n */\npublic class SelfAwareParameters extends HashMap<String, String> {\n\n    // Unused\n    private static final long serialVersionUID = 516920492195138564L;\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SelfAwareParameters.class.getSimpleName();\n\n    private final Context mContext;\n    private final SelfAwareParameters21 bundle;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     */\n    public SelfAwareParameters(@NonNull final Context mContext) {\n        super(new HashMap<String, String>());\n        this.mContext = mContext.getApplicationContext();\n        bundle = new SelfAwareParameters21();\n    }\n\n    public Bundle getBundle() {\n        return bundle.getBundle();\n    }\n\n    /**\n     * Check if the user wants to use network voice engines and if the minimum required connection\n     * speed is met.\n     *\n     * @return true if conditions are satisfied.\n     */\n    public boolean shouldNetwork() {\n        return Network.shouldTTSNetwork(mContext);\n    }\n\n\n    /**\n     * Check if the Text to Speech request requires a network synthesised voice.\n     *\n     * @return true if network synthesis is required. False otherwise.\n     */\n    @SuppressWarnings(\"deprecation\")\n    public boolean isNetworkAllowed() {\n\n        final String networkAllowed = get(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS);\n\n        if (networkAllowed != null) {\n            return Boolean.parseBoolean(networkAllowed);\n        } else {\n            return SPH.getNetworkSynthesis(mContext);\n        }\n    }\n\n    /**\n     * Get the volume the Text to Speech object should use. This can be manipulated by the user, if they\n     * prefer a certain volume.\n     *\n     * @return the value the user has chosen or the default.\n     */\n    public float getVolume() {\n\n        final Pair<Boolean, Float> volumePair = VolumeHelper.getUserMediaVolumeValue(mContext);\n\n        if (volumePair.first) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getVolume: volumePair.second: \" + volumePair.second);\n            }\n\n            if (volumePair.second < 0.25F) {\n                    new Handler(Looper.getMainLooper()).post(new Runnable() {\n                        @Override\n                        public void run() {\n                            Toast.makeText(mContext,\n                                    mContext.getString(R.string.error_tts_volume), Toast.LENGTH_SHORT).show();\n                        }\n                    });\n            }\n\n            return volumePair.second;\n        } else {\n                new Handler(Looper.getMainLooper()).post(new Runnable() {\n                    @Override\n                    public void run() {\n                        Toast.makeText(mContext,\n                                mContext.getString(R.string.error_tts_volume), Toast.LENGTH_SHORT).show();\n                    }\n                });\n        }\n\n        return 1F;\n    }\n\n    /**\n     * Currently constant pan value\n     *\n     * @return the pan value\n     */\n    private float getPan() {\n        return 0;\n    }\n\n    /**\n     * Set the parameters the {@link SaiyTextToSpeech} engine will use. This\n     * is now deprecated, however, currently most voice engines are not responding correctly to the\n     * usage of the new APIs\n     *\n     * @param isSpeakListen whether the speech should start a recognition request on completion\n     * @param conditions    the {@link SelfAwareConditions}\n     */\n    @SuppressWarnings(\"deprecation\")\n    public void setParams(final boolean isSpeakListen, @NonNull final SelfAwareConditions conditions) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setParams: isSpeakListen: \" + isSpeakListen);\n        }\n\n        if (isSpeakListen) {\n            putObject(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.valueOf(LocalRequest.ACTION_SPEAK_LISTEN));\n        } else {\n            putObject(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.valueOf(LocalRequest.ACTION_SPEAK_ONLY));\n        }\n\n        putObject(TextToSpeech.Engine.KEY_PARAM_VOLUME, getVolume());\n        putObject(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_MUSIC);\n        putObject(TextToSpeech.Engine.KEY_PARAM_PAN, getPan());\n\n        switch (conditions.getDefaultTTS()) {\n\n            case LOCAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"SpeechDefault.LOCAL\");\n                }\n                putObject(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, String.valueOf(shouldNetwork()));\n                break;\n            case NETWORK_NUANCE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"SpeechDefault.NETWORK_NUANCE\");\n                }\n                break;\n        }\n    }\n\n    /**\n     * Check if the current utterance is part of an array of speech. If so, the\n     * {@link SaiyProgressListener} will only react to certain positions in\n     * the array of speech. The utterance id may have been altered during the array of speech with\n     * one of {@link SaiyTextToSpeech#ARRAY_FIRST} {@link SaiyTextToSpeech#ARRAY_INTERIM}\n     * {@link SaiyTextToSpeech#ARRAY_LAST}\n     *\n     * @param utteranceId the current utterance id\n     * @return true if the {@link SaiyProgressListener} should react\n     */\n    public boolean validateOnStart(@NonNull final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateOnStart: \" + utteranceId);\n        }\n\n        if (get(TTSDefaults.EXTRA_INTERRUPTED) != null || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_FIRST)\n                || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_SINGLE)) {\n            remove(TTSDefaults.EXTRA_INTERRUPTED);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateOnStart: ARRAY_FIRST/EXTRA_INTERRUPTED/ARRAY_SINGLE\");\n            }\n            return true;\n        } else if (utteranceId.startsWith(SaiyTextToSpeech.ARRAY_INTERIM)\n                || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_LAST)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateOnStart: ARRAY_INTERIM/ARRAY_LAST\");\n            }\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * Check if the current utterance is part of an array of speech. If so, the\n     * {@link SaiyProgressListener} will only react to certain positions in\n     * the array of speech. The utterance id may have been altered during the array of speech with\n     * {@link SaiyTextToSpeech#ARRAY_LAST} appended with the true utterance id.\n     *\n     * @param utteranceId the current utterance id\n     * @return a Pair with the first parameter denoting if the {@link SaiyProgressListener}\n     * should react and the second the actual utterance id.\n     */\n    public Pair<Boolean, String> validateOnDone(@NonNull final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"validateOnDone: \" + utteranceId);\n        }\n\n        if (get(TTSDefaults.EXTRA_INTERRUPTED) != null || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_LAST)\n                || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_SINGLE)) {\n            remove(TTSDefaults.EXTRA_INTERRUPTED);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateOnDone: ARRAY_LAST/EXTRA_INTERRUPTED/ARRAY_SINGLE\");\n            }\n\n            if (get(TTSDefaults.EXTRA_INTERRUPTED_FORCED) != null) {\n                remove(TTSDefaults.EXTRA_INTERRUPTED_FORCED);\n                return new Pair<>(true, String.valueOf(LocalRequest.ACTION_SPEAK_ONLY));\n            } else if (utteranceId.contains(SaiyTextToSpeech.ARRAY_DELIMITER)) {\n                final String extracted = TextUtils.split(utteranceId, SaiyTextToSpeech.ARRAY_DELIMITER)[1];\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"validateOnDone: extracted: \" + extracted);\n                }\n                return new Pair<>(true, extracted);\n            } else {\n                return new Pair<>(true, utteranceId);\n            }\n\n        } else if (utteranceId.startsWith(SaiyTextToSpeech.ARRAY_FIRST)\n                || utteranceId.startsWith(SaiyTextToSpeech.ARRAY_INTERIM)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"validateOnDone: ARRAY_FIRST/ARRAY_INTERIM\");\n            }\n            return new Pair<>(false, null);\n        } else {\n            return new Pair<>(true, utteranceId);\n        }\n    }\n\n    /**\n     * Set the utterance id\n     *\n     * @param utteranceId the utterance id to set\n     */\n    public void setUtteranceId(@NonNull final String utteranceId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setUtteranceId: \" + utteranceId);\n        }\n\n        putObject(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);\n    }\n\n    /**\n     * Get the id of the most recent utterance\n     *\n     * @return the utterance id\n     */\n    public String getUtteranceId() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUtteranceId\");\n        }\n\n        final String utteranceId = get(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUtteranceId: \" + utteranceId);\n        }\n\n        if (UtilsString.notNaked(utteranceId)) {\n            return utteranceId;\n        }\n\n        return String.valueOf(LocalRequest.ACTION_SPEAK_ONLY);\n    }\n\n    protected void putObject(@NonNull final String key, @NonNull final Object value) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n\n            if (value instanceof String) {\n                bundle.putString(key, String.valueOf(value));\n            } else if (value instanceof Integer) {\n                bundle.putInt(key, (Integer) value);\n            } else if (value instanceof Float) {\n                bundle.putFloat(key, (Float) value);\n            } else if (value instanceof Double) {\n                bundle.putDouble(key, (Double) value);\n            } else if (value instanceof Boolean) {\n                bundle.putBoolean(key, (Boolean) value);\n            }\n        } else {\n            super.put(key, String.valueOf(value));\n        }\n    }\n\n    @Override\n    public String get(final Object key) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            return bundle.get(key);\n        } else {\n            return super.get(key);\n        }\n    }\n\n    @Override\n    public String remove(final Object key) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            return bundle.remove(key);\n        } else {\n            return super.remove(key);\n        }\n    }\n\n    @Override\n    public void clear() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            bundle.clear();\n        } else {\n            super.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareParameters21.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport java.util.HashMap;\n\n/**\n * Class to simulate the behaviour of the depreciated {@link SelfAwareParameters} used in the\n * {@link ai.saiy.android.tts.SaiyTextToSpeech#speak(String, int, HashMap)}\n * <p>\n * This class provides the methods for the\n * API > 21 {@link ai.saiy.android.tts.SaiyTextToSpeech#speak(CharSequence, int, Bundle, String)}\n * <p>\n * Created by benrandall76@gmail.com on 20/06/2016.\n */\n\nclass SelfAwareParameters21 {\n\n    protected final Bundle params;\n\n    SelfAwareParameters21() {\n        this.params = new Bundle();\n    }\n\n    protected Bundle getBundle() {\n        return params;\n    }\n\n    protected String get(@NonNull final Object key) {\n\n        final Object object = params.get((String) key);\n\n        if (object != null) {\n            return String.valueOf(object);\n        } else {\n            return null;\n        }\n    }\n\n    protected String putBoolean(@NonNull final String key, final boolean value) {\n        params.putBoolean(key, value);\n        return null;\n    }\n\n    protected String putDouble(@NonNull final String key, final double value) {\n        params.putDouble(key, value);\n        return null;\n    }\n\n    protected String putFloat(@NonNull final String key, final float value) {\n        params.putFloat(key, value);\n        return null;\n    }\n\n    protected String putInt(@NonNull final String key, final int value) {\n        params.putInt(key, value);\n        return null;\n    }\n\n    protected String putString(@NonNull final String key, @NonNull final String value) {\n        params.putString(key, value);\n        return null;\n    }\n\n    protected String remove(@NonNull final Object key) {\n        params.remove((String) key);\n        return null;\n    }\n\n    void clear() {\n        params.clear();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/service/helper/SelfAwareVerbose.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.service.helper;\n\nimport android.app.Service;\nimport android.os.Bundle;\nimport android.speech.SpeechRecognizer;\nimport android.support.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Set;\n\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class purely for logging verbose information. This will not be used in production, unless\n * logging is enabled when attempting to resolve a specific issue with a user.\n * <p/>\n * Created by benrandall76@gmail.com on 03/04/2016.\n */\npublic class SelfAwareVerbose {\n\n    private static final String CLS_NAME = SelfAwareVerbose.class.getSimpleName();\n\n    /**\n     * Iterate through the recognition results and their associated confidence scores.\n     *\n     * @param bundle of recognition data\n     */\n    public static void logSpeechResults(final Bundle bundle) {\n        MyLog.i(CLS_NAME, \"logSpeechResults\");\n\n        examineBundle(bundle);\n\n        final ArrayList<String> heardVoice = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);\n        final ArrayList<String> unstable = bundle.getStringArrayList(RecognitionNative.UNSTABLE_RESULTS);\n        final float[] confidence = bundle.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES);\n\n        if (heardVoice != null) {\n            MyLog.d(CLS_NAME, \"heardVoice: \" + heardVoice.size());\n        }\n        if (unstable != null) {\n            MyLog.d(CLS_NAME, \"unstable: \" + unstable.size());\n        }\n        if (confidence != null) {\n            MyLog.d(CLS_NAME, \"confidence: \" + confidence.length);\n        }\n\n        /* handles empty string bug */\n        if (UtilsList.notNaked(heardVoice)) {\n            heardVoice.removeAll(Collections.singleton(\"\"));\n        }\n\n        /* handles empty string bug */\n        if (UtilsList.notNaked(unstable)) {\n            unstable.removeAll(Collections.singleton(\"\"));\n        }\n\n        if (UtilsList.notNaked(confidence) && UtilsList.notNaked(heardVoice) && confidence.length == heardVoice.size()) {\n            for (int i = 0; i < heardVoice.size(); i++) {\n                MyLog.i(CLS_NAME, \"Results: \" + heardVoice.get(i) + \" ~ \" + confidence[i]);\n            }\n        } else if (UtilsList.notNaked(heardVoice)) {\n            for (int i = 0; i < heardVoice.size(); i++) {\n                MyLog.i(CLS_NAME, \"Results: \" + heardVoice.get(i));\n            }\n        } else if (UtilsList.notNaked(unstable)) {\n            for (int i = 0; i < unstable.size(); i++) {\n                MyLog.i(CLS_NAME, \"Unstable: \" + unstable.get(i));\n            }\n        } else {\n            MyLog.w(CLS_NAME, \"Results: values error\");\n        }\n    }\n\n    /**\n     * Check the parameter of any coming {@link Service#onTrimMemory(int)} call\n     *\n     * @param level the 'Trim' level\n     */\n    public static void memoryVerbose(final int level) {\n        switch (level) {\n            case Service.TRIM_MEMORY_BACKGROUND:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_BACKGROUND\");\n                break;\n            case Service.TRIM_MEMORY_COMPLETE:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_COMPLETE\");\n                break;\n            case Service.TRIM_MEMORY_MODERATE:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_MODERATE\");\n                break;\n            case Service.TRIM_MEMORY_RUNNING_CRITICAL:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_RUNNING_CRITICAL\");\n                break;\n            case Service.TRIM_MEMORY_RUNNING_LOW:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_BACKGROUND\");\n                break;\n            case Service.TRIM_MEMORY_RUNNING_MODERATE:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_RUNNING_MODERATE\");\n                break;\n            case Service.TRIM_MEMORY_UI_HIDDEN:\n                MyLog.w(CLS_NAME, \"memoryVerbose: TRIM_MEMORY_UI_HIDDEN\");\n                break;\n            default:\n                MyLog.w(CLS_NAME, \"memoryVerbose: default\");\n                break;\n        }\n    }\n\n    /**\n     * For debugging the intent extras\n     *\n     * @param bundle containing potential extras\n     */\n    private static void examineBundle(@Nullable final Bundle bundle) {\n        MyLog.i(CLS_NAME, \"examineBundle\");\n\n        if (bundle != null) {\n            final Set<String> keys = bundle.keySet();\n            //noinspection Convert2streamapi\n            for (final String key : keys) {\n                MyLog.v(CLS_NAME, \"examineBundle: \" + key + \" ~ \" + bundle.get(key));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/sound/VolumeHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.sound;\n\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to handle the manipulation of various system volume settings, including the ducking of\n * audio during speech and the pausing of audio during recognition.\n * <p/>\n * Unfortunately, many applications that are broadcasting media do not adhere to these requests.\n * <p/>\n * Static access for ease of use.\n * <p/>\n * Created by benrandall76@gmail.com on 26/03/2016.\n */\npublic class VolumeHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = VolumeHelper.class.getSimpleName();\n\n    private static final float VOLUME_MARGIN = 0.2F;\n\n    /**\n     * Our {@link AudioManager.OnAudioFocusChangeListener}\n     */\n    private static final AudioManager.OnAudioFocusChangeListener audioFocus = new AudioManager.OnAudioFocusChangeListener() {\n        @Override\n        public void onAudioFocusChange(final int focusChange) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"AudioManager focusChange: \" + focusChange);\n            }\n\n            switch (focusChange) {\n\n                case AudioManager.AUDIOFOCUS_GAIN:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager focusChange: AUDIOFOCUS_GAIN\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_LOSS:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager focusChange: AUDIOFOCUS_LOSS\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager focusChange: AUDIOFOCUS_LOSS_TRANSIENT\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager focusChange: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK\");\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager focusChange: AUDIOFOCUS default\");\n                    }\n                    break;\n            }\n        }\n    };\n\n    /**\n     * Mute the ringtone during an incoming call. This method should be used with caution.\n     * <p/>\n     * The ringtone cannot be 'ducked' as with other media.\n     *\n     * @param ctx  the application context\n     * @param mute whether or not to mute or restore the ringtone settings.\n     */\n    @SuppressWarnings(\"deprecation\")\n    public static void muteRinger(final Context ctx, final boolean mute) {\n        if (DEBUG) {\n            MyLog.d(CLS_NAME, \"Ringer Muting: \" + mute);\n        }\n\n        final AudioManager am = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n\n        if (DEBUG) {\n            switch (SPH.getDefaultRinger(ctx)) {\n\n                case AudioManager.RINGER_MODE_NORMAL:\n                    MyLog.v(CLS_NAME, \"getRingerDefault: RINGER_MODE_NORMAL\");\n                    break;\n                case AudioManager.RINGER_MODE_SILENT:\n                    MyLog.v(CLS_NAME, \"getRingerDefault: RINGER_MODE_SILENT\");\n                    break;\n                case AudioManager.RINGER_MODE_VIBRATE:\n                    MyLog.v(CLS_NAME, \"getRingerDefault: RINGER_MODE_VIBRATE\");\n                    break;\n                default:\n                    MyLog.w(CLS_NAME, \"getRingerDefault: \" + SPH.getDefaultRinger(ctx));\n                    break;\n            }\n\n            switch (am.getRingerMode()) {\n\n                case AudioManager.RINGER_MODE_NORMAL:\n                    MyLog.i(CLS_NAME, \"getRingerMode: RINGER_MODE_NORMAL\");\n                    break;\n                case AudioManager.RINGER_MODE_SILENT:\n                    MyLog.i(CLS_NAME, \"getRingerMode: RINGER_MODE_SILENT\");\n                    break;\n                case AudioManager.RINGER_MODE_VIBRATE:\n                    MyLog.i(CLS_NAME, \"getRingerMode: RINGER_MODE_VIBRATE\");\n                    break;\n                default:\n                    MyLog.w(CLS_NAME, \"getRingerMode: \" + am.getRingerMode());\n                    break;\n            }\n\n            switch (am.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER)) {\n\n                case AudioManager.VIBRATE_SETTING_ON:\n                    MyLog.i(CLS_NAME, \"getVibrateSetting: VIBRATE_SETTING_ON\");\n                    break;\n                case AudioManager.VIBRATE_SETTING_OFF:\n                    MyLog.i(CLS_NAME, \"getVibrateSetting: VIBRATE_SETTING_OFF\");\n                    break;\n                case AudioManager.VIBRATE_SETTING_ONLY_SILENT:\n                    MyLog.i(CLS_NAME, \"getVibrateSetting: VIBRATE_SETTING_ONLY_SILENT\");\n                    break;\n                default:\n                    MyLog.w(CLS_NAME, \"getVibrateSetting: Default\");\n                    break;\n            }\n\n            MyLog.i(CLS_NAME, \"shouldVibrate: \" + am.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER));\n        }\n\n        if (mute) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Ringer Muting\");\n            }\n\n            SPH.setDefaultRinger(ctx, am.getRingerMode());\n\n            try {\n                am.setRingerMode(AudioManager.RINGER_MODE_SILENT);\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Ringer Muting Exception\");\n                    e.printStackTrace();\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"Ringer Muting Restore\");\n            }\n\n            try {\n                am.setRingerMode(SPH.getDefaultRinger(ctx));\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Ringer Restore Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Duck the audio\n     *\n     * @param ctx the application context\n     */\n    public static void duckAudioMedia(final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"duckAudioMedia\");\n        }\n\n        try {\n\n            final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n\n            switch (audioManager.requestAudioFocus(audioFocus, AudioManager.STREAM_MUSIC,\n                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {\n\n                case AudioManager.AUDIOFOCUS_REQUEST_FAILED:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"AudioManager duckAudioMedia AUDIOFOCUS_REQUEST_FAILED\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager duckAudioMedia AUDIOFOCUS_REQUEST_GRANTED\");\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager duckAudioMedia AUDIOFOCUS default\");\n                    }\n                    break;\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"duckAudioMedia: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"duckAudioMedia: Exception\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n\n    /**\n     * Pause the audio\n     *\n     * @param ctx the application context\n     */\n    public static void pauseAudioMedia(final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"pauseAudioMedia\");\n        }\n\n        try {\n\n            final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n\n            int requestType;\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                requestType = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;\n            } else {\n                requestType = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;\n            }\n\n            switch (audioManager.requestAudioFocus(audioFocus, AudioManager.STREAM_MUSIC, requestType)) {\n\n                case AudioManager.AUDIOFOCUS_REQUEST_FAILED:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"AudioManager pauseAudioMedia AUDIOFOCUS_REQUEST_FAILED\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager pauseAudioMedia AUDIOFOCUS_REQUEST_GRANTED\");\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager pauseAudioMedia AUDIOFOCUS default\");\n                    }\n                    break;\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"abandonAudioMedia: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"abandonAudioMedia: Exception\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Notify the System that any previous condition requiring to duck or pause audio is now complete.\n     *\n     * @param ctx the application context\n     */\n    public static void abandonAudioMedia(final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"abandonAudioMedia\");\n        }\n\n        try {\n\n            final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n\n            switch (audioManager.abandonAudioFocus(audioFocus)) {\n\n                case AudioManager.AUDIOFOCUS_REQUEST_FAILED:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"AudioManager abandonAudioMedia AUDIOFOCUS_REQUEST_FAILED\");\n                    }\n                    break;\n                case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager abandonAudioMedia AUDIOFOCUS_REQUEST_GRANTED\");\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"AudioManager abandonAudioMedia AUDIOFOCUS default\");\n                    }\n                    break;\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"abandonAudioMedia: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"abandonAudioMedia: Exception\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Get the value we should set the media stream to prior to speech, based on the current stream level\n     * and the percentage by which the user prefers the stream to be adjusted\n     *\n     * @param ctx the application context\n     * @return the rounded stream level\n     */\n    public static Pair<Boolean, Float> getUserMediaVolumeValue(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUserMediaVolumeValue\");\n        }\n\n        try {\n\n            final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n\n            final boolean volumeProfileEnabled = volumeProfileEnabled(audioManager);\n\n            if (volumeProfileEnabled) {\n\n                final int currentPercentage = getMediaVolumePercentage(audioManager);\n                final int userPercentage = SPH.getTTSVolume(ctx);\n                final int combinedPercentage = currentPercentage + userPercentage;\n\n                if (currentPercentage > 0) {\n                    if (combinedPercentage > 0) {\n                        if (combinedPercentage > 100) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"getUserMediaVolumeValue: using combined max\");\n                            }\n                            return new Pair<>(true, 1F);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"getUserMediaVolumeValue: using combined\");\n                            }\n                            return new Pair<>(true, ((float) combinedPercentage / 100));\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getUserMediaVolumeValue: combined below zero: applying margin\");\n                        }\n                        return new Pair<>(true, VOLUME_MARGIN);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"getUserMediaVolumeValue: sound off\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getUserMediaVolumeValue: device muted\");\n                }\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getUserMediaVolumeValue: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final ArithmeticException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getUserMediaVolumeValue: ArithmeticException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getUserMediaVolumeValue: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return new Pair<>(false, 0F);\n    }\n\n    /**\n     * Check if the device can currently output sound, by checking the media profile. This is unfortunately\n     * not foolproof, due to the various ways different ROM and device manufacturers link the stream types.\n     *\n     * @param audioManager object\n     * @return true if the device will output sounds, false otherwise\n     */\n    private static boolean volumeProfileEnabled(@NonNull final AudioManager audioManager) {\n\n        switch (audioManager.getRingerMode()) {\n\n            case AudioManager.RINGER_MODE_NORMAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"volumeProfileEnabled: RINGER_MODE_NORMAL\");\n                }\n                return true;\n            case AudioManager.RINGER_MODE_VIBRATE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"volumeProfileEnabled: RINGER_MODE_VIBRATE\");\n                }\n                break;\n            case AudioManager.RINGER_MODE_SILENT:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"volumeProfileEnabled: RINGER_MODE_SILENT\");\n                }\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"volumeProfileEnabled: Default\");\n                }\n                break;\n        }\n\n        return false;\n\n    }\n\n    /**\n     * Get a media stream value based on a given percentage of the maximum permitted volume level\n     *\n     * @param audioManager object\n     * @param percentage   to calculate\n     * @return the rounded percentage\n     */\n    private static int getMediaPercentageValue(@NonNull final AudioManager audioManager, final int percentage) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMediaPercentageValue\");\n        }\n\n        final int maxVolume = getMaxMediaVolume(audioManager);\n        final int resolvedValue = Math.round(maxVolume * percentage);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMediaPercentageValue: percentage: \" + percentage);\n            MyLog.i(CLS_NAME, \"getMediaPercentageValue: maxVolume: \" + maxVolume);\n            MyLog.i(CLS_NAME, \"getMediaPercentageValue: resolvedValue: \" + resolvedValue);\n        }\n\n        return resolvedValue;\n    }\n\n    /**\n     * Get the percentage of the current media volume in relation to the maximum level permitted\n     *\n     * @param audioManager object\n     * @return the integer value or {@link Integer#MAX_VALUE} if the request fails\n     */\n    private static int getMediaVolumePercentage(@NonNull final AudioManager audioManager) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMediaVolumePercentage\");\n        }\n\n        final int currentVolume = getMediaVolume(audioManager);\n        final int maxVolume = getMaxMediaVolume(audioManager);\n        final int percentage = (int) Math.round(100.0 * currentVolume / maxVolume);\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getMediaVolumePercentage: currentVolume: \" + currentVolume);\n            MyLog.i(CLS_NAME, \"getMediaVolumePercentage: maxVolume: \" + maxVolume);\n            MyLog.i(CLS_NAME, \"getMediaVolumePercentage: percentage: \" + percentage);\n        }\n\n        return percentage;\n    }\n\n    /**\n     * Get the current volume value of the media stream\n     *\n     * @param audioManager object\n     * @return the integer value\n     */\n    private static int getMediaVolume(@NonNull final AudioManager audioManager) {\n        return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n    }\n\n    /**\n     * Get the current volume value of the media stream\n     *\n     * @param ctx the application context\n     * @return the integer value\n     */\n    public static int getMediaVolume(@NonNull final Context ctx) {\n        final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n        return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n    }\n\n    /**\n     * Get the maximum volume value of the media stream\n     *\n     * @param audioManager object\n     * @return the integer value\n     */\n    private static int getMaxMediaVolume(@NonNull final AudioManager audioManager) {\n        return audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);\n    }\n\n    /**\n     * Get the maximum volume value of the media stream\n     *\n     * @param ctx the application context\n     * @return the integer value\n     */\n    public static int getMaxMediaVolume(@NonNull final Context ctx) {\n        final AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);\n        return audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/thirdparty/tasker/TaskerHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.thirdparty.tasker;\n\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 10/08/2016.\n */\n\npublic class TaskerHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = TaskerHelper.class.getSimpleName();\n\n    private static final String TASKER_ACCESS_URI = \"content://net.dinglisch.android.tasker/prefs\";\n    private static final String TASKER_CONTENT_URI = \"content://net.dinglisch.android.tasker/tasks\";\n    private static final String COLUMN_PROJECT_NAME = \"project_name\";\n    private static final String COLUMN_TASK_NAME = \"name\";\n    private static final String COLUMN_ENABLED = \"enabled\";\n    private static final String COLUMN_ACCESS = \"ext_access\";\n\n\n    /**\n     * Get a list of the user's tasks and associated project names\n     *\n     * @param ctx the application context\n     * @return an ArrayList of {@link TaskerTask} objects\n     */\n    public ArrayList<TaskerTask> getTasks(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getTasks\");\n        }\n\n        long then = System.nanoTime();\n\n        final ArrayList<TaskerTask> taskArrayList = new ArrayList<>();\n\n        Cursor cursor = null;\n\n        try {\n            cursor = ctx.getContentResolver().query(Uri.parse(TASKER_CONTENT_URI), null, null, null, null);\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getTasks: cursor open Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        if (cursor != null) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"getTasks: cursor count: \" + cursor.getCount());\n            }\n\n            if (cursor.getCount() > 0) {\n\n                try {\n\n                    final int projectIndex = cursor.getColumnIndex(COLUMN_PROJECT_NAME);\n                    final int taskIndex = cursor.getColumnIndex(COLUMN_TASK_NAME);\n\n                    String projectName;\n                    String taskName;\n                    TaskerTask taskerTask;\n                    while (cursor.moveToNext()) {\n\n                        projectName = cursor.getString(projectIndex);\n                        taskName = cursor.getString(taskIndex);\n\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"Project Name: \" + projectName);\n                            MyLog.d(CLS_NAME, \"Task Name: \" + taskName);\n                        }\n\n                        if (UtilsString.notNaked(projectName) && UtilsString.notNaked(taskName)) {\n                            taskerTask = new TaskerTask();\n                            taskerTask.setProjectName(projectName);\n                            taskerTask.setTaskName(taskName);\n                            taskArrayList.add(taskerTask);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"getTasks: project/task naked\");\n                            }\n                        }\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"getTasks: cursor seek Exception\");\n                        e.printStackTrace();\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getTasks: cursor empty\");\n                }\n            }\n\n            try {\n                cursor.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getTasks: cursor close Exception\");\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getTasks: cursor null\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"Tasks count: \" + taskArrayList.size());\n            MyLog.getElapsed(TaskerHelper.class.getSimpleName(), then);\n        }\n\n        return taskArrayList;\n    }\n\n    /**\n     * Check if Tasker in enabled and external access permission have been granted.\n     *\n     * @param ctx the application context\n     * @return a {@link Pair} with the first parameter denoting enabled, the seconds access permissions\n     */\n    public Pair<Boolean, Boolean> canInteract(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"canInteract\");\n        }\n\n        long then = System.nanoTime();\n\n        Pair<Boolean, Boolean> toReturn = new Pair<>(false, false);\n\n        Cursor cursor = null;\n\n        try {\n            cursor = ctx.getContentResolver().query(Uri.parse(TASKER_ACCESS_URI), null, null, null, null);\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"canInteract: cursor open Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        if (cursor != null) {\n            if (DEBUG) {\n                MyLog.d(CLS_NAME, \"canInteract: cursor count: \" + cursor.getCount());\n            }\n\n            if (cursor.getCount() > 0) {\n\n                try {\n\n                    final int enabledIndex = cursor.getColumnIndex(COLUMN_ENABLED);\n                    final int accessIndex = cursor.getColumnIndex(COLUMN_ACCESS);\n\n                    String enabled;\n                    String access;\n                    if (cursor.moveToFirst()) {\n\n                        enabled = cursor.getString(enabledIndex);\n                        access = cursor.getString(accessIndex);\n\n                        if (DEBUG) {\n                            MyLog.d(CLS_NAME, \"enabled: \" + enabled);\n                            MyLog.d(CLS_NAME, \"access: \" + access);\n                        }\n\n                        toReturn = new Pair<>(Boolean.parseBoolean(enabled), Boolean.parseBoolean(access));\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"canInteract: cursor failed to move to first?\");\n                        }\n                    }\n\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"canInteract: cursor seek Exception\");\n                        e.printStackTrace();\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"canInteract: cursor empty\");\n                }\n            }\n\n            try {\n                cursor.close();\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"canInteract: cursor close Exception\");\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"canInteract: cursor null\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(TaskerHelper.class.getSimpleName(), then);\n        }\n\n        return toReturn;\n    }\n\n    /**\n     * Check if either of the Tasker packages are installed. The Eclair version is not supported\n     *\n     * @param ctx the application context\n     * @return a {@link Pair} with the first parameter denoting true if a package is installed, false\n     * otherwise and the second the installed package name or null.\n     */\n    public Pair<Boolean, String> isTaskerInstalled(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isTaskerInstalled\");\n        }\n\n        if (Installed.isPackageInstalled(ctx, Installed.PACKAGE_TASKER_DIRECT)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isTaskerInstalled: PACKAGE_TASKER_DIRECT\");\n            }\n            return new Pair<>(true, Installed.PACKAGE_TASKER_DIRECT);\n        }\n\n        if (Installed.isPackageInstalled(ctx, Installed.PACKAGE_TASKER_MARKET)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isTaskerInstalled: PACKAGE_TASKER_MARKET\");\n            }\n            return new Pair<>(true, Installed.PACKAGE_TASKER_MARKET);\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    /**\n     * Execute a tasker task of the supplied name\n     *\n     * @param ctx      the application context\n     * @param taskName the task name\n     * @return true if the Broadcast is sent successfully, false otherwise.\n     */\n    public boolean executeTask(@NonNull final Context ctx, @NonNull final String taskName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"executeTask\");\n        }\n\n        final TaskerIntent intent = new TaskerIntent(taskName);\n\n        try {\n            ctx.sendBroadcast(intent);\n            return true;\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"executeTask Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Show the external access settings in Tasker\n     *\n     * @param ctx the application context\n     * @return true if the intent was executed successfully, false otherwise\n     */\n    public boolean showTaskerExternalAccess(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showTaskerExternalAccess\");\n        }\n\n        final Intent intent = new Intent(TaskerIntent.getExternalAccessPrefsIntent());\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        try {\n            ctx.startActivity(intent);\n            return true;\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"showTaskerExternalAccess Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if there is an install order permission issue\n     *\n     * @param ctx the application context\n     * @return true if the receiver is reachable, false otherwise\n     */\n    public boolean receiverExists(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"receiverExists\");\n        }\n\n        try {\n            final List<ResolveInfo> receivers = ctx.getPackageManager().queryBroadcastReceivers(\n                    new TaskerIntent(\"\"), 0);\n            return receivers != null && receivers.size() > 0;\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"receiverExists Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Send an Intent to Tasker containing an array list of detected voice data\n     *\n     * @param ctx       the application context\n     * @param voiceData an array list of voice data\n     * @return true if the intent executed successfully, false otherwise\n     */\n    public static boolean broadcastVoiceData(@NonNull final Context ctx, @NonNull final ArrayList<String> voiceData) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"broadcastVoiceData\");\n        }\n\n        final TaskerHelper taskerHelper = new TaskerHelper();\n        final Pair<Boolean, String> taskerPair = taskerHelper.isTaskerInstalled(ctx);\n\n        if (taskerPair.first) {\n\n            final Intent intent = new Intent(IntentConstants.ACTION_SAIY_VOICE_DATA);\n            intent.setPackage(taskerPair.second);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.putStringArrayListExtra(IntentConstants.EXTRA_VOICE_DATA, voiceData);\n\n            try {\n                ctx.startActivity(intent);\n                return true;\n            } catch (final ActivityNotFoundException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"broadcastVoiceData: ActivityNotFoundException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"broadcastVoiceData: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/thirdparty/tasker/TaskerIntent.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.thirdparty.tasker;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.PatternMatcher;\nimport android.os.Process;\nimport android.util.Log;\n\n// Version 1.3.3\n\n// Changelog\n\n// Version 1.3.3\n//   - increased MAX_NO_ARGS to 10\n\n// Version 1.3.2\n// \t- bug setting app arg\n//\t- pulled provider column names out of function\n\n// For usage examples see http://tasker.dinglisch.net/invoketasks.html\n\n/**\n * Created by benrandall76@gmail.com on 11/08/2016.\n */\n\npublic class TaskerIntent extends Intent {\n\n    // 3 Tasker versions\n    public final static String TASKER_PACKAGE = \"net.dinglisch.android.tasker\";\n    public final static String TASKER_PACKAGE_MARKET = TASKER_PACKAGE + \"m\";\n    public final static String TASKER_PACKAGE_CUPCAKE = TASKER_PACKAGE + \"cupcake\";\n\n    // Play Store download URLs\n    public final static String MARKET_DOWNLOAD_URL_PREFIX = \"market://details?id=\";\n    private final static String TASKER_MARKET_URL =  MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_MARKET;\n    private final static String TASKER_MARKET_URL_CUPCAKE = MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_CUPCAKE;\n\n    // Direct-purchase version\n    private final static String TASKER_DOWNLOAD_URL = \"http://tasker.dinglisch.net/download.html\";\n\n    // Intent actions\n    public final static String \tACTION_TASK = TASKER_PACKAGE + \".ACTION_TASK\";\n    public final static String \tACTION_TASK_COMPLETE = TASKER_PACKAGE + \".ACTION_TASK_COMPLETE\";\n    public final static String \tACTION_TASK_SELECT = TASKER_PACKAGE + \".ACTION_TASK_SELECT\";\n\n    // Intent parameters\n    public final static String\tEXTRA_ACTION_INDEX_PREFIX = \"action\";\n    public final static String\tTASK_NAME_DATA_SCHEME = \"task\";\n    public final static String\tEXTRA_TASK_NAME  = \"task_name\";\n    public final static String\tEXTRA_TASK_PRIORITY  = \"task_priority\";\n    public final static String\tEXTRA_SUCCESS_FLAG = \"success\";\n    public final static String\tEXTRA_VAR_NAMES_LIST = \"varNames\";\n    public final static String\tEXTRA_VAR_VALUES_LIST = \"varValues\";\n    public final static String\tEXTRA_TASK_OUTPUT = \"output\";\n\n    // Content provider columns\n    public static final String\tPROVIDER_COL_NAME_EXTERNAL_ACCESS = \"ext_access\";\n    public static final String\tPROVIDER_COL_NAME_ENABLED = \"enabled\";\n\n    // DEPRECATED, use EXTRA_VAR_NAMES_LIST, EXTRA_VAR_VALUES_LIST\n    public final static String\tEXTRA_PARAM_LIST = \"params\";\n\n    // Intent data\n\n    public final static String\tTASK_ID_SCHEME = \"id\";\n\n    // For particular actions\n\n    public final static String\tDEFAULT_ENCRYPTION_KEY= \"default\";\n    public final static String\tENCRYPTED_AFFIX = \"tec\";\n    public final static int\t\tMAX_NO_ARGS = 10;\n\n    // Bundle keys\n    // Only useful for Tasker\n    public final static String\tACTION_CODE = \"action\";\n    public final static String\tAPP_ARG_PREFIX = \"app:\";\n    public final static String\tICON_ARG_PREFIX = \"icn:\";\n    public final static String\tARG_INDEX_PREFIX = \"arg:\";\n    public static final String\tPARAM_VAR_NAME_PREFIX = \"par\";\n\n    // Misc\n    private final static String PERMISSION_RUN_TASKS = TASKER_PACKAGE + \".PERMISSION_RUN_TASKS\";\n\n    private final static String ACTION_OPEN_PREFS = TASKER_PACKAGE + \".ACTION_OPEN_PREFS\";\n    public  final static String EXTRA_OPEN_PREFS_TAB_NO = \"tno\";\n    private final static int\tMISC_PREFS_TAB_NO = 3;  // 0 based\n\n    // To query whether Tasker is enabled and external access is enabled\n    private final static String TASKER_PREFS_URI = \"content://\" + TASKER_PACKAGE + \"/prefs\";\n\n    private final static int\tCUPCAKE_SDK_VERSION = 3;\n\n    // result values for TestSend\n\n    // NotInstalled: Tasker package not found on device\n    // NoPermission: calling app does not have permission PERMISSION_RUN_TASKS\n    // NotEnabled: Tasker is not enabled\n    // AccessBlocked: user prefs disallow external access\n    // NoReceiver: Tasker has not created a listener for external access (probably a Tasker bug)\n    // OK: you should be able to send a task to run. Still need to listen for result\n    //     for e.g. task not found\n\n    public enum Status { NotInstalled, NoPermission, NotEnabled, AccessBlocked, NoReceiver, OK }\n\n    // -------------------------- PRIVATE VARS ---------------------------- //\n\n    private final static String TAG = \"TaskerIntent\";\n\n    private final static String\tEXTRA_INTENT_VERSION_NUMBER = \"version_number\";\n    private final static String\tINTENT_VERSION_NUMBER = \"1.1\";\n\n    // Inclusive values\n    private final static int\tMIN_PRIORITY = 0;\n    private final static int\tMAX_PRIORITY = 10;\n\n    // For generating random names\n    private static Random       rand = new Random();\n\n    // Tracking state\n    private int \t\t\t\tactionCount = 0;\n    private int \t\t\t\targCount;\n\n    // -------------------------- PUBLIC METHODS ---------------------------- //\n\n    public static int getMaxPriority() {\n        return MAX_PRIORITY;\n    }\n\n    public static boolean validatePriority( int pri ) {\n        return (\n                ( pri >= MIN_PRIORITY ) ||\n                        ( pri <= MAX_PRIORITY )\n        );\n    }\n\n    // Tasker has different package names for Play Store and non- versions\n    // for historical reasons\n\n    public static String getInstalledTaskerPackage( Context context ) {\n\n        String foundPackage = null;\n\n        try {\n            context.getPackageManager().getPackageInfo( TASKER_PACKAGE, 0 );\n            foundPackage = TASKER_PACKAGE;\n        }\n        catch ( PackageManager.NameNotFoundException e ) {\n        }\n\n        try {\n            context.getPackageManager().getPackageInfo( TASKER_PACKAGE_MARKET, 0 );\n            foundPackage = TASKER_PACKAGE_MARKET;\n        }\n        catch ( PackageManager.NameNotFoundException e ) {\n        }\n\n        return foundPackage;\n    }\n\n    // test we can send a TaskerIntent to Tasker\n    // use *before* sending an intent\n    // still need to test the *result after* sending intent\n\n    public static Status testStatus( Context c ) {\n\n        Status result;\n\n        if ( ! taskerInstalled( c ) )\n            result = Status.NotInstalled;\n        else if ( ! havePermission( c ) )\n            result = Status.NoPermission;\n        else if ( ! TaskerIntent.prefSet( c, PROVIDER_COL_NAME_ENABLED ) )\n            result = Status.NotEnabled;\n        else if ( ! TaskerIntent.prefSet( c, PROVIDER_COL_NAME_EXTERNAL_ACCESS ) )\n            result = Status.AccessBlocked;\n        else if ( ! new TaskerIntent( \"\" ).receiverExists( c ) )\n            result = Status.NoReceiver;\n        else\n            result = Status.OK;\n\n        return result;\n    }\n\n    // Check if Tasker installed\n\n    public static boolean taskerInstalled( Context context ) {\n        return ( getInstalledTaskerPackage( context ) != null );\n    }\n\n    // Use with startActivity to retrieve Tasker from Android market\n    public static Intent getTaskerInstallIntent( boolean marketFlag ) {\n\n        return new Intent(\n                Intent.ACTION_VIEW,\n                Uri.parse(\n                        marketFlag ?\n                                ( ( SDKVersion() == CUPCAKE_SDK_VERSION ) ? TASKER_MARKET_URL_CUPCAKE : TASKER_MARKET_URL ) :\n                                TASKER_DOWNLOAD_URL\n                )\n        );\n    }\n\n    public static int SDKVersion() {\n        try {\n            Field f = android.os.Build.VERSION.class.getField( \"SDK_INT\" );\n            return f.getInt( null );\n        }\n        catch ( Exception e ) {\n            return CUPCAKE_SDK_VERSION;\n        }\n    }\n\n    public static IntentFilter getCompletionFilter( String taskName ) {\n\n        IntentFilter filter = new IntentFilter( TaskerIntent.ACTION_TASK_COMPLETE );\n\n        filter.addDataScheme( TASK_NAME_DATA_SCHEME );\n        filter.addDataPath( taskName, PatternMatcher.PATTERN_LITERAL );\n\n        return filter;\n    }\n\n    public static Intent getTaskSelectIntent() {\n        return new Intent( ACTION_TASK_SELECT ).\n                setFlags(\n                        Intent.FLAG_ACTIVITY_NO_USER_ACTION |\n                                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |\n                                Intent.FLAG_ACTIVITY_NO_HISTORY\n                );\n    }\n\n    // public access deprecated, use TaskerIntent.testSend() instead\n\n    public static boolean havePermission( Context c ) {\n        return c.checkPermission( PERMISSION_RUN_TASKS, Process.myPid(), Process.myUid() ) ==\n                PackageManager.PERMISSION_GRANTED;\n    }\n\n    // Get an intent that will bring up the Tasker prefs screen with the External Access control(s)\n    // Probably you want to use startActivity or startActivityForResult with it\n\n    public static Intent getExternalAccessPrefsIntent() {\n        return new Intent( ACTION_OPEN_PREFS ).putExtra( EXTRA_OPEN_PREFS_TAB_NO, MISC_PREFS_TAB_NO );\n    }\n\n    // ------------------------------------- INSTANCE METHODS ----------------------------- //\n\n    public TaskerIntent() {\n        super( ACTION_TASK );\n        setRandomData();\n        putMetaExtras( getRandomString() );\n    }\n\n    public TaskerIntent( String taskName ) {\n        super( ACTION_TASK );\n        setRandomData();\n        putMetaExtras( taskName );\n    }\n\n    public TaskerIntent setTaskPriority( int priority ) {\n\n        if ( validatePriority( priority ) )\n            putExtra( EXTRA_TASK_PRIORITY, priority );\n        else\n            Log.e( TAG, \"priority out of range: \" + MIN_PRIORITY + \":\" + MAX_PRIORITY );\n\n        return this;\n    }\n\n    // Sets subsequently %par1, %par2 etc\n    public TaskerIntent addParameter( String value ) {\n\n        int index = 1;\n\n        if ( getExtras().containsKey( EXTRA_VAR_NAMES_LIST ) )\n            index = getExtras().getStringArrayList( EXTRA_VAR_NAMES_LIST ).size() + 1;\n\n        Log.d(TAG, \"index: \" + index );\n\n        addLocalVariable( \"%\" + PARAM_VAR_NAME_PREFIX + index, value );\n\n        return this;\n    }\n\n    // Arbitrary specification of (local) variable names and values\n    public TaskerIntent addLocalVariable( String name, String value ) {\n\n        ArrayList<String> names, values;\n\n        if ( hasExtra( EXTRA_VAR_NAMES_LIST ) ) {\n            names = getStringArrayListExtra( EXTRA_VAR_NAMES_LIST );\n            values = getStringArrayListExtra( EXTRA_VAR_VALUES_LIST );\n        }\n        else {\n            names = new ArrayList<>();\n            values = new ArrayList<>();\n\n            putStringArrayListExtra( EXTRA_VAR_NAMES_LIST, names );\n            putStringArrayListExtra( EXTRA_VAR_VALUES_LIST, values );\n        }\n\n        names.add( name );\n        values.add( value );\n\n        return this;\n    }\n\n    public TaskerIntent addAction( int code ) {\n\n        actionCount++;\n        argCount = 1;\n\n        Bundle actionBundle = new Bundle();\n\n        actionBundle.putInt( ACTION_CODE, code );\n\n        // Add action bundle to intent\n        putExtra( EXTRA_ACTION_INDEX_PREFIX + Integer.toString( actionCount ), actionBundle );\n\n        return this;\n    }\n\n    // string arg\n    public TaskerIntent addArg( String arg ) {\n\n        Bundle b = getActionBundle();\n\n        if ( b != null )\n            b.putString( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );\n\n        return this;\n    }\n\n    // int arg\n    public TaskerIntent addArg( int arg ) {\n        Bundle b = getActionBundle();\n\n        if ( b != null )\n            b.putInt( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );\n\n        return this;\n    }\n\n    // boolean arg\n    public TaskerIntent addArg( boolean arg ) {\n        Bundle b = getActionBundle();\n\n        if ( b != null )\n            b.putBoolean( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );\n\n        return this;\n    }\n\n    // Application arg\n    public TaskerIntent addArg( String pkg, String cls ) {\n        Bundle b = getActionBundle();\n\n        if ( b != null ) {\n            StringBuilder builder = new StringBuilder();\n            builder.append( APP_ARG_PREFIX ).\n                    append( pkg ). append( \",\" ). append( cls );\n            b.putString( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), builder.toString() );\n        }\n\n        return this;\n    }\n\n    public IntentFilter getCompletionFilter() {\n        return getCompletionFilter( getTaskName() );\n    }\n\n    public String getTaskName() {\n        return getStringExtra( EXTRA_TASK_NAME );\n    }\n\n    public boolean receiverExists( Context context ) {\n        List<ResolveInfo> recs = context.getPackageManager().queryBroadcastReceivers( this, 0 );\n        return (\n                ( recs != null ) &&\n                        ( recs.size() > 0 )\n        );\n    }\n\n    // -------------------- PRIVATE METHODS -------------------- //\n\n    private String getRandomString() {\n        return Long.toString( rand.nextLong() );\n    }\n\n    // so that if multiple TaskerIntents are used in PendingIntents there's virtually no\n    // clash chance\n    private void setRandomData() {\n        setData( Uri.parse( TASK_ID_SCHEME + \":\" + getRandomString() ) );\n    }\n\n    private Bundle getActionBundle() {\n\n        Bundle toReturn = null;\n\n        if ( argCount > MAX_NO_ARGS )\n            Log.e( TAG, \"maximum number of arguments exceeded (\" + MAX_NO_ARGS + \")\" );\n        else {\n            String key = EXTRA_ACTION_INDEX_PREFIX + Integer.toString( actionCount );\n\n            if ( this.hasExtra( key ) )\n                toReturn = getBundleExtra( key );\n            else\n                Log.e( TAG, \"no actions added yet\" );\n        }\n\n        return toReturn;\n    }\n\n    private void putMetaExtras( String taskName ) {\n        putExtra( EXTRA_INTENT_VERSION_NUMBER, INTENT_VERSION_NUMBER );\n        putExtra( EXTRA_TASK_NAME, taskName );\n    }\n\n    // for testing that Tasker is enabled and external access is allowed\n\n    private static boolean prefSet( Context context, String col ) {\n\n        String [] proj = new String [] { col };\n\n        Cursor c = context.getContentResolver().query( Uri.parse( TASKER_PREFS_URI ), proj, null, null, null );\n\n        boolean acceptingFlag = false;\n\n        if ( c == null )\n            Log.w( TAG, \"no cursor for \" + TASKER_PREFS_URI );\n        else {\n            c.moveToFirst();\n\n            if ( Boolean.TRUE.toString().equals( c.getString( 0 ) ) )\n                acceptingFlag = true;\n\n            c.close();\n        }\n\n        return acceptingFlag;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/thirdparty/tasker/TaskerTask.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.thirdparty.tasker;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 10/08/2016.\n */\n\npublic class TaskerTask {\n\n    private String projectName;\n    private String taskName;\n\n    public TaskerTask() {\n    }\n\n    public TaskerTask(@NonNull final String projectName, @NonNull final String taskName) {\n        this.projectName = projectName;\n        this.taskName = taskName;\n    }\n\n    public String getProjectName() {\n        return projectName;\n    }\n\n    public void setProjectName(@NonNull final String projectName) {\n        this.projectName = projectName;\n    }\n\n    public String getTaskName() {\n        return taskName;\n    }\n\n    public void setTaskName(@NonNull final String taskName) {\n        this.taskName = taskName;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/SaiyProgressListener.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts;\n\n\nimport android.speech.tts.TextToSpeech;\nimport android.speech.tts.UtteranceProgressListener;\n\n/**\n * Listener for events relating to the progress of an utterance through\n * the synthesis queue. Each utterance is associated with a call to\n * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} with an\n * associated utterance identifier, as per {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.\n * <p>\n * The callbacks specified in this method can be called from multiple threads.\n * <p>\n * Created by benrandall76@gmail.com on 09/03/2016.\n */\npublic abstract class SaiyProgressListener extends UtteranceProgressListener {\n\n    public void onAudioAvailable(final String utteranceId, final byte[] audio) {\n    }\n\n    public void onBeginSynthesis(final String utteranceId, final int sampleRateInHz, final int audioFormat,\n                                 final int channelCount) {\n     }\n\n    /**\n     * Called when an utterance \"starts\" as perceived by the caller. This will\n     * be soon before audio is played back in the case of a {@link TextToSpeech#speak}\n     * or before the first bytes of a file are written to storage in the case\n     * of {@link TextToSpeech#synthesizeToFile}.\n     *\n     * @param utteranceId the utterance ID of the utterance.\n     */\n    public abstract void onStart(String utteranceId);\n\n    /**\n     * Called when an utterance has successfully completed processing.\n     * All audio will have been played back by this point for audible output, and all\n     * output will have been written to disk for file synthesis requests.\n     * <p>\n     * This request is guaranteed to be called after {@link #onStart(String)}.\n     *\n     * @param utteranceId the utterance ID of the utterance.\n     */\n    public abstract void onDone(String utteranceId);\n\n    /**\n     * Called when an error has occurred during processing. This can be called\n     * at any point in the synthesis process. Note that there might be calls\n     * to {@link #onStart(String)} for specified utteranceId but there will never\n     * be a call to both {@link #onDone(String)} and this method for\n     * the same utterance.\n     *\n     * @param utteranceId the utterance ID of the utterance.\n     * @deprecated Use {@link #onError(String, int)} instead\n     */\n    @Deprecated\n    public abstract void onError(String utteranceId);\n\n    /**\n     * Called when an error has occurred during processing. This can be called\n     * at any point in the synthesis process. Note that there might be calls\n     * to {@link #onStart(String)} for specified utteranceId but there will never\n     * be a call to both {@link #onDone(String)} and {@link #onError(String, int)} for\n     * the same utterance. The default implementation calls {@link #onError(String)}.\n     *\n     * @param utteranceId the utterance ID of the utterance.\n     * @param errorCode   one of the ERROR_* codes from {@link TextToSpeech}\n     */\n    @SuppressWarnings(\"deprecation\")\n    public void onError(String utteranceId, int errorCode) {\n        onError(utteranceId);\n    }\n\n    /**\n     * Called when an utterance has been stopped while in progress or flushed from the\n     * synthesis queue. This can happen if a client calls {@link TextToSpeech#stop()}\n     * or uses {@link TextToSpeech#QUEUE_FLUSH} as an argument with the\n     * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.\n     *\n     * @param utteranceId the utterance ID of the utterance.\n     * @param interrupted If true, then the utterance was interrupted while being synthesized\n     *                    and its output is incomplete. If false, then the utterance was flushed\n     *                    before the synthesis started.\n     */\n    public void onStop(String utteranceId, boolean interrupted) {\n    }\n\n    /**\n     * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new\n     * progress listener.\n     */\n    @SuppressWarnings(\"deprecation\")\n    static UtteranceProgressListener from(\n            final TextToSpeech.OnUtteranceCompletedListener listener) {\n        return new UtteranceProgressListener() {\n            @Override\n            public synchronized void onDone(String utteranceId) {\n                listener.onUtteranceCompleted(utteranceId);\n            }\n\n            @Override\n            public void onError(String utteranceId) {\n                listener.onUtteranceCompleted(utteranceId);\n            }\n\n            @Override\n            public void onStart(String utteranceId) {\n                // Left unimplemented, has no equivalent in the old\n                // API.\n            }\n\n            @Override\n            public void onStop(String utteranceId, boolean interrupted) {\n                listener.onUtteranceCompleted(utteranceId);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/SaiyTextToSpeech.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.media.AudioAttributes;\nimport android.media.AudioManager;\nimport android.media.AudioTrack;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Process;\nimport android.provider.Settings;\nimport android.speech.tts.TextToSpeech;\nimport android.speech.tts.UtteranceProgressListener;\nimport android.speech.tts.Voice;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.util.Pair;\nimport android.widget.Toast;\n\nimport com.google.gson.GsonBuilder;\n\nimport java.io.File;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.IllformedLocaleException;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Locale;\nimport java.util.MissingResourceException;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.audio.AudioCompression;\nimport ai.saiy.android.audio.SaiyAudioTrack;\nimport ai.saiy.android.cache.speech.SpeechCacheResult;\nimport ai.saiy.android.database.DBSpeech;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.SelfAwareCache;\nimport ai.saiy.android.service.helper.SelfAwareConditions;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.service.helper.SelfAwareParameters;\nimport ai.saiy.android.tts.attributes.Gender;\nimport ai.saiy.android.tts.helper.SaiyVoice;\nimport ai.saiy.android.tts.helper.TTSDefaults;\nimport ai.saiy.android.tts.sound.SoundEffect;\nimport ai.saiy.android.tts.sound.SoundEffectHelper;\nimport ai.saiy.android.tts.sound.SoundEffectItem;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsLocale;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Due to misbehaving voice engines, it's necessary to subclass the TTS object here and handle extra\n * eventualities that have caused many crashes along the way.\n * <p/>\n * Additionally, handling the try/catch inside the methods keeps other classes tidy and more readable.\n * <p/>\n * Created by benrandall76@gmail.com on 13/03/2016.\n */\npublic class SaiyTextToSpeech extends TextToSpeech {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SaiyTextToSpeech.class.getSimpleName();\n\n    public static final int MAX_UTTERANCE_LENGTH = 500;\n    public static final int MIN_UTTERANCE_LENGTH = 200;\n\n    private static final String ARRAY = \"array\";\n    public static final String ARRAY_FIRST = \"array_first\";\n    public static final String ARRAY_INTERIM = \"array_interim\";\n    public static final String ARRAY_LAST = \"array_last\";\n    public static final String ARRAY_SINGLE = \"array_single\";\n    public static final String ARRAY_DELIMITER = \"~~\";\n\n    private volatile SaiyAudioTrack audioTrack;\n\n    private volatile SaiyProgressListener listener;\n    private volatile String initEngine;\n    private volatile Set<SaiyVoice> saiyVoiceSet;\n    private volatile Set<Voice> defaultVoiceSet;\n\n    private final Context mContext;\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param listener the {@link OnInitListener} to monitor the tts engine\n     */\n    public SaiyTextToSpeech(final Context mContext, final OnInitListener listener) {\n        super(mContext, listener);\n        this.mContext = mContext;\n        init();\n    }\n\n    /**\n     * Constructor\n     *\n     * @param mContext the application context\n     * @param listener the {@link OnInitListener} to monitor the tts engine\n     * @param engine   package name of a requested engine\n     */\n    public SaiyTextToSpeech(final Context mContext, final OnInitListener listener, final String engine) {\n        super(mContext, listener, engine);\n        this.mContext = mContext;\n        init();\n    }\n\n    /**\n     * Set post constructor stuff up\n     */\n    private void init() {\n        initialiseAudioTrack();\n        setAttributes();\n    }\n\n    /**\n     * Set the standard audio attributes for the Text to Speech stream\n     */\n    private void setAttributes() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)\n                    .setUsage(AudioAttributes.USAGE_MEDIA)\n                    .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)\n                    .setLegacyStreamType(AudioManager.STREAM_MUSIC).build());\n        }\n    }\n\n    /**\n     * Initialise our {@link SaiyAudioTrack} object, which will be used for streaming stored utterances.\n     */\n    private void initialiseAudioTrack() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            audioTrack = getAudioTrack();\n        }\n    }\n\n    /**\n     * Helper method to double check the returned {@link SaiyAudioTrack} object hasn't been released\n     * elsewhere.\n     *\n     * @return the {@link SaiyAudioTrack} object, or null it the creation process failed.\n     */\n    private SaiyAudioTrack getAudioTrack() {\n        if (audioTrack == null || audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {\n            audioTrack = SaiyAudioTrack.getSaiyAudioTrack();\n            audioTrack.setListener(listener);\n            return audioTrack;\n        } else {\n            return audioTrack;\n        }\n    }\n\n    /**\n     * Process a Text to Speech utterance request, considering API versions, sound effects, silence\n     * and other possibilities.\n     *\n     * @param text        the utterance\n     * @param queueMode   one of {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}\n     * @param params      the {@link SelfAwareParameters} object\n     * @param utteranceId the utterance id\n     * @return one of {@link #SUCCESS} or {@link Error}\n     */\n    public int speak(@NonNull CharSequence text, final int queueMode,\n                     @NonNull final SelfAwareParameters params, @NonNull final String utteranceId) {\n\n        if (SoundEffectHelper.pSOUND_EFFECT.matcher(text).matches()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"speak: have sound effect\");\n            }\n\n            final Gender gender;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n\n                final SaiyVoice voice = getBoundSaiyVoice();\n\n                if (voice != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speak: have sound effect: voice.getGender(): \" + voice.getGender().name());\n                    }\n                    gender = voice.getGender();\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speak: have sound effect: voice null\");\n                    }\n                    gender = Gender.UNDEFINED;\n                }\n            } else {\n                gender = Gender.UNDEFINED;\n            }\n\n            final SoundEffectHelper helper = new SoundEffectHelper(text, utteranceId, gender);\n            helper.sort();\n            final ArrayList<SoundEffectItem> items = helper.getArray();\n            final ArrayList<String> addedItems = SoundEffectHelper.getAddedItems();\n\n            int result = ERROR;\n            for (final SoundEffectItem item : items) {\n\n                params.setUtteranceId(item.getUtteranceId());\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"item getText: \" + item.getText());\n                    MyLog.i(CLS_NAME, \"item getItemType: \" + item.getItemType());\n                    MyLog.i(CLS_NAME, \"item getUtteranceId: \" + item.getUtteranceId());\n                }\n\n                switch (item.getItemType()) {\n\n                    case SoundEffectItem.SOUND:\n\n                        if (addedItems.contains(item.getText())) {\n                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                                result = playEarcon(item.getText(), QUEUE_ADD, params.getBundle(), item.getUtteranceId());\n                            } else {\n                                result = playEarcon(item.getText(), QUEUE_ADD, params);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"No valid sound effect: \" + item.getText());\n                            }\n                            result = ERROR;\n                        }\n\n                        break;\n                    case SoundEffectItem.SPEECH:\n\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                            result = speak21(item.getText(), QUEUE_ADD, params, item.getUtteranceId());\n                        } else {\n                            result = speak(item.getText(), QUEUE_ADD, params);\n                        }\n                        break;\n                    case SoundEffectItem.SILENCE:\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                            result = playSilentUtterance(SoundEffect.SILENCE_DURATION, QUEUE_ADD,\n                                    item.getUtteranceId());\n                        } else {\n                            result = playSilence(SoundEffect.SILENCE_DURATION, QUEUE_ADD, params);\n                        }\n                        break;\n                }\n            }\n\n            return result;\n        } else {\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                return speak21(text, queueMode, params, utteranceId);\n            } else {\n                return speak(text.toString(), queueMode, params);\n            }\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private int speak21(@NonNull final CharSequence text, final int queueMode,\n                        @NonNull final SelfAwareParameters params, @NonNull final String utteranceId) {\n\n        if (queueMode != QUEUE_ADD && !params.getUtteranceId().startsWith(ARRAY)\n                && canSynthesise(text.toString(), params)) {\n            return SUCCESS;\n        } else {\n            if (text.length() > getMaxUtteranceLength()) {\n\n                final ArrayList<String> splitUtterances = SelfAwareHelper.splitUtteranceRegex(\n                        text.toString(), getMaxUtteranceLength());\n\n                final int splitUtterancesSize = splitUtterances.size();\n\n                if (splitUtterancesSize > 1) {\n\n                    final boolean overrideId = utteranceId.contains(ARRAY_DELIMITER);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speak21: SU: \" + utteranceId);\n                        MyLog.i(CLS_NAME, \"speak21: SU override: \" + overrideId);\n                    }\n\n                    for (int i = 0; i < splitUtterancesSize; i++) {\n\n                        final String resolvedUtteranceId = resolveUtteranceId(utteranceId, splitUtterancesSize,\n                                i, overrideId);\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"speak21: SU resolvedUtteranceId: \" + resolvedUtteranceId);\n                        }\n\n                        params.setUtteranceId(resolvedUtteranceId);\n\n                        if (i == (splitUtterancesSize - 1)) {\n                            return super.speak(splitUtterances.get(i), QUEUE_ADD, params.getBundle(),\n                                    params.getUtteranceId());\n                        } else {\n                            super.speak(splitUtterances.get(i), QUEUE_ADD, params.getBundle(),\n                                    params.getUtteranceId());\n                        }\n                    }\n\n                } else {\n                    return super.speak(text, queueMode, params.getBundle(), utteranceId);\n                }\n            } else {\n                return super.speak(text, queueMode, params.getBundle(), utteranceId);\n            }\n        }\n\n        return super.speak(text, queueMode, params.getBundle(), utteranceId);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int speak(final String text, final int queueMode, final HashMap<String, String> map) {\n\n        if (text.length() > getMaxUtteranceLength()) {\n\n            final ArrayList<String> splitUtterances = SelfAwareHelper.splitUtteranceRegex(text,\n                    getMaxUtteranceLength());\n\n            final int splitUtterancesSize = splitUtterances.size();\n\n            if (splitUtterancesSize > 1) {\n\n                final String utteranceId = map.get(Engine.KEY_PARAM_UTTERANCE_ID);\n                final boolean overrideId = utteranceId.contains(ARRAY_DELIMITER);\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"speak: SU: \" + utteranceId);\n                    MyLog.i(CLS_NAME, \"speak: SU override: \" + overrideId);\n                }\n\n                for (int i = 0; i < splitUtterancesSize; i++) {\n\n                    final String resolvedUtteranceId = resolveUtteranceId(utteranceId, splitUtterancesSize,\n                            i, overrideId);\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"speak: SU resolvedUtteranceId: \" + resolvedUtteranceId);\n                    }\n\n                    map.put(Engine.KEY_PARAM_UTTERANCE_ID, resolvedUtteranceId);\n\n                    if (i == (splitUtterancesSize - 1)) {\n                        return super.speak(splitUtterances.get(i), QUEUE_ADD, map);\n                    } else {\n                        super.speak(splitUtterances.get(i), QUEUE_ADD, map);\n                    }\n                }\n\n            } else {\n                return super.speak(text, queueMode, map);\n            }\n        } else {\n            return super.speak(text, queueMode, map);\n        }\n\n        return super.speak(text, queueMode, map);\n    }\n\n    private String resolveUtteranceId(@NonNull final String utteranceId, final int arraySize, final int position,\n                                      final boolean overrideId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolveUtteranceId: utteranceId: \" + utteranceId);\n            MyLog.i(CLS_NAME, \"resolveUtteranceId: arraySize: \" + arraySize);\n            MyLog.i(CLS_NAME, \"resolveUtteranceId: position: \" + position);\n            MyLog.i(CLS_NAME, \"resolveUtteranceId: overrideId: \" + overrideId);\n        }\n\n        if (position == (arraySize - 1)) {\n\n            if (overrideId) {\n\n                if (utteranceId.startsWith(ARRAY_INTERIM) || utteranceId.startsWith(ARRAY_LAST)) {\n                    return utteranceId;\n                } else {\n                    return ARRAY_INTERIM + ARRAY_DELIMITER + TextUtils.split(utteranceId, ARRAY_DELIMITER)[1];\n                }\n\n            } else {\n                return ARRAY_LAST + ARRAY_DELIMITER + utteranceId;\n            }\n\n        } else {\n\n            switch (position) {\n\n                case 0:\n\n                    if (overrideId) {\n\n                        if (utteranceId.startsWith(ARRAY_FIRST) || utteranceId.startsWith(ARRAY_INTERIM)) {\n                            return utteranceId;\n                        } else {\n                            return ARRAY_INTERIM + ARRAY_DELIMITER + TextUtils.split(utteranceId, ARRAY_DELIMITER)[1];\n                        }\n\n                    } else {\n                        return ARRAY_FIRST + ARRAY_DELIMITER + utteranceId;\n                    }\n\n                default:\n\n                    if (overrideId) {\n\n                        if (utteranceId.startsWith(ARRAY_INTERIM)) {\n                            return utteranceId;\n                        } else {\n                            return ARRAY_INTERIM + ARRAY_DELIMITER + TextUtils.split(utteranceId, ARRAY_DELIMITER)[1];\n                        }\n\n                    } else {\n                        return ARRAY_INTERIM + ARRAY_DELIMITER + utteranceId;\n                    }\n\n            }\n        }\n    }\n\n    /**\n     * Method to check if synthesis exists in the {@link DBSpeech} for the pending utterance.\n     *\n     * @param utterance the pending utterance\n     * @param voiceName the name of the current Text to Speech Voice\n     * @return true if the audio data is available to stream. False otherwise.\n     */\n    private boolean synthesisAvailable(@NonNull final String utterance, @NonNull final String voiceName) {\n\n        final DBSpeech dbSpeech = new DBSpeech(mContext);\n        final SpeechCacheResult speechCacheResult = dbSpeech.getBytes(getInitialisedEngine(),\n                voiceName, utterance);\n\n        if (speechCacheResult.isSuccess()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"synthesisAvailable: true\");\n            }\n            return true;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"synthesisAvailable: getBytes failed or speech does not exist\");\n            }\n\n            if (speechCacheResult.getRowId() > -1) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"synthesisAvailable: speech does not exist\");\n                }\n                AsyncTask.execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);\n                        dbSpeech.deleteEntry(speechCacheResult.getRowId());\n                    }\n                });\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Method to check if synthesis exists in the {@link DBSpeech} for the pending utterance. If so,\n     * the byte[] is pulled from the database and streamed using the {@link AudioTrack}, rather\n     * than than via the Text to Speech engine.\n     *\n     * @param utterance the pending utterance\n     * @param params    the {@link SelfAwareParameters}\n     * @return true if the audio data is available to stream. False otherwise.\n     */\n    private boolean canSynthesise(@NonNull final String utterance, @NonNull final SelfAwareParameters params) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            if (getAudioTrack() != null) {\n                if (TTSDefaults.isApprovedVoice(getInitialisedEngine())) {\n                    if (utterance.length() < SelfAwareCache.MAX_UTTERANCE_CHARS) {\n                        if (!utterance.matches(SaiyRequestParams.SILENCE)) {\n                            if (UtilsString.notNaked(getInitialisedEngine())) {\n\n                                final SaiyVoice voice = getBoundSaiyVoice();\n\n                                if (voice != null) {\n\n                                    final DBSpeech dbSpeech = new DBSpeech(mContext);\n                                    final SpeechCacheResult speechCacheResult = dbSpeech.getBytes(getInitialisedEngine(),\n                                            voice.getName(), utterance);\n\n                                    if (speechCacheResult.isSuccess()) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"canSynthesise: true\");\n                                        }\n\n                                        final byte[] uncompressedBytes = AudioCompression.decompressBytes(mContext,\n                                                speechCacheResult.getCompressedBytes(), speechCacheResult.getRowId());\n\n                                        if (UtilsList.notNaked(uncompressedBytes)) {\n                                            startSynthesis(params, uncompressedBytes);\n                                            return true;\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"canSynthesise: getBytes empty or null\");\n                                            }\n                                        }\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"canSynthesise: getBytes failed or speech does not exist\");\n                                        }\n\n                                        if (speechCacheResult.getRowId() > -1) {\n                                            AsyncTask.execute(new Runnable() {\n                                                @Override\n                                                public void run() {\n                                                    Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);\n                                                    dbSpeech.deleteEntry(speechCacheResult.getRowId());\n                                                }\n                                            });\n                                        }\n                                    }\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"canSynthesise: false: saiyVoice null\");\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"canSynthesise: false: getBytes engine naked\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"canSynthesise: false: silence\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"canSynthesise: false: length > MAX_UTTERANCE_CHARS\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"canSynthesise: false: not approved engine\");\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"canSynthesise: false: audioTrack null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"canSynthesise: false: < LOLLIPOP\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Begin streaming the byte[] of pcm audio data via the {@link AudioTrack} object\n     *\n     * @param uncompressedBytes to stream\n     * @param params            the {@link SelfAwareParameters}\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private void startSynthesis(@NonNull final SelfAwareParameters params,\n                                @NonNull final byte[] uncompressedBytes) {\n        audioTrack.setListener(listener);\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);\n                audioTrack.setVolume(params.getVolume());\n                audioTrack.enqueue(uncompressedBytes, params.getUtteranceId());\n            }\n        });\n    }\n\n    /**\n     * Add the inbuilt array of sound effect that can be played by the engine on demand\n     */\n    private void addSoundEffects() {\n\n        final ArrayList<String> addedItems = new ArrayList<>();\n        final TypedArray typedArray = mContext.getResources().obtainTypedArray(R.array.array_se);\n\n        int temp;\n        String fileName;\n        for (int i = 0; i < typedArray.length(); i++) {\n\n            temp = typedArray.getResourceId(i, -1);\n\n            if (temp > 0) {\n\n                try {\n\n                    fileName = mContext.getResources().getResourceEntryName(temp);\n\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"addSoundEffects: fileName: \" + fileName);\n                    }\n\n                    switch (addEarcon(fileName, mContext.getPackageName(), temp)) {\n\n                        case SUCCESS:\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"addSoundEffects: fileName: SUCCESS: \" + fileName);\n                            }\n                            addedItems.add(fileName);\n                            break;\n                        case ERROR:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"addSoundEffects: fileName: ERROR: \" + fileName);\n                            }\n                            break;\n                    }\n\n                } catch (final Resources.NotFoundException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"addSoundEffects: Resources.NotFoundException: \" + temp);\n                        e.printStackTrace();\n                    }\n                }\n            }\n        }\n\n        typedArray.recycle();\n        SoundEffectHelper.setAddedItems(addedItems);\n    }\n\n    @Override\n    public int addEarcon(final String earcon, final File file) {\n        return super.addEarcon(earcon, file);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int addEarcon(final String earcon, final String filename) {\n        return super.addEarcon(earcon, filename);\n    }\n\n    @Override\n    public int addEarcon(final String earcon, final String packageName, final int resourceId) {\n        return super.addEarcon(earcon, packageName, resourceId);\n    }\n\n    @Override\n    public int addSpeech(final CharSequence text, final File file) {\n        return super.addSpeech(text, file);\n    }\n\n    @Override\n    public int addSpeech(final CharSequence text, final String packageName,\n                         final int resourceId) {\n        return super.addSpeech(text, packageName, resourceId);\n    }\n\n    @Override\n    public int addSpeech(final String text, final String filename) {\n        return super.addSpeech(text, filename);\n    }\n\n    @Override\n    public int addSpeech(final String text, final String packageName, final int resourceId) {\n        return super.addSpeech(text, packageName, resourceId);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public boolean areDefaultsEnforced() {\n        return super.areDefaultsEnforced();\n    }\n\n    @Override\n    public Set<Locale> getAvailableLanguages() {\n        return super.getAvailableLanguages();\n    }\n\n    @Override\n    public String getDefaultEngine() {\n\n        String packageName = \"\";\n\n        try {\n            packageName = super.getDefaultEngine();\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getDefaultEngineSecure: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getDefaultEngineSecure: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return packageName;\n    }\n\n    /**\n     * for some TTS Engines, the {@link #getDefaultEngine()} is very slow - here we jump straight\n     * to the Secure Settings to access it. If an exception is thrown, we revert to the latter.\n     *\n     * @return the user default TTS Engine\n     */\n\n    private String getDefaultEngineSecure() {\n\n        try {\n\n            final String packageName = Settings.Secure.getString(mContext.getContentResolver(),\n                    Settings.Secure.TTS_DEFAULT_SYNTH);\n\n            if (packageName != null) {\n                if (DEBUG) {\n                    MyLog.v(CLS_NAME, \"getDefaultEngineSecure: Secure: \" + packageName);\n                }\n\n                return packageName;\n            }\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getDefaultEngineSecure: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getDefaultEngineSecure: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return getDefaultEngine();\n    }\n\n    /**\n     * Compare the current initialised TTS Engine with the user's default selection. If this has\n     * changed since initialisation occurred, we need to restart the Engine, to bind to the new\n     * default choice.\n     *\n     * @param packageName supplied if we want to compare a specific Engine\n     * @return true if the default Engine does not match the initialised Engine\n     */\n    public boolean shouldReinitialise(final String packageName) {\n\n        final String initialisedEngine = getInitialisedEngine();\n\n        if (!UtilsString.notNaked(initialisedEngine)) {\n            return true;\n        }\n\n        if (packageName != null) {\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"shouldReinitialise: comparing \" + initialisedEngine\n                        + \" ~ \" + packageName);\n            }\n            return !packageName.matches(initialisedEngine);\n        } else {\n\n            final String defaultEngine = getDefaultEngineSecure();\n\n            if (DEBUG) {\n                MyLog.v(CLS_NAME, \"shouldReinitialise: comparing \" + defaultEngine\n                        + \" ~ \" + initialisedEngine);\n            }\n\n            return !UtilsString.notNaked(defaultEngine) || !defaultEngine.matches(initialisedEngine);\n\n        }\n    }\n\n    /**\n     * Check if the user has the Google TTS Engine installed\n     *\n     * @param ctx the application context\n     * @return true if the Google TTS Engine is installed\n     */\n    public static boolean isGoogleTTSAvailable(final Context ctx) {\n        try {\n            ctx.getApplicationContext().getPackageManager().getApplicationInfo(TTSDefaults.TTS_PKG_NAME_GOOGLE, 0);\n            return true;\n        } catch (final PackageManager.NameNotFoundException e) {\n            return false;\n        }\n    }\n\n    /**\n     * Limit of length of input string passed to speak and synthesizeToFile.\n     *\n     * @see #speak\n     * @see #synthesizeToFile\n     */\n    private int getMaxUtteranceLength() {\n\n        if (getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_GOOGLE)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getMaxUtteranceLength: TTS_PKG_NAME_GOOGLE\");\n            }\n        } else if (getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_IVONA)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getMaxUtteranceLength: TTS_PKG_NAME_IVONA\");\n            }\n        } else if (getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_CEREPROC)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getMaxUtteranceLength: TTS_PKG_NAME_CEREPROC\");\n            }\n        } else if (getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_PICO)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getMaxUtteranceLength: TTS_PKG_NAME_PICO\");\n            }\n        } else if (getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_SVOX)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getMaxUtteranceLength: TTS_PKG_NAME_SVOX\");\n            }\n        }\n\n        return MAX_UTTERANCE_LENGTH;\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public Locale getDefaultLanguage() {\n        return super.getDefaultLanguage();\n    }\n\n    @Override\n    public List<EngineInfo> getEngines() {\n        return super.getEngines();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public Set<String> getFeatures(final Locale locale) {\n        return super.getFeatures(locale);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public Locale getLanguage() {\n        return super.getLanguage();\n    }\n\n    @Override\n    public Voice getDefaultVoice() {\n        return super.getDefaultVoice();\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private SaiyVoice getUserDefaultSaiyVoice() {\n\n        final String userDefaultSaiyVoiceString = SPH.getDefaultTTSVoice(mContext);\n\n        if (UtilsString.notNaked(userDefaultSaiyVoiceString)) {\n\n            final SaiyVoice userDefaultSaiyVoice = new GsonBuilder().disableHtmlEscaping().create().fromJson(\n                    userDefaultSaiyVoiceString, SaiyVoice.class);\n\n            if (userDefaultSaiyVoice != null) {\n                return userDefaultSaiyVoice;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"userDefaultSaiyVoiceString: naked\");\n            }\n        }\n\n        return null;\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private SaiyVoice getEngineDefaultSaiyVoice() {\n\n        final Voice voice = getDefaultVoice();\n\n        if (voice != null) {\n            final SaiyVoice saiyVoice = new SaiyVoice(voice);\n            saiyVoice.setEngine(getInitialisedEngine());\n            saiyVoice.setGender(saiyVoice.getName());\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getEngineDefaultSaiyVoice: setting Gender: \" + saiyVoice.getGender().name());\n            }\n\n            return saiyVoice;\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getEngineDefaultSaiyVoice: voice null\");\n            }\n            return null;\n        }\n    }\n\n    @Override\n    public Voice getVoice() {\n        return super.getVoice();\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    public SaiyVoice getBoundSaiyVoice() {\n\n        final Voice voice = getVoice();\n\n        if (voice != null) {\n            final SaiyVoice saiyVoice = new SaiyVoice(voice);\n            saiyVoice.setEngine(getInitialisedEngine());\n            saiyVoice.setGender(saiyVoice.getName());\n            return saiyVoice;\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    public Set<Voice> getVoices() {\n        final long then = System.nanoTime();\n\n        if (defaultVoiceSet == null || defaultVoiceSet.isEmpty()) {\n            defaultVoiceSet = super.getVoices();\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getVoices: already prepared\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(\"getVoices\", then);\n        }\n\n        return defaultVoiceSet;\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private Set<SaiyVoice> getSaiyVoices() {\n        final long then = System.nanoTime();\n        final Set<Voice> voiceSet = getVoices();\n\n        if (saiyVoiceSet == null || saiyVoiceSet.size() != voiceSet.size()) {\n            saiyVoiceSet = SaiyVoice.getSaiyVoices(voiceSet, getInitialisedEngine());\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getSaiyVoices: already prepared\");\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.getElapsed(\"getSaiyVoices\", then);\n        }\n        return saiyVoiceSet;\n    }\n\n    @Override\n    public int isLanguageAvailable(final Locale loc) {\n\n        int result = LANG_NOT_SUPPORTED;\n\n        try {\n            result = super.isLanguageAvailable(loc);\n        } catch (final IllegalArgumentException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"isLanguageAvailable: IllegalArgumentException: \"\n                        + loc.toString());\n                e.printStackTrace();\n            }\n        }\n\n        return result;\n    }\n\n    @Override\n    public boolean isSpeaking() {\n\n        if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"isSpeaking: audioTrack STATE_INITIALIZED\");\n            }\n\n            if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING\n                    || audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"isSpeaking: audioTrack PLAYSTATE_PLAYING/PLAYSTATE_PAUSED\");\n                }\n                return true;\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"isSpeaking: audioTrack not playing\");\n                }\n            }\n        }\n\n        final boolean speakingSuper = super.isSpeaking();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"isSpeaking: speakingSuper \" + speakingSuper);\n        }\n\n        return speakingSuper;\n    }\n\n    @Override\n    public int playEarcon(final String earcon, final int queueMode, final Bundle params, final String utteranceId) {\n        return super.playEarcon(earcon, queueMode, params, utteranceId);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int playEarcon(final String earcon, final int queueMode, final HashMap<String, String> params) {\n        return super.playEarcon(earcon, queueMode, params);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int playSilence(final long durationInMs, final int queueMode, final HashMap<String, String> params) {\n        return super.playSilence(durationInMs, queueMode, params);\n    }\n\n    @Override\n    public int playSilentUtterance(final long durationInMs, final int queueMode, final String utteranceId) {\n        return super.playSilentUtterance(durationInMs, queueMode, utteranceId);\n    }\n\n    @Override\n    public int setAudioAttributes(final AudioAttributes audioAttributes) {\n        return super.setAudioAttributes(audioAttributes);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int setEngineByPackageName(final String enginePackageName) {\n        return super.setEngineByPackageName(enginePackageName);\n    }\n\n    @Override\n    public int setLanguage(final Locale loc) {\n        return super.setLanguage(loc);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {\n        return super.setOnUtteranceCompletedListener(listener);\n    }\n\n    @Override\n    public int setOnUtteranceProgressListener(final UtteranceProgressListener listener) {\n        this.listener = (SaiyProgressListener) listener;\n        return super.setOnUtteranceProgressListener(listener);\n    }\n\n    @Override\n    public int setPitch(final float pitch) {\n        return super.setPitch(pitch);\n    }\n\n    @Override\n    public int setSpeechRate(final float speechRate) {\n        return super.setSpeechRate(speechRate);\n    }\n\n    @Override\n    public int setVoice(final Voice voice) {\n        return super.setVoice(voice);\n    }\n\n    /**\n     * Set the voice of the TTS object. API levels are handled here.\n     *\n     * @param language   the {@link Locale} language\n     * @param region     the {@link Locale} region\n     * @param conditions the {@link SelfAwareConditions}\n     * @param params     the {@link SelfAwareParameters}\n     * @return one of {@link #SUCCESS} or {@link #ERROR}\n     */\n    public int setVoice(@NonNull final String language, @NonNull final String region,\n                        @NonNull final SelfAwareConditions conditions, @NonNull final SelfAwareParameters params) {\n\n        int result;\n\n        final String initialisedEngine = getInitialisedEngine();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setVoice: initialisedEngine: \" + initialisedEngine);\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP\n                && TTSDefaults.isApprovedVoice(getInitialisedEngine())) {\n\n            result = setVoice21(language, region, conditions, params);\n\n            if (DEBUG) {\n                switch (result) {\n                    case SUCCESS:\n                        MyLog.i(CLS_NAME, \"setVoice21: SUCCESS\");\n                        break;\n                    case ERROR:\n                        MyLog.w(CLS_NAME, \"setVoice21: ERROR\");\n                        break;\n                    default:\n                        MyLog.w(CLS_NAME, \"setVoice21: default\");\n                        break;\n                }\n            }\n\n            if (result != SUCCESS) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setVoice21: FAIL: notifying user\");\n                }\n                conditions.showToast(mContext.getString(R.string.error_tts_voice), Toast.LENGTH_SHORT);\n            }\n\n            return result;\n        } else {\n\n            result = setVoiceDeprecated(language, region);\n\n            if (DEBUG) {\n                switch (result) {\n                    case SUCCESS:\n                        MyLog.i(CLS_NAME, \"setVoiceDeprecated: SUCCESS\");\n                        break;\n                    case ERROR:\n                        MyLog.w(CLS_NAME, \"setVoiceDeprecated: ERROR\");\n                        break;\n                    default:\n                        MyLog.w(CLS_NAME, \"setVoiceDeprecated: default\");\n                        break;\n                }\n            }\n\n            if (result != SUCCESS) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setVoiceDeprecated: FAIL: notifying user\");\n                }\n                conditions.showToast(mContext.getString(R.string.error_tts_voice), Toast.LENGTH_SHORT);\n            }\n\n            return result;\n        }\n    }\n\n    /**\n     * Automatically select the user's default voice.\n     *\n     * @param language   the {@link Locale} language\n     * @param region     the {@link Locale} region\n     * @param conditions the {@link SelfAwareConditions}\n     * @param params     the {@link SelfAwareParameters}\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private void setDefaultVoice(@NonNull final String language, @NonNull final String region,\n                                 @NonNull final SelfAwareConditions conditions,\n                                 @NonNull final SelfAwareParameters params) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setDefaultVoice\");\n        }\n\n        final Pair<SaiyVoice, Locale> voicePair = new TTSVoice(language, region, conditions, params, null)\n                .buildVoice();\n\n        if (voicePair.first != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setDefaultVoice: Setting Voice: \" + voicePair.first.toString());\n            }\n\n            final SaiyVoice saiyVoice = new SaiyVoice(voicePair.first);\n            saiyVoice.setEngine(getInitialisedEngine());\n            saiyVoice.setGender(saiyVoice.getName());\n\n            final String gsonString = new GsonBuilder().disableHtmlEscaping().create().toJson(saiyVoice);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setDefaultVoice: gsonString: \" + gsonString);\n            }\n\n            SPH.setDefaultTTSVoice(mContext, gsonString);\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setDefaultVoice: Unable to establish a default voice\");\n            }\n        }\n    }\n\n    /**\n     * Set the voice of the TTS object.\n     *\n     * @param language   the {@link Locale} language\n     * @param region     the {@link Locale} region\n     * @param conditions the {@link SelfAwareConditions}\n     * @param params     the {@link SelfAwareParameters}\n     * @return one of {@link #SUCCESS} or {@link #ERROR}\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private int setVoice21(@NonNull final String language, @NonNull final String region,\n                           @NonNull final SelfAwareConditions conditions, @NonNull final SelfAwareParameters params) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setVoice21\");\n        }\n\n        final boolean isNetworkAvailable = params.shouldNetwork();\n\n        SaiyVoice userDefaultSaiyVoice = null;\n\n        try {\n\n            switch (conditions.getCondition()) {\n\n                case Condition.CONDITION_TRANSLATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"setVoice21: CONDITION_TRANSLATION\");\n                    }\n                    break;\n                default:\n\n                    userDefaultSaiyVoice = getUserDefaultSaiyVoice();\n\n                    if (userDefaultSaiyVoice == null) {\n                        AsyncTask.execute(new Runnable() {\n                            @Override\n                            public void run() {\n                                SaiyTextToSpeech.this.setDefaultVoice(language, region, conditions, params);\n                            }\n                        });\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"setVoice21: userDefaultSaiyVoice: \" + userDefaultSaiyVoice.toString());\n                        }\n\n                        boolean exists = false;\n                        for (final SaiyVoice voice : getSaiyVoices()) {\n                            if (userDefaultSaiyVoice.getName().matches(\"(?i)\" + Pattern.quote(voice.getName()))) {\n                                exists = true;\n                                break;\n                            }\n                        }\n\n                        if (!exists) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: defaultSaiyVoice: no longer exists\");\n                            }\n                            userDefaultSaiyVoice = null;\n                            SPH.setDefaultTTSVoice(mContext, null);\n                            AsyncTask.execute(new Runnable() {\n                                @Override\n                                public void run() {\n                                    SaiyTextToSpeech.this.setDefaultVoice(language, region, conditions, params);\n                                }\n                            });\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: defaultSaiyVoice: exists\");\n                            }\n                        }\n                    }\n\n                    break;\n            }\n\n            SaiyVoice boundSaiyVoice = getBoundSaiyVoice();\n\n            if (boundSaiyVoice == null) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice null\");\n                }\n                return setVoiceDeprecated(language, region);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice: \" + boundSaiyVoice.toString());\n                    MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice matches default: \" + boundSaiyVoice.equals(userDefaultSaiyVoice));\n                    MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice Locale: \" + boundSaiyVoice.getLocale().toString());\n                    MyLog.i(CLS_NAME, \"setVoice21: Required Locale: \" + language + \" ~ \" + region);\n                }\n            }\n\n            switch (conditions.getCondition()) {\n\n                case Condition.CONDITION_TRANSLATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"setVoice21: CONDITION_TRANSLATION\");\n                    }\n                    break;\n                default:\n\n                    if (userDefaultSaiyVoice != null) {\n                        if (!boundSaiyVoice.equals(userDefaultSaiyVoice)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: userDefaultSaiyVoice engine: \" + userDefaultSaiyVoice.getEngine());\n                                MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice engine: \" + boundSaiyVoice.getEngine());\n                            }\n\n                            if (userDefaultSaiyVoice.getEngine().matches(boundSaiyVoice.getEngine())) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"setVoice21: userDefaultSaiyVoice & boundSaiyVoice engines match\");\n                                }\n\n                                if (isNetworkAvailable) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"setVoice21: engines match: network available: setting default\");\n                                    }\n                                    setVoice(userDefaultSaiyVoice);\n                                    boundSaiyVoice = getBoundSaiyVoice();\n                                } else {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"setVoice21: engines match: no network\");\n                                    }\n\n                                    if (!userDefaultSaiyVoice.isNetworkConnectionRequired()) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"setVoice21: engines match: userDefaultSaiyVoice network not needed\");\n                                        }\n                                        setVoice(userDefaultSaiyVoice);\n                                        boundSaiyVoice = getBoundSaiyVoice();\n                                    } else {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"setVoice21: engines match: userDefaultSaiyVoice requires network\");\n                                        }\n\n                                        final String utterance = conditions.getUtterance();\n\n                                        if (UtilsString.notNaked(utterance) && !utterance.matches(SaiyRequestParams.SILENCE)) {\n\n                                            if (synthesisAvailable(conditions.getUtterance(), userDefaultSaiyVoice.getName())) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"setVoice21: synthesis cached: SUCCESS\");\n                                                }\n                                                setVoice(userDefaultSaiyVoice);\n                                                return SUCCESS;\n                                            } else {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"setVoice21: no synthesis cache\");\n                                                }\n                                            }\n                                        } else {\n                                            if (DEBUG) {\n                                                MyLog.i(CLS_NAME, \"setVoice21: engine warm up only\");\n                                            }\n                                            return SUCCESS;\n                                        }\n                                    }\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"setVoice21: userDefaultSaiyVoice & boundSaiyVoice engines don't match.\");\n                                }\n\n                                saiyVoiceSet = null;\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: boundSaiyVoice and userDefaultSaiyVoice match\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"setVoice21: defaultSaiyVoice: null\");\n                        }\n                        SPH.setDefaultTTSVoice(mContext, null);\n                    }\n\n                    break;\n            }\n\n            if (!UtilsLocale.localesLanguageMatch(boundSaiyVoice.getLocale(), new Locale(language, region))) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setVoice21: locales don't match\");\n                }\n                return resolveVoice(language, region, conditions, params, boundSaiyVoice);\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setVoice21: locales match\");\n                }\n\n                if (boundSaiyVoice.isNetworkConnectionRequired()) {\n                    if (isNetworkAvailable) {\n                        if (SPH.getNetworkSynthesis(mContext)) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: network required: SUCCESS\");\n                            }\n                            return SUCCESS;\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: user no network\");\n                            }\n                            return resolveVoice(language, region, conditions, params, boundSaiyVoice);\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"setVoice21: network unavailable\");\n                        }\n\n                        final String utterance = conditions.getUtterance();\n\n                        if (UtilsString.notNaked(utterance) && !utterance.matches(SaiyRequestParams.SILENCE)) {\n\n                            if (synthesisAvailable(conditions.getUtterance(), boundSaiyVoice.getName())) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"setVoice21: synthesis cached: SUCCESS\");\n                                }\n                                return SUCCESS;\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"setVoice21: no synthesis cache\");\n                                }\n                                return resolveVoice(language, region, conditions, params, boundSaiyVoice);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"setVoice21: engine warm up only\");\n                            }\n                            return SUCCESS;\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"setVoice21: no network required: SUCCESS\");\n                    }\n                    return SUCCESS;\n                }\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setVoice21 NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final MissingResourceException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setVoice21 MissingResourceException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"setVoice21 Exception\");\n                e.printStackTrace();\n            }\n            if (e instanceof IllformedLocaleException) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"setVoice21 IllformedLocaleException\");\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setVoice21: falling back to setVoiceDeprecated\");\n        }\n\n        return setVoiceDeprecated(language, region);\n    }\n\n    /**\n     * Attempt to resolve the voice that most suits the conditions and the user's preferences.\n     *\n     * @param language   the {@link Locale} language\n     * @param region     the {@link Locale} region\n     * @param conditions the {@link SelfAwareConditions}\n     * @param params     the {@link SelfAwareParameters}\n     * @return one of {@link #SUCCESS} or {@link #ERROR}\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private int resolveVoice(@NonNull final String language, @NonNull final String region,\n                             @NonNull final SelfAwareConditions conditions, @NonNull final SelfAwareParameters params,\n                             @Nullable final SaiyVoice currentVoice) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolveVoice\");\n        }\n\n        final Pair<SaiyVoice, Locale> voicePair = new TTSVoice(language, region, conditions, params,\n                currentVoice).buildVoice();\n\n        if (voicePair.first != null) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"resolveVoice: Setting Voice: \" + voicePair.first.toString());\n                MyLog.i(CLS_NAME, \"resolveVoice: Setting Voice loc: \" + voicePair.first.getLocale());\n                try {\n                    MyLog.i(CLS_NAME, \"resolveVoice: Setting Voice: isLanguageAvailable: \"\n                            + resolveSuccess(isLanguageAvailable(new Locale(voicePair.first.getLocale().getLanguage(),\n                            voicePair.first.getLocale().getCountry()))));\n                } catch (final MissingResourceException e) {\n                    MyLog.w(CLS_NAME, \"MissingResourceException: isLanguageAvailable failed\");\n                    e.printStackTrace();\n                }\n            }\n\n            return super.setVoice(voicePair.first);\n        } else {\n            if (voicePair.second != null) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"resolveVoice: Setting Locale deprecated\");\n                }\n                return setVoiceDeprecated(voicePair.second.getLanguage(), voicePair.second.getCountry());\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"resolveVoice: voicePair.second null: falling back\");\n                }\n                return resolveSuccess(super.setLanguage(new Locale(language, region)));\n            }\n        }\n    }\n\n    /**\n     * Set the voice/language of the TTS object for lower API levels, or if the current engine does\n     * not support the latest APIs.\n     *\n     * @param language the {@link Locale} language\n     * @param region   the {@link Locale} region\n     * @return one of {@link #SUCCESS} or {@link #ERROR}\n     */\n\n    private int setVoiceDeprecated(@NonNull final String language, @NonNull final String region) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setVoiceDeprecated: Setting Locale\");\n        }\n\n        try {\n\n            Locale locale = new Locale(language, region);\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"setVoiceDeprecated: comparing current: \" + getLanguage()\n                        + \" with \" + locale.toString());\n            }\n\n            if (!UtilsLocale.localesMatch(getLanguage(), locale)) {\n\n                try {\n                    locale.getCountry();\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"setVoiceDeprecated: isLanguageAvailable: \"\n                                + resolveSuccess(super.isLanguageAvailable(new Locale(locale.getLanguage(),\n                                locale.getCountry()))));\n                    }\n                } catch (final MissingResourceException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"MissingResourceException: Just using language\");\n                        e.printStackTrace();\n                    }\n                    locale = new Locale(locale.getLanguage());\n                }\n\n                return resolveSuccess(super.setLanguage(locale));\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setVoice: Current matches\");\n                }\n                return SUCCESS;\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final MissingResourceException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"MissingResourceException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return ERROR;\n    }\n\n\n    @Override\n    public void shutdown() {\n\n        if (audioTrack != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            switch (audioTrack.getPlayState()) {\n                case AudioTrack.PLAYSTATE_PLAYING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_PLAYING\");\n                    }\n                    audioTrack.stop(true);\n                    audioTrack.flush();\n                    audioTrack.release();\n                case AudioTrack.PLAYSTATE_PAUSED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_PAUSED\");\n                    }\n                    audioTrack.stop(true);\n                    audioTrack.flush();\n                    audioTrack.release();\n                case AudioTrack.PLAYSTATE_STOPPED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_STOPPED\");\n                    }\n                    audioTrack.flush();\n                    audioTrack.release();\n                    break;\n            }\n        }\n\n        super.shutdown();\n    }\n\n    @Override\n    public int stop() {\n\n        if (audioTrack != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            switch (audioTrack.getPlayState()) {\n                case AudioTrack.PLAYSTATE_PLAYING:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_PLAYING\");\n                    }\n                    audioTrack.stop(true);\n                    return SUCCESS;\n                case AudioTrack.PLAYSTATE_PAUSED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_PAUSED\");\n                    }\n                    audioTrack.stop(true);\n                    return SUCCESS;\n                case AudioTrack.PLAYSTATE_STOPPED:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"stop: PLAYSTATE_STOPPED\");\n                    }\n                    break;\n            }\n        }\n\n        try {\n            return super.stop();\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stop: NullPointerException\");\n                e.printStackTrace();\n            }\n        }\n\n        return ERROR;\n    }\n\n    @Override\n    public int synthesizeToFile(final CharSequence text, final Bundle params, final File file, final String utteranceId) {\n        return super.synthesizeToFile(text, params, file, utteranceId);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int synthesizeToFile(final String text, final HashMap<String, String> params, final String filename) {\n        return super.synthesizeToFile(text, params, filename);\n    }\n\n    /**\n     * Get the previously initialised TTS Engine package name. If it doesn't match the user's default\n     * we need to reinitialise the Engine.\n     *\n     * @return the package name of the currently initialised TTS Engine.\n     */\n    public String getInitialisedEngine() {\n        if (initEngine == null) {\n            return \"\";\n        } else {\n            return this.initEngine;\n        }\n    }\n\n    /**\n     * Store the initialised engine package name, so to check in the future if the user has changed\n     * their default TTS Engine in the Android Settings. There is no Broadcast exposed to handle\n     * this elsewhere, so we need to use reflection to access the variable in the super class.\n     *\n     * @param initEngine the user's default Text to Speech Engine in the Android Application Settings\n     */\n    private void setInitialisedEngine(@NonNull final String initEngine) {\n\n        String reflectEngine;\n\n        try {\n            final Method method = this.getClass().getSuperclass().getMethod(TTSDefaults.BOUND_ENGINE_METHOD);\n            reflectEngine = (String) method.invoke(this);\n\n            if (UtilsString.notNaked(reflectEngine)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Method reflect: reflectEngine: \" + reflectEngine);\n                }\n\n                this.initEngine = reflectEngine;\n                return;\n            }\n        } catch (final NoSuchMethodException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NoSuchMethodException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalAccessException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"IllegalAccessException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (InvocationTargetException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"InvocationTargetException\");\n                e.printStackTrace();\n            }\n        }\n\n        try {\n            final Field f = this.getClass().getSuperclass().getDeclaredField(TTSDefaults.BOUND_ENGINE_FIELD);\n            f.setAccessible(true);\n            reflectEngine = (String) f.get(this);\n\n            if (UtilsString.notNaked(reflectEngine)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"Field reflect: reflectEngine: \" + reflectEngine);\n                }\n\n                this.initEngine = reflectEngine;\n                return;\n            }\n\n        } catch (final NoSuchFieldException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NoSuchFieldException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalAccessException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"IllegalAccessException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        }\n\n        if (UtilsString.notNaked(initEngine)) {\n            this.initEngine = initEngine;\n        } else {\n            this.initEngine = \"\";\n        }\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"initEngine: \" + this.initEngine);\n        }\n    }\n\n    /**\n     * Store the initialised Text to Speech Engine\n     */\n    public void initialised() {\n\n        try {\n\n            final String packageName = getDefaultEngineSecure();\n            setInitialisedEngine(packageName);\n\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        if (DEBUG) {\n            //getInfo();\n        }\n\n        addSoundEffects();\n    }\n\n\n    /**\n     * Examine TTS objects in an overly verbose way. Debugging only.\n     */\n    @SuppressWarnings(\"deprecation\")\n    private void getInfo() {\n        MyLog.i(CLS_NAME, \"getQuickInfo\");\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n\n                    try {\n                        MyLog.v(CLS_NAME, \"getEngineDefaultSaiyVoice: \" + SaiyTextToSpeech.this.getEngineDefaultSaiyVoice().toString());\n                        MyLog.v(CLS_NAME, \"getBoundSaiyVoice: \" + SaiyTextToSpeech.this.getBoundSaiyVoice().toString());\n\n                        final SaiyVoice userDefaultSaiyVoice = SaiyTextToSpeech.this.getUserDefaultSaiyVoice();\n\n                        if (userDefaultSaiyVoice != null) {\n                            MyLog.v(CLS_NAME, \"userDefaultSaiyVoice: \" + userDefaultSaiyVoice.toString());\n                        }\n\n                    } catch (final NullPointerException e) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    } catch (final Exception e) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    } finally {\n\n                        try {\n                            final Locale defaultLanguage = SaiyTextToSpeech.this.getDefaultLanguage();\n                            MyLog.v(CLS_NAME, \"defaultLanguage toString: \" + defaultLanguage.toString());\n                        } catch (final NullPointerException e) {\n                            MyLog.w(CLS_NAME, \"NullPointerException\");\n                            e.printStackTrace();\n                        } catch (final Exception e) {\n                            MyLog.w(CLS_NAME, \"Exception\");\n                            e.printStackTrace();\n                        } finally {\n                            try {\n                                final Locale languageLocale = SaiyTextToSpeech.this.getLanguage();\n                                MyLog.v(CLS_NAME, \"languageLocale toString: \" + languageLocale.toString());\n                            } catch (final NullPointerException e) {\n                                MyLog.w(CLS_NAME, \"NullPointerException\");\n                                e.printStackTrace();\n                            } catch (final Exception e) {\n                                MyLog.w(CLS_NAME, \"Exception\");\n                                e.printStackTrace();\n                            }\n                        }\n                    }\n\n                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n\n                    try {\n                        final Locale defaultLanguage = SaiyTextToSpeech.this.getDefaultLanguage();\n                        MyLog.v(CLS_NAME, \"defaultLanguage toString: \" + defaultLanguage.toString());\n                    } catch (final NullPointerException e) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    } catch (final Exception e) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    } finally {\n                        try {\n                            final Locale languageLocale = SaiyTextToSpeech.this.getLanguage();\n                            MyLog.v(CLS_NAME, \"languageLocale toString: \" + languageLocale.toString());\n                        } catch (final NullPointerException e) {\n                            MyLog.w(CLS_NAME, \"NullPointerException\");\n                            e.printStackTrace();\n                        } catch (final Exception e) {\n                            MyLog.w(CLS_NAME, \"Exception\");\n                            e.printStackTrace();\n                        }\n                    }\n                } else {\n                    try {\n                        final Locale languageLocale = SaiyTextToSpeech.this.getLanguage();\n                        MyLog.v(CLS_NAME, \"languageLocale toString: \" + languageLocale.toString());\n                    } catch (final NullPointerException e) {\n                        MyLog.w(CLS_NAME, \"NullPointerException\");\n                        e.printStackTrace();\n                    } catch (final Exception e) {\n                        MyLog.w(CLS_NAME, \"Exception\");\n                        e.printStackTrace();\n                    }\n                }\n\n                SaiyTextToSpeech.this.getVerboseInfo();\n            }\n        });\n    }\n\n    /**\n     * More debugging info\n     */\n    private void getVerboseInfo() {\n        MyLog.i(CLS_NAME, \"getVerboseInfo\");\n\n        try {\n            final List<EngineInfo> engines = getEngines();\n\n            for (int i = 0; i < engines.size(); i++) {\n                MyLog.v(CLS_NAME, \"inf label: \" + engines.get(i).label);\n                MyLog.v(CLS_NAME, \"inf name: \" + engines.get(i).name);\n            }\n\n        } catch (final NullPointerException e) {\n            MyLog.w(CLS_NAME, \"NullPointerException\");\n            e.printStackTrace();\n        } catch (final Exception e) {\n            MyLog.w(CLS_NAME, \"Exception\");\n            e.printStackTrace();\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n\n            try {\n\n                for (final SaiyVoice v : getSaiyVoices()) {\n                    MyLog.v(CLS_NAME, \"v : \" + v.toString());\n                }\n\n            } catch (final NullPointerException e) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            } catch (final Exception e) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n\n            try {\n\n                for (final Locale loc : getAvailableLanguages()) {\n                    MyLog.v(CLS_NAME, \"loc: \" + loc.toString());\n                }\n\n            } catch (final NullPointerException e) {\n                MyLog.w(CLS_NAME, \"NullPointerException\");\n                e.printStackTrace();\n            } catch (final Exception e) {\n                MyLog.w(CLS_NAME, \"Exception\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Convert the responses of {@link #LANG_COUNTRY_AVAILABLE} {@link #LANG_AVAILABLE}\n     * {@link #LANG_COUNTRY_VAR_AVAILABLE} {@link #LANG_MISSING_DATA}\n     * {@link #LANG_NOT_SUPPORTED} to {@link #SUCCESS} {@link #ERROR}\n     *\n     * @param result the result of attempting to set a voice/language {@link Locale}\n     * @return one of {@link #SUCCESS} {@link #ERROR}\n     */\n    private int resolveSuccess(final int result) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"resolveSuccess\");\n        }\n\n        switch (result) {\n\n            case LANG_AVAILABLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"resolveSuccess: LANG_AVAILABLE\");\n                }\n                return SUCCESS;\n            case LANG_COUNTRY_AVAILABLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"resolveSuccess: LANG_COUNTRY_AVAILABLE\");\n                }\n                return SUCCESS;\n            case LANG_COUNTRY_VAR_AVAILABLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"resolveSuccess: LANG_COUNTRY_VAR_AVAILABLE\");\n                }\n                return SUCCESS;\n            case LANG_MISSING_DATA:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"resolveSuccess: LANG_MISSING_DATA\");\n                }\n                return ERROR;\n            case LANG_NOT_SUPPORTED:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"resolveSuccess: LANG_NOT_SUPPORTED\");\n                }\n                return ERROR;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"resolveSuccess: default\");\n                }\n                return ERROR;\n        }\n    }\n\n    /**\n     * Attempt to select a voice object based on the user's preference or required locale. The\n     * omission of parameters that let us know if the engine is correctly installed is a major\n     * frustration here, along with no providers distinguishing between the gender of their voices.\n     * <p/>\n     * I could rant further....\n     * <p/>\n     * Created by benrandall76@gmail.com on 13/03/2016.\n     */\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private class TTSVoice {\n\n        private final boolean DEBUG = MyLog.DEBUG;\n        private final String CLS_NAME = TTSVoice.class.getSimpleName();\n\n        private final String language;\n        private final String region;\n        private final SelfAwareConditions conditions;\n        private final SelfAwareParameters params;\n        private final boolean isNetworkAllowed;\n        private final boolean isNetworkAvailable;\n        private final SaiyVoice currentVoice;\n\n        /**\n         * Constructor\n         *\n         * @param language the {@link Locale} language\n         * @param region   the {@link Locale} region\n         */\n        private TTSVoice(@NonNull final String language, @NonNull final String region,\n                         @NonNull final SelfAwareConditions conditions,\n                         @NonNull final SelfAwareParameters params, @Nullable final SaiyVoice currentVoice) {\n            this.language = language;\n            this.region = region;\n            this.conditions = conditions;\n            this.params = params;\n            this.currentVoice = currentVoice;\n\n            isNetworkAllowed = this.params.isNetworkAllowed();\n            isNetworkAvailable = this.params.shouldNetwork();\n\n        }\n\n        private Pair<SaiyVoice, Locale> buildVoice() {\n\n            Locale requiredLocale = null;\n            SaiyVoice voice = null;\n\n            try {\n\n                final Set<SaiyVoice> voices = getSaiyVoices();\n                requiredLocale = new Locale(language, region);\n\n                if (conditions.getCondition() != Condition.CONDITION_TRANSLATION) {\n\n                    if (currentVoice != null && getInitialisedEngine().startsWith(TTSDefaults.TTS_PKG_NAME_GOOGLE)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Have a current google voice\");\n                        }\n\n                        final TTSDefaults.Google parentGoogle = TTSDefaults.Google.getGoogle(currentVoice.getName());\n\n                        if (parentGoogle != null) {\n\n                            SaiyVoice associatedVoice = null;\n                            TTSDefaults.Google associatedGoogle = null;\n\n                            for (final SaiyVoice v : voices) {\n                                if (parentGoogle.getVoiceName().matches(\"(?i)\" + Pattern.quote(v.getName()))) {\n\n                                    if (v.isNetworkConnectionRequired()) {\n                                        associatedGoogle = TTSDefaults.Google.getAssociatedVoice(parentGoogle,\n                                                TTSDefaults.TYPE_LOCAL);\n                                    } else {\n                                        associatedGoogle = TTSDefaults.Google.getAssociatedVoice(parentGoogle,\n                                                TTSDefaults.TYPE_NETWORK);\n                                    }\n\n                                    break;\n                                }\n                            }\n\n                            if (associatedGoogle != null) {\n                                for (final SaiyVoice v : voices) {\n                                    if (associatedGoogle.getVoiceName().matches(\"(?i)\" + Pattern.quote(v.getName()))) {\n                                        associatedVoice = v;\n                                    }\n                                }\n\n                                if (associatedVoice != null) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"returning associated: \" + associatedVoice.getName());\n                                    }\n                                    return new Pair<>(associatedVoice, requiredLocale);\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"associated google null\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"parent google null\");\n                            }\n                        }\n                    }\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"buildVoice: CONDITION_TRANSLATION\");\n                    }\n\n                    final ArrayList<SaiyVoice> voiceArray = new ArrayList<>();\n                    final ArrayList<SaiyVoice> voiceExactArray = new ArrayList<>();\n\n                    if (!voices.isEmpty()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Have \" + voices.size() + \" starting voices\");\n                        }\n\n                        for (final SaiyVoice v : voices) {\n                            if (UtilsLocale.localesMatch(v.getLocale(), requiredLocale)) {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"v : \" + v.toString());\n                                }\n                                voiceArray.add(v);\n                            }\n                        }\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Have \" + voiceArray.size() + \" potential voices\");\n                        }\n\n                        if (voiceArray.isEmpty()) {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"Checking ISO \");\n                            }\n\n                            for (final SaiyVoice v : voices) {\n                                if (v.getLocale().getISO3Language().matches(requiredLocale.getISO3Language())) {\n                                    if (DEBUG) {\n                                        MyLog.v(CLS_NAME, \"vISO language: \" + v.getLocale().getISO3Language());\n                                    }\n                                    if (v.getLocale().getISO3Country().matches(requiredLocale.getISO3Country())) {\n                                        if (DEBUG) {\n                                            MyLog.v(CLS_NAME, \"vISO country: \" + v.getLocale().getISO3Country());\n                                        }\n                                        voiceArray.add(0, v);\n                                    } else {\n                                        voiceArray.add(v);\n                                    }\n                                }\n                            }\n                        }\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Have \" + voiceArray.size() + \" potential voices\");\n                        }\n\n                        if (voiceArray.isEmpty()) {\n                            if (DEBUG) {\n                                MyLog.v(CLS_NAME, \"Checking loose ISO \");\n                            }\n\n                            for (final SaiyVoice v : voices) {\n                                if (v.getLocale().getISO3Language().matches(requiredLocale.getLanguage())) {\n                                    if (DEBUG) {\n                                        MyLog.v(CLS_NAME, \"vISO language: \" + v.getLocale().getLanguage());\n                                    }\n                                    if (v.getLocale().getISO3Country().matches(requiredLocale.getCountry())) {\n                                        if (DEBUG) {\n                                            MyLog.v(CLS_NAME, \"vISO country: \" + v.getLocale().getCountry());\n                                        }\n                                        voiceArray.add(0, v);\n                                    } else {\n                                        voiceArray.add(v);\n                                    }\n                                }\n                            }\n                        }\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"Have \" + voiceArray.size() + \" potential voices\");\n                        }\n\n                        if (!voiceArray.isEmpty()) {\n\n                            if (!isNetworkAllowed || !isNetworkAvailable) {\n                                if (DEBUG) {\n                                    MyLog.v(CLS_NAME, \"Removing networked voices\");\n                                }\n\n                                final ListIterator<SaiyVoice> itr = voiceArray.listIterator();\n\n                                SaiyVoice v;\n                                while (itr.hasNext()) {\n                                    v = itr.next();\n                                    if ((v.isNetworkConnectionRequired()\n                                            && !v.getFeatures().contains(TTSDefaults.EMBEDDED_TTS_FIELD))\n                                            || v.getFeatures().contains(Engine.KEY_FEATURE_NOT_INSTALLED)) {\n                                        if (DEBUG) {\n                                            MyLog.v(CLS_NAME, \"Removing networked voice: \" + v.toString());\n                                        }\n                                        itr.remove();\n                                    }\n                                }\n                            }\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"Have \" + voiceArray.size() + \" potential voices\");\n                            }\n\n                            if (!voiceArray.isEmpty()) {\n\n                                for (final SaiyVoice v : voiceArray) {\n                                    if (isNetworkAllowed) {\n                                        if (v.isNetworkConnectionRequired()) {\n                                            voiceExactArray.add(0, v);\n                                        }\n                                    } else {\n                                        voiceExactArray.add(v);\n                                    }\n                                }\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"Have \" + voiceExactArray.size() + \" exact voices\");\n                                }\n\n                                if (!voiceExactArray.isEmpty()) {\n                                    if (DEBUG) {\n                                        for (final SaiyVoice v : voiceExactArray) {\n                                            MyLog.v(CLS_NAME, \"Exact Voice \" + v.toString());\n                                        }\n                                    }\n\n                                    voice = getVoiceDetailed(voiceExactArray);\n\n                                } else {\n                                    if (DEBUG) {\n                                        for (final SaiyVoice v : voiceArray) {\n                                            MyLog.v(CLS_NAME, \"Settled Voice \" + v.toString());\n                                        }\n                                    }\n\n                                    voice = getVoiceDetailed(voiceArray);\n                                }\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.w(CLS_NAME, \"Only networked voices available\");\n                                }\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"Could not match engine Locale\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"Engine has no voices?\");\n                        }\n                    }\n                }\n            } catch (final MissingResourceException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"MissingResourceException\");\n                    e.printStackTrace();\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"Exception\");\n                    e.printStackTrace();\n                }\n                if (e instanceof IllformedLocaleException) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"IllformedLocaleException\");\n                    }\n                }\n            }\n\n            if (voice != null) {\n                voice.setEngine(getInitialisedEngine());\n                voice.setGender(voice.getName());\n                return new Pair<>(voice, requiredLocale);\n            } else {\n                return new Pair<>(null, requiredLocale);\n            }\n        }\n\n        private SaiyVoice getVoiceDetailed(@NonNull final ArrayList<SaiyVoice> voiceArray) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"getVoiceDetailed\");\n            }\n\n            if (voiceArray.size() > 1) {\n                return filterGender(voiceArray).get(0);\n            } else {\n                return voiceArray.get(0);\n            }\n        }\n\n        private ArrayList<SaiyVoice> filterGender(@NonNull final ArrayList<SaiyVoice> voiceArray) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"filterGender\");\n            }\n\n            final Gender preferredGender = SPH.getDefaultTTSGender(mContext);\n            final ArrayList<SaiyVoice> voiceArrayCopy = new ArrayList<>(voiceArray);\n\n            final ListIterator<SaiyVoice> itr = voiceArrayCopy.listIterator();\n\n            SaiyVoice v;\n            while (itr.hasNext()) {\n                v = itr.next();\n                if (v.getGender() != preferredGender) {\n                    itr.remove();\n                }\n            }\n\n            if (voiceArrayCopy.isEmpty()) {\n                return filterLegacy(voiceArray);\n            } else {\n                return filterLegacy(voiceArrayCopy);\n            }\n        }\n\n        private ArrayList<SaiyVoice> filterLegacy(@NonNull final ArrayList<SaiyVoice> voiceArray) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"filterLegacy\");\n            }\n\n            if (isNetworkAllowed) {\n\n                final ArrayList<SaiyVoice> voiceArrayCopy = new ArrayList<>(voiceArray);\n                final ListIterator<SaiyVoice> itr = voiceArrayCopy.listIterator();\n\n                SaiyVoice v;\n                while (itr.hasNext()) {\n                    v = itr.next();\n                    if (v.getFeatures().contains(TTSDefaults.LEGACY_ENGINE_FIELD)) {\n                        itr.remove();\n                    }\n                }\n\n                if (voiceArrayCopy.isEmpty()) {\n                    return filterQuality(voiceArray);\n                } else {\n                    return filterQuality(voiceArrayCopy);\n                }\n            }\n\n            return filterQuality(voiceArray);\n        }\n\n\n        private ArrayList<SaiyVoice> filterQuality(@NonNull final ArrayList<SaiyVoice> voiceArray) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"filterQuality\");\n            }\n\n            Collections.sort(voiceArray, new Comparator<SaiyVoice>() {\n                @Override\n                public int compare(final SaiyVoice v1, final SaiyVoice v2) {\n                    return v2.getQuality() - v1.getQuality();\n                }\n            });\n            return voiceArray;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/TTS.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts;\n\nimport android.support.annotation.NonNull;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Set the global state of network speech, so regardless of the implementation used, it can be\n * quickly cancelled if required.\n * <p>\n * This is a necessary evil. Must be reset under all error circumstances or after multiple attempts.\n * <p>\n * Created by benrandall76@gmail.com on 08/02/2016.\n */\npublic class TTS {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = TTS.class.getSimpleName();\n\n    private static volatile State state = State.IDLE;\n\n    public enum State {\n        IDLE,\n        SPEAKING\n    }\n\n    /**\n     * Set the global state of network speech\n     *\n     * @param newState the updated {@link TTS.State}\n     */\n    public static void setState(@NonNull final State newState) {\n\n        state = newState;\n\n        switch (state) {\n            case IDLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setState: IDLE\");\n                }\n                break;\n            case SPEAKING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"setState: SPEAKING\");\n                }\n                break;\n        }\n    }\n\n    /**\n     * Get the global state of network speech\n     *\n     * @return the {@link TTS.State}\n     */\n    public static State getState() {\n\n        switch (state) {\n            case IDLE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getState: IDLE\");\n                }\n                break;\n            case SPEAKING:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getState: SPEAKING\");\n                }\n                break;\n        }\n\n        return state;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/attributes/Gender.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.attributes;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 19/08/2016.\n */\n\npublic enum Gender {\n\n    MALE(ai.saiy.android.api.attributes.Gender.MALE),\n    FEMALE(ai.saiy.android.api.attributes.Gender.FEMALE),\n    UNDEFINED(ai.saiy.android.api.attributes.Gender.UNDEFINED);\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Gender.class.getSimpleName();\n\n    public static final String EN_MALE_STRING = \"male\";\n    public static final String EN_FEMALE_STRING = \"female\";\n\n    public static final Pattern pEN_MALE_STRING = Pattern.compile(\".*\" + EN_MALE_STRING + \".*\", Pattern.CASE_INSENSITIVE);\n    public static final Pattern pEN_FEMALE_STRING = Pattern.compile(\".*\" + EN_FEMALE_STRING + \".*\", Pattern.CASE_INSENSITIVE);\n\n    private final ai.saiy.android.api.attributes.Gender gender;\n\n    Gender(final ai.saiy.android.api.attributes.Gender gender) {\n        this.gender = gender;\n    }\n\n    public ai.saiy.android.api.attributes.Gender getRemoteGender() {\n        return this.gender;\n    }\n\n    /**\n     * Convert a remote Gender object to the local variant\n     *\n     * @param gender the remote {@link ai.saiy.android.api.attributes.Gender}\n     * @return the local variant of {@link Gender}\n     */\n    public static Gender remoteToLocal(@NonNull final ai.saiy.android.api.attributes.Gender gender) {\n\n        switch (gender) {\n\n            case FEMALE:\n                return FEMALE;\n            case MALE:\n                return MALE;\n            case UNDEFINED:\n                return UNDEFINED;\n            default:\n                return UNDEFINED;\n        }\n    }\n\n    public static Gender getGender(@Nullable final String name) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getGender: \" + name);\n        }\n\n        if (name != null) {\n\n            try {\n                return Enum.valueOf(Gender.class, name.trim());\n            } catch (final IllegalArgumentException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getGender: IllegalArgumentException\");\n                    e.printStackTrace();\n                }\n                return Gender.UNDEFINED;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getGender: name null\");\n            }\n            return Gender.UNDEFINED;\n        }\n    }\n\n    public static Gender getGenderFromVoiceName(@NonNull final String engineName) {\n\n        if (pEN_FEMALE_STRING.matcher(engineName).matches()) {\n            return FEMALE;\n        } else if (pEN_MALE_STRING.matcher(engineName).matches()) {\n            return MALE;\n        } else {\n            return UNDEFINED;\n        }\n\n//        if (StringUtils.containsIgnoreCase(engineName, EN_FEMALE_STRING)) {\n//            return FEMALE;\n//        } else if (StringUtils.containsIgnoreCase(engineName, EN_MALE_STRING)) {\n//            return MALE;\n//        } else {\n//            return UNDEFINED;\n//        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/engine/EngineNuance.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.engine;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.support.annotation.NonNull;\n\nimport com.nuance.speechkit.Audio;\nimport com.nuance.speechkit.AudioPlayer;\nimport com.nuance.speechkit.Language;\nimport com.nuance.speechkit.Session;\nimport com.nuance.speechkit.Transaction;\nimport com.nuance.speechkit.TransactionException;\nimport com.nuance.speechkit.Voice;\n\nimport ai.saiy.android.tts.SaiyProgressListener;\nimport ai.saiy.android.tts.TTS;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to use the Dragon Nuance SpeechKit for our Text to Speech. The Android sdk demonstrated here\n * is very limited in its functionality. The use of the HTTP interface is a much better and more\n * flexible option, but requires an expensive Gold Membership.\n * <p>\n * BUG - The ability to pause or stop the TTS playback is currently broken\n * <a href=\"https://developer.nuance.com/phpbb/viewtopic.php?f=16&t=1151&sid=f35fca93a9a735e4fa2ef85abd6d2b64\">Pause Stop TTS Playback/a>\n * <p>\n * BUG - Must call Looper.getMainLooper() - Handles Nuance Speech kit failing to call Looper.getMainLooper()\n * <a href=\"https://developer.nuance.com/phpbb/viewtopic.php?f=16&t=1152&sid=b166db2117e306f594dcacc3972c955b\">Can't create handler not called Looper.prepare()/a>\n * <p>\n * Created by benrandall76@gmail.com on 09/03/2016.\n */\npublic class EngineNuance implements AudioPlayer.Listener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = EngineNuance.class.getSimpleName();\n\n    private Session speechSession;\n    private Transaction ttsTransaction;\n    private final Transaction.Options options;\n    private final SaiyProgressListener listener;\n\n    private final String utteranceId;\n\n    /**\n     * Constructor\n     *\n     * @param mContext  the application context\n     * @param listener  the associated {@link SaiyProgressListener}\n     * @param language  the Locale we are using to analyse the voice data. This is not necessarily the\n     *                  Locale of the device, as the user may be multi-lingual and have set a custom\n     *                  recognition language in a launcher short-cut.\n     * @param serverUri the Nuance Server_URI\n     * @param appKey    the Nuance APP_KEY\n     */\n    public EngineNuance(@NonNull final Context mContext, @NonNull final SaiyProgressListener listener,\n                        @NonNull final String language, final String languageName,\n                        final String utteranceId, @NonNull final Uri serverUri,\n                        @NonNull final String appKey) {\n        this.listener = listener;\n        this.utteranceId = utteranceId;\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"language: \" + language);\n            MyLog.i(CLS_NAME, \"languageName: \" + languageName);\n        }\n\n        options = new Transaction.Options();\n        options.setLanguage(new Language(language));\n        options.setAutoplay(false);\n\n        if (languageName != null) {\n            options.setVoice(new Voice(languageName));\n        }\n\n        try {\n            speechSession = Session.Factory.session(mContext.getApplicationContext(), serverUri, appKey);\n            speechSession.getAudioPlayer().setListener(this);\n        } catch (final RuntimeException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"RuntimeException\");\n                e.printStackTrace();\n            }\n\n            this.listener.onError(utteranceId, 0);\n        }\n\n    }\n\n    /**\n     * Stop the speech\n     * <p>\n     * BUG - this functionality is broken in the latest SDK\n     * <a href=\"https://developer.nuance.com/phpbb/viewtopic.php?f=16&t=1151&sid=f35fca93a9a735e4fa2ef85abd6d2b64\">Pause Stop TTS Playback/a>\n     */\n    public void stopSpeech() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"stopSpeech\");\n        }\n\n        try {\n            speechSession.getAudioPlayer().pause();\n            speechSession.getAudioPlayer().stop();\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        try {\n            ttsTransaction.cancel();\n            ttsTransaction.stopRecording();\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: ttsTransaction NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final IllegalStateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: ttsTransaction IllegalStateException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stopSpeech: ttsTransaction Exception\");\n                e.printStackTrace();\n            }\n        } finally {\n            TTS.setState(TTS.State.IDLE);\n        }\n    }\n\n    /**\n     * TODO - Extract listener to handle utterances of length greater than 500 characters that need to be looped\n     * <p>\n     * Start the speech\n     *\n     * @param utterance to speak\n     */\n    public void startSpeech(@NonNull final String utterance) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startSpeech\");\n        }\n\n        ttsTransaction = speechSession.speakString(utterance, options, new Transaction.Listener() {\n            @Override\n            public void onAudio(final Transaction transaction, final Audio audio) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAudio\");\n                }\n                speechSession.getAudioPlayer().playAudio(audio);\n            }\n\n            @Override\n            public void onSuccess(final Transaction transaction, final String s) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onSuccess\");\n                }\n            }\n\n            @Override\n            public void onError(final Transaction transaction, final String s, final TransactionException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onError: \" + e.getMessage() + \". \" + s);\n                    e.printStackTrace();\n                }\n\n                TTS.setState(TTS.State.IDLE);\n\n                // TODO - more verbose error handling\n                listener.onError(utteranceId, 0);\n                ttsTransaction = null;\n            }\n\n        });\n    }\n\n    @Override\n    public void onBeginPlaying(final AudioPlayer audioPlayer, final Audio audio) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBeginPlaying\");\n        }\n\n        TTS.setState(TTS.State.SPEAKING);\n        listener.onStart(utteranceId);\n    }\n\n    @Override\n    public void onFinishedPlaying(final AudioPlayer audioPlayer, final Audio audio) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onFinishedPlaying\");\n        }\n\n        TTS.setState(TTS.State.IDLE);\n        listener.onDone(utteranceId);\n        ttsTransaction = null;\n        speechSession = null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/helper/PendingTTS.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.helper;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Helper Class to store speech requests whilst the TTS engine sorts itself out....\n * <p>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic class PendingTTS {\n\n    private String pendingUtterance;\n    private int pendingQueueType;\n\n    /**\n     * Get the pending utterance\n     *\n     * @return the pending utterance\n     */\n    public String getPendingUtterance() {\n        return this.pendingUtterance;\n    }\n\n    /**\n     * Set the pending utterance\n     *\n     * @param pendingUtterance to set\n     */\n    public void setPendingUtterance(@NonNull final String pendingUtterance) {\n        this.pendingUtterance = pendingUtterance;\n    }\n\n    /**\n     * Get the pending queue type\n     *\n     * @return one of {@link android.speech.tts.TextToSpeech#QUEUE_ADD}\n     * or {@link android.speech.tts.TextToSpeech#QUEUE_FLUSH}\n     */\n    public int getPendingQueueType() {\n        return this.pendingQueueType;\n    }\n\n    /**\n     * Set the pending queue type\n     *\n     * @param pendingQueueType one of {@link android.speech.tts.TextToSpeech#QUEUE_ADD}\n     *                         or {@link android.speech.tts.TextToSpeech#QUEUE_FLUSH}\n     */\n    public void setPendingQueueType(final int pendingQueueType) {\n        this.pendingQueueType = pendingQueueType;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/helper/SaiyVoice.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.helper;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\nimport android.os.Parcel;\nimport android.speech.tts.Voice;\nimport android.support.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.tts.attributes.Gender;\n\n/**\n * Created by benrandall76@gmail.com on 19/08/2016.\n */\n\n@TargetApi(Build.VERSION_CODES.LOLLIPOP)\npublic class SaiyVoice extends Voice {\n\n    private String engine;\n    private Gender gender = Gender.UNDEFINED;\n\n    public SaiyVoice(@NonNull final Voice voice) {\n        super(voice.getName(), voice.getLocale(), voice.getQuality(), voice.getLatency(), voice.isNetworkConnectionRequired(),\n                voice.getFeatures());\n    }\n\n    public static Set<SaiyVoice> getSaiyVoices(@NonNull final Set<Voice> voiceSet, @NonNull final String initialisedEngine) {\n\n        final Set<SaiyVoice> saiyVoiceSet = new HashSet<>(voiceSet.size());\n\n        if (initialisedEngine.matches(TTSDefaults.TTS_PKG_NAME_GOOGLE)) {\n            final TTSDefaults.Google[] googleList = TTSDefaults.Google.values();\n\n            SaiyVoice saiyVoice;\n            String voicePattern;\n            for (final Voice voice : voiceSet) {\n                saiyVoice = new SaiyVoice(voice);\n                saiyVoice.setEngine(initialisedEngine);\n\n                voicePattern = Pattern.quote(voice.getName());\n                for (final TTSDefaults.Google g : googleList) {\n                    if (g.getVoiceName().matches(voicePattern)) {\n                        saiyVoice.setGender(g.getGender());\n                        break;\n                    }\n                }\n\n                saiyVoiceSet.add(saiyVoice);\n            }\n        } else {\n\n            SaiyVoice saiyVoice;\n            for (final Voice voice : voiceSet) {\n                saiyVoice = new SaiyVoice(voice);\n                saiyVoice.setEngine(initialisedEngine);\n                saiyVoice.setGender(Gender.getGenderFromVoiceName(voice.getName()));\n                saiyVoiceSet.add(saiyVoice);\n            }\n        }\n\n        return saiyVoiceSet;\n    }\n\n    public Gender getGender() {\n        return gender;\n    }\n\n    public void setGender(@NonNull final Gender gender) {\n        this.gender = gender;\n    }\n\n    public void setGender(@NonNull final String voiceName) {\n\n        if (this.engine != null) {\n            if (TTSDefaults.pTTS_PKG_NAME_GOOGLE.matcher(this.engine).matches()) {\n                this.gender = TTSDefaults.Google.getGender(voiceName);\n            } else {\n                this.gender = Gender.getGenderFromVoiceName(voiceName);\n            }\n        } else {\n            this.gender = Gender.UNDEFINED;\n        }\n    }\n\n    public String getEngine() {\n        return engine;\n    }\n\n    public void setEngine(@NonNull final String engine) {\n        this.engine = engine;\n    }\n\n    public static class VoiceComparator implements Comparator<Voice> {\n\n        @Override\n        public int compare(final Voice v1, final Voice v2) {\n            return v1.getLocale().toString().compareTo(v2.getLocale().toString());\n        }\n    }\n\n    public static class SaiyVoiceComparator implements Comparator<SaiyVoice> {\n\n        @Override\n        public int compare(final SaiyVoice v1, final SaiyVoice v2) {\n            return v1.getLocale().toString().compareTo(v2.getLocale().toString());\n        }\n    }\n\n    @Override\n    public String toString() {\n        final StringBuilder builder = new StringBuilder();\n        return builder.append(\"SaiyVoice[Name: \").append(getName())\n                .append(\", locale: \").append(getLocale())\n                .append(\", quality: \").append(getQuality())\n                .append(\", latency: \").append(getLatency())\n                .append(\", requiresNetwork: \").append(isNetworkConnectionRequired())\n                .append(\", features: \").append(getFeatures().toString())\n                .append(\", engine: \").append(engine)\n                .append(\", gender: \").append(gender.name())\n                .append(\"]\").toString();\n    }\n\n    @Override\n    public void writeToParcel(final Parcel dest, final int flags) {\n        dest.writeString(getName());\n        dest.writeSerializable(getLocale());\n        dest.writeInt(getQuality());\n        dest.writeInt(getLatency());\n        dest.writeByte((byte) (isNetworkConnectionRequired() ? 1 : 0));\n        dest.writeStringList(new ArrayList<>(getFeatures()));\n        dest.writeString(engine);\n        dest.writeSerializable(gender);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/helper/SpeechPriority.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.tts.helper;\n\n/**\n * Created by benrandall76@gmail.com on 04/02/2017.\n */\n\npublic class SpeechPriority {\n\n    public static final int PRIORITY_LOW = 1;\n    public static final int PRIORITY_REMOTE = 10;\n    public static final int PRIORITY_NORMAL = 25;\n    public static final int PRIORITY_TUTORIAL = 45;\n    public static final int PRIORITY_MAX = 50;\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/helper/TTSDefaults.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.helper;\n\nimport android.support.annotation.NonNull;\n\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.tts.attributes.Gender;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 14/04/2016.\n */\npublic class TTSDefaults {\n\n    public static final int TYPE_NETWORK = 1;\n    public static final int TYPE_LOCAL = 2;\n\n    public static final String TTS_PKG_NAME_GOOGLE = \"com.google.android.tts\";\n    public static final String TTS_PKG_NAME_IVONA = \"com.ivona.tts\";\n    public static final String TTS_PKG_NAME_PICO = \"com.svox.pico\";\n    public static final String TTS_PKG_NAME_CEREPROC = \"com.cereproc\";\n    public static final String TTS_PKG_NAME_SVOX = \"com.svox.classic\";\n    public static final String TTS_PKG_NAME_VOCALIZER = \"es.codefactory.vocalizertts\";\n\n    public static final String BOUND_ENGINE_FIELD = \"mCurrentEngine\";\n    public static final String BOUND_ENGINE_METHOD = \"getCurrentEngine\";\n    public static final String LEGACY_ENGINE_FIELD = \"LegacySetLanguageVoice\";\n    public static final String EMBEDDED_TTS_FIELD = \"embeddedTts\";\n    public static final String EXTRA_INTERRUPTED = \"extra_interrupted\";\n    public static final String EXTRA_INTERRUPTED_FORCED = \"extra_interrupted_forced\";\n\n    private static final String[] APPROVED_ENGINES = {TTS_PKG_NAME_VOCALIZER, TTS_PKG_NAME_GOOGLE};\n\n    protected static final Pattern pTTS_PKG_NAME_GOOGLE = Pattern.compile(TTS_PKG_NAME_GOOGLE);\n\n    /**\n     * Due to only Google TTS correctly handling the new Voice API features, it is the only 'approved' package\n     * to perform more advanced features. Others will be handled using depreciated methods.\n     *\n     * @param initialisedEngine of the tts object\n     * @return true if the engine is approved for advanced voice features, false otherwise.\n     */\n    public static boolean isApprovedVoice(@NonNull final String initialisedEngine) {\n        return UtilsString.notNaked(initialisedEngine) && ArrayUtils.contains(APPROVED_ENGINES, initialisedEngine);\n    }\n\n    public enum Google {\n\n        GOOGLE_EN_AU_X_AFH_LOCAL(\"en-au-x-afh-local\", Gender.FEMALE),\n        GOOGLE_EN_AU_X_AFH_NETWORK(\"en-au-x-afh-network\", Gender.FEMALE),\n        GOOGLE_EN_AU_LANGUAGE(\"en-AU-language\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_FIS_FEMALE_2_LOCAL(\"en-gb-x-fis#female_2-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_FIS_MALE_3_LOCAL(\"en-gb-x-fis#male_3-local\", Gender.MALE),\n        GOOGLE_EN_GB_X_RJS_FEMALE_2_LOCAL(\"en-gb-x-rjs#female_2-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_MALE_3_LOCAL(\"en-gb-x-rjs#male_3-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_FEMALE_3_LOCAL(\"en-gb-x-rjs#female_3-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_NETWORK(\"en-gb-x-rjs-network\", Gender.MALE),\n        GOOGLE_EN_GB_X_RJS_FEMALE_1_LOCAL(\"en-gb-x-rjs#female_1-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_FIS_MALE_1_LOCAL(\"en-gb-x-fis#male_1-local\", Gender.MALE),\n        GOOGLE_EN_GB_X_FIS_LOCAL(\"en-gb-x-fis-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_MALE_2_LOCAL(\"en-gb-x-rjs#male_2-local\", Gender.MALE),\n        GOOGLE_EN_GB_X_FIS_FEMALE_1_LOCAL(\"en-gb-x-fis#female_1-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_MALE_1_LOCAL(\"en-gb-x-rjs#male_1-local\", Gender.MALE),\n        GOOGLE_EN_GB_X_FIS_MALE_2_LOCAL(\"en-gb-x-fis#male_2-local\", Gender.MALE),\n        GOOGLE_EN_GB_X_FIS_NETWORK(\"en-gb-x-fis-network\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_FIS_FEMALE_3_LOCAL(\"en-gb-x-fis#female_3-local\", Gender.FEMALE),\n        GOOGLE_EN_GB_X_RJS_LOCAL(\"en-gb-x-rjs-local\", Gender.MALE),\n        GOOGLE_EN_GB_LANGUAGE(\"en-GB-language\", Gender.FEMALE),\n        GOOGLE_EN_IN_X_AHP_NETWORK(\"en-in-x-ahp-network\", Gender.FEMALE),\n        GOOGLE_EN_IN_X_CXX_LOCAL(\"en-in-x-cxx-local\", Gender.FEMALE),\n        GOOGLE_EN_IN_X_AHP_LOCAL(\"en-in-x-ahp-local\", Gender.FEMALE),\n        GOOGLE_EN_IN_LANGUAGE(\"en-IN-language\", Gender.FEMALE),\n        GOOGLE_EN_IN_X_CXX_NETWORK(\"en-in-x-cxx-network\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_FEMALE_2_LOCAL(\"en-us-x-sfg#female_2-local\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_MALE_3_LOCAL(\"en-us-x-sfg#male_3-local\", Gender.MALE),\n        GOOGLE_EN_US_X_SFG_FEMALE_1_LOCAL(\"en-us-x-sfg#female_1-local\", Gender.FEMALE),\n        GOOGLE_EN_US_LANGUAGE(\"en-US-language\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_MALE_2_LOCAL(\"en-us-x-sfg#male_2-local\", Gender.MALE),\n        GOOGLE_EN_US_X_SFG_FEMALE_3_LOCAL(\"en-us-x-sfg#female_3-local\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_NETWORK(\"en-us-x-sfg-network\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_LOCAL(\"en-us-x-sfg-local\", Gender.FEMALE),\n        GOOGLE_EN_US_X_SFG_MALE_1_LOCAL(\"en-us-x-sfg#male_1-local\", Gender.MALE);\n\n        private final String voiceName;\n        private final Gender gender;\n\n        Google(@NonNull final String voiceName, @NonNull final Gender gender) {\n            this.voiceName = voiceName;\n            this.gender = gender;\n        }\n\n        public Gender getGender() {\n            return gender;\n        }\n\n        public String getVoiceName() {\n            return voiceName;\n        }\n\n        public static Google getAssociatedVoice(@NonNull final Google google, final int type) {\n\n            switch (google) {\n\n                case GOOGLE_EN_AU_X_AFH_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_AU_X_AFH_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_AU_X_AFH_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_AU_X_AFH_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_AU_X_AFH_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_AU_X_AFH_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_AU_LANGUAGE:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_AU_LANGUAGE;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_AU_X_AFH_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_FEMALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_FEMALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_FEMALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_LANGUAGE;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_MALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_MALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_MALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_MALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_FEMALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_FEMALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_MALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_MALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_FEMALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_FEMALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_FEMALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_FEMALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_MALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_MALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_MALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_MALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_FEMALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_FEMALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_MALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_MALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_FIS_FEMALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_FIS_FEMALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_X_RJS_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_X_RJS_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_RJS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_GB_LANGUAGE:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_GB_LANGUAGE;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_GB_X_FIS_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_IN_X_AHP_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_IN_X_AHP_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_IN_X_AHP_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_IN_X_CXX_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_IN_X_CXX_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_IN_X_CXX_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_IN_X_AHP_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_IN_X_AHP_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_IN_X_AHP_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_IN_LANGUAGE:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_IN_LANGUAGE;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_IN_X_CXX_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_IN_X_CXX_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_IN_X_CXX_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_IN_X_CXX_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_FEMALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_FEMALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_MALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_MALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_MALE_3_LOCAL;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_FEMALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_FEMALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_LANGUAGE:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_LANGUAGE;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_MALE_2_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_MALE_2_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_MALE_2_LOCAL;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_FEMALE_3_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_FEMALE_3_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_NETWORK:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_NETWORK;\n                    }\n\n                    break;\n                case GOOGLE_EN_US_X_SFG_MALE_1_LOCAL:\n\n                    switch (type) {\n\n                        case TYPE_LOCAL:\n                            return GOOGLE_EN_US_X_SFG_MALE_1_LOCAL;\n                        case TYPE_NETWORK:\n                            return GOOGLE_EN_US_X_SFG_MALE_1_LOCAL;\n                    }\n\n                    break;\n            }\n\n            return null;\n        }\n\n        /**\n         * Get all Google voices\n         *\n         * @return a list of google voices\n         */\n        private static Google[] getEngines() {\n            return Google.values();\n        }\n\n        public static Google getGoogle(@NonNull final String voiceName) {\n\n            for (final Google g : getEngines()) {\n                if (g.getVoiceName().matches(\"(?i)\" + Pattern.quote(voiceName))) {\n                    return g;\n                }\n            }\n\n            return null;\n        }\n\n        /**\n         * Check to see if the required gender is contained in the voice name or as part of the Gender object.\n         *\n         * @param voiceName      the voice name\n         * @param requiredGender one of {@link Gender#FEMALE} or {@link Gender#MALE}\n         * @return true if the required {@link Gender} matches for the engine name\n         */\n\n        public static boolean matchGender(@NonNull final String voiceName, @NonNull final Gender requiredGender) {\n\n            for (final Google g : getEngines()) {\n                if (g.getVoiceName().matches(\"(?i)\" + Pattern.quote(voiceName))) {\n                    return g.getGender() == requiredGender;\n                }\n            }\n\n            switch (requiredGender) {\n                case FEMALE:\n                    return Gender.getGenderFromVoiceName(voiceName) == requiredGender;\n                case MALE:\n                    return Gender.getGenderFromVoiceName(voiceName) == requiredGender;\n            }\n\n            return false;\n        }\n\n        /**\n         * Get the gender from the given voice name\n         *\n         * @param voiceName the voice name\n         * @return one of {@link Gender#MALE} {@link Gender#FEMALE} or if unknown {@link Gender#UNDEFINED}\n         */\n        public static Gender getGender(@NonNull final String voiceName) {\n\n            for (final Google g : getEngines()) {\n                if (g.getVoiceName().matches(\"(?i)\" + Pattern.quote(voiceName))) {\n                    return g.getGender();\n                }\n            }\n\n            return Gender.getGenderFromVoiceName(voiceName);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/sound/SoundEffect.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.sound;\n\nimport android.support.annotation.NonNull;\n\nimport java.util.Random;\n\nimport ai.saiy.android.tts.attributes.Gender;\n\n/**\n * Created by benrandall76@gmail.com on 12/09/2016.\n */\n\npublic class SoundEffect {\n\n    public static final long SILENCE_DURATION = 350L;\n\n    public static final String FART = \"fart\";\n    private static final String FART_1 = \"fart1\";\n    private static final String FART_2 = \"fart2\";\n    private static final String FART_3 = \"fart3\";\n    public static final String BURP = \"burp\";\n    private static final String BURP_1 = \"burp1\";\n    private static final String BURP_2 = \"burp2\";\n    public static final String COUGH = \"cough\";\n    private static final String COUGH_1 = \"cough1\";\n    private static final String COUGH_2 = \"cough2\";\n    public static final String GIGGLE = \"giggle\";\n    private static final String GIGGLE_1 = \"giggle1\";\n    private static final String GIGGLE_2 = \"giggle2\";\n    public static final String CRY = \"cry\";\n    private static final String CRY_1 = \"cry1\";\n    public static final String PEE = \"pee\";\n    private static final String PEE_1 = \"pee1\";\n    public static final String SNEEZE = \"sneeze\";\n    private static final String SNEEZE_1 = \"sneeze1\";\n    public static final String PUKE = \"puke\";\n    private static final String PUKE_1 = \"puke1\";\n    public static final String FLUSH = \"flush\";\n    private static final String FLUSH_1 = \"flush1\";\n\n    private static final String[] fartArrayMale = new String[]{FART_1, FART_2, FART_3};\n    private static final String[] fartArrayFemale = new String[]{FART_1, FART_2, FART_3};\n\n    private static final String[] burpArrayMale = new String[]{BURP_1, BURP_2};\n    private static final String[] burpArrayFemale = new String[]{BURP_1, BURP_2};\n\n    private static final String[] coughArrayMale = new String[]{COUGH_2};\n    private static final String[] coughArrayFemale = new String[]{COUGH_1};\n\n    private static final String[] giggleArrayMale = new String[]{GIGGLE_2};\n    private static final String[] giggleArrayFemale = new String[]{GIGGLE_1};\n\n    private static final String[] cryArrayMale = new String[]{CRY_1};\n    private static final String[] cryArrayFemale = new String[]{CRY_1};\n\n    private static final String[] peeArrayMale = new String[]{PEE_1};\n    private static final String[] peeArrayFemale = new String[]{PEE_1};\n\n    private static final String[] sneezeArrayMale = new String[]{SNEEZE_1};\n    private static final String[] sneezeArrayFemale = new String[]{SNEEZE_1};\n\n    private static final String[] pukeArrayMale = new String[]{PUKE_1};\n    private static final String[] pukeArrayFemale = new String[]{PUKE_1};\n\n    private static final String[] flushArrayMale = new String[]{FLUSH_1};\n    private static final String[] flushArrayFemale = new String[]{FLUSH_1};\n\n    public static String getSoundEffect(@NonNull final String name, @NonNull final Gender gender) {\n\n        switch (name) {\n\n            case FART:\n                return getFart(gender);\n            case BURP:\n                return getBurp(gender);\n            case COUGH:\n                return getCough(gender);\n            case GIGGLE:\n                return getGiggle(gender);\n            case CRY:\n                return getCry(gender);\n            case PEE:\n                return getPee(gender);\n            case SNEEZE:\n                return getSneeze(gender);\n            case PUKE:\n                return getPuke(gender);\n            case FLUSH:\n                return getFlush(gender);\n            default:\n                return name;\n        }\n    }\n\n    public static String getFart(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return fartArrayMale[new Random().nextInt(fartArrayMale.length)];\n            case FEMALE:\n                return fartArrayFemale[new Random().nextInt(fartArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return fartArrayFemale[new Random().nextInt(fartArrayFemale.length)];\n        }\n    }\n\n    public static String getBurp(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return burpArrayMale[new Random().nextInt(burpArrayMale.length)];\n            case FEMALE:\n                return burpArrayFemale[new Random().nextInt(burpArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return burpArrayFemale[new Random().nextInt(burpArrayFemale.length)];\n        }\n    }\n\n    public static String getCough(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return coughArrayMale[new Random().nextInt(coughArrayMale.length)];\n            case FEMALE:\n                return coughArrayFemale[new Random().nextInt(coughArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return coughArrayFemale[new Random().nextInt(coughArrayFemale.length)];\n        }\n    }\n\n    public static String getGiggle(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return giggleArrayMale[new Random().nextInt(giggleArrayMale.length)];\n            case FEMALE:\n                return giggleArrayFemale[new Random().nextInt(giggleArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return giggleArrayFemale[new Random().nextInt(giggleArrayFemale.length)];\n        }\n    }\n\n    public static String getCry(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return cryArrayMale[new Random().nextInt(cryArrayMale.length)];\n            case FEMALE:\n                return cryArrayFemale[new Random().nextInt(cryArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return cryArrayFemale[new Random().nextInt(cryArrayFemale.length)];\n        }\n    }\n\n    public static String getPee(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return peeArrayMale[new Random().nextInt(peeArrayMale.length)];\n            case FEMALE:\n                return peeArrayFemale[new Random().nextInt(peeArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return peeArrayFemale[new Random().nextInt(peeArrayFemale.length)];\n        }\n    }\n\n    public static String getSneeze(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return sneezeArrayMale[new Random().nextInt(sneezeArrayMale.length)];\n            case FEMALE:\n                return sneezeArrayFemale[new Random().nextInt(sneezeArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return sneezeArrayFemale[new Random().nextInt(sneezeArrayFemale.length)];\n        }\n    }\n\n    public static String getPuke(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return pukeArrayMale[new Random().nextInt(pukeArrayMale.length)];\n            case FEMALE:\n                return pukeArrayFemale[new Random().nextInt(pukeArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return pukeArrayFemale[new Random().nextInt(pukeArrayFemale.length)];\n        }\n    }\n\n    public static String getFlush(@NonNull final Gender gender) {\n\n        switch (gender) {\n\n            case MALE:\n                return flushArrayMale[new Random().nextInt(flushArrayMale.length)];\n            case FEMALE:\n                return flushArrayFemale[new Random().nextInt(flushArrayFemale.length)];\n            case UNDEFINED:\n            default:\n                return flushArrayFemale[new Random().nextInt(flushArrayFemale.length)];\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/sound/SoundEffectHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.sound;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.ListIterator;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.tts.attributes.Gender;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\nimport static ai.saiy.android.api.request.SaiyRequestParams.SILENCE;\nimport static ai.saiy.android.tts.SaiyTextToSpeech.ARRAY_DELIMITER;\nimport static ai.saiy.android.tts.SaiyTextToSpeech.ARRAY_FIRST;\nimport static ai.saiy.android.tts.SaiyTextToSpeech.ARRAY_INTERIM;\nimport static ai.saiy.android.tts.SaiyTextToSpeech.ARRAY_LAST;\nimport static ai.saiy.android.tts.SaiyTextToSpeech.ARRAY_SINGLE;\n\n/**\n * Created by benrandall76@gmail.com on 12/09/2016.\n */\n\npublic class SoundEffectHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SoundEffectHelper.class.getSimpleName();\n\n    public static final Pattern pSOUND_EFFECT = Pattern.compile(\".*\\\\[[0-9A-Za-z\\\\s]+\\\\].*\", Pattern.DOTALL);\n    private static final Pattern pSOUND_EFFECT_CONTENT = Pattern.compile(\"(.*?)\\\\[(.*?)\\\\]\", Pattern.DOTALL);\n\n    private static final int REMOVE = 0;\n    private static final int SAY = 1;\n    private static final int SOUND = 2;\n\n    private final CharSequence text;\n    private final String utteranceId;\n    private final Gender gender;\n\n    private final ArrayList<SoundEffectItem> itemArray = new ArrayList<>();\n\n    public static ArrayList<String> addedItems = new ArrayList<>();\n\n    /**\n     * Constructor\n     *\n     * @param text the utterance to extract speech and sound effects from\n     */\n    public SoundEffectHelper(@NonNull final CharSequence text, @NonNull final String utteranceId,\n                             @NonNull final Gender gender) {\n        this.text = text;\n        this.utteranceId = utteranceId;\n        this.gender = gender;\n    }\n\n    /**\n     * Get the sorted speech and sound results\n     *\n     * @return the resolved {@link ArrayList} containing ordered {@link SoundEffectItem} objects\n     */\n    public ArrayList<SoundEffectItem> getArray() {\n        getValidatedArray();\n        return checkSingle();\n    }\n\n    /**\n     * Check that the array doesn't finally, only contain a single entry. If it does, the\n     * utterance id needs to be manipulated to avoid it failing TTS initialisation checks.\n     *\n     * @return the resolved {@link ArrayList} containing ordered {@link SoundEffectItem} objects\n     */\n    public ArrayList<SoundEffectItem> checkSingle() {\n\n        if (itemArray.size() == 1) {\n            itemArray.get(0).setUtteranceId(ARRAY_SINGLE + ARRAY_DELIMITER + utteranceId);\n        }\n\n        return itemArray;\n    }\n\n    private ArrayList<SoundEffectItem> getValidatedArray() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getValidatedArray: \" + itemArray.size());\n        }\n\n        if (UtilsList.notNaked(itemArray)) {\n\n            final ListIterator<SoundEffectItem> itr = itemArray.listIterator(itemArray.size() - 1);\n\n            if (itr.hasNext()) {\n                final SoundEffectItem item = itr.next();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getValidatedArray: getText: \" + item.getText());\n                    MyLog.i(CLS_NAME, \"getValidatedArray: getUtteranceId: \" + item.getUtteranceId());\n                    MyLog.i(CLS_NAME, \"getValidatedArray: soundEffect: \" + item.getItemType());\n                }\n\n                switch (item.getItemType()) {\n\n                    case SoundEffectItem.SILENCE:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getValidatedArray: removing silence\");\n                        }\n                        itr.remove();\n                        return getValidatedArray();\n                    default:\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getValidatedArray: updating to ARRAY_LAST\");\n                        }\n                        item.setUtteranceId(ARRAY_LAST + ARRAY_DELIMITER + utteranceId);\n                        break;\n                }\n            }\n        }\n\n        return itemArray;\n    }\n\n    /**\n     * Begin sorting the utterance\n     */\n    public void sort() {\n        extract(this.text.toString());\n    }\n\n    /**\n     * Method to loop over a gradually reducing utterance.\n     *\n     * @param text the utterance\n     */\n    private void extract(@Nullable final String text) {\n\n        if (UtilsString.notNaked(text)) {\n\n            String nextToCheck;\n\n            try {\n\n                final Matcher matcher = pSOUND_EFFECT_CONTENT.matcher(text);\n\n                SoundEffectItem item;\n                String remove;\n                String soundEffect;\n                String say;\n\n                if (matcher.find()) {\n\n                    remove = matcher.group(REMOVE);\n                    say = matcher.group(SAY);\n\n                    if (UtilsString.notNaked(say)) {\n\n                        item = new SoundEffectItem(UtilsString.stripLeadingPunctuation(say.trim()),\n                                SoundEffectItem.SPEECH, resolveUtteranceId());\n                        itemArray.add(item);\n                    }\n\n                    soundEffect = matcher.group(SOUND);\n                    nextToCheck = text.replaceFirst(Pattern.quote(remove), \"\");\n\n                    if (UtilsString.notNaked(soundEffect)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getSoundEffect: \" + soundEffect.trim() + \" ~ \" + gender.name());\n                        }\n\n                        item = new SoundEffectItem(SoundEffect.getSoundEffect(soundEffect.trim(), gender),\n                                SoundEffectItem.SOUND,\n                                UtilsString.notNaked(nextToCheck) ? resolveUtteranceId()\n                                        : ARRAY_LAST + ARRAY_DELIMITER + this.utteranceId);\n                        itemArray.add(item);\n\n                        if (UtilsString.notNaked(nextToCheck)\n                                && UtilsString.notNaked(\n                                UtilsString.stripLeadingPunctuation(nextToCheck.trim()))) {\n\n                            item = new SoundEffectItem(SILENCE, SoundEffectItem.SILENCE,\n                                    ARRAY_INTERIM + ARRAY_DELIMITER + this.utteranceId);\n                            itemArray.add(item);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"extract: ignoring remaining punctuation\");\n                            }\n\n                            nextToCheck = null;\n                        }\n                    }\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"extract: remove: \" + remove);\n                        MyLog.i(CLS_NAME, \"extract: say: \" + say);\n                        MyLog.i(CLS_NAME, \"extract: soundEffect: \" + soundEffect);\n                        MyLog.i(CLS_NAME, \"extract: nextToCheck: \" + nextToCheck);\n                    }\n                } else {\n\n                    if (UtilsString.notNaked(text)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"extract: no more sounds: \" + text);\n                        }\n\n                        if (UtilsString.notNaked(UtilsString.stripLeadingPunctuation(text.trim()))) {\n\n                            item = new SoundEffectItem(UtilsString.stripLeadingPunctuation(text.trim()),\n                                    SoundEffectItem.SPEECH, ARRAY_LAST + ARRAY_DELIMITER + this.utteranceId);\n                            itemArray.add(item);\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"extract: ignoring remaining punctuation\");\n                            }\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"extract: ignoring remaining white space\");\n                        }\n                    }\n\n                    nextToCheck = null;\n                }\n\n            } catch (final ArrayIndexOutOfBoundsException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"extract: ArrayIndexOutOfBoundsException\" + text);\n                    e.printStackTrace();\n                }\n                nextToCheck = null;\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"extract: NullPointerException\" + text);\n                    e.printStackTrace();\n                }\n                nextToCheck = null;\n            }\n\n            if (UtilsString.notNaked(nextToCheck)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"extract: continuing with: \" + nextToCheck);\n                }\n                extract(nextToCheck.trim());\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"extract: complete\");\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"extract: text naked: complete\");\n            }\n        }\n    }\n\n    private String resolveUtteranceId() {\n        return itemArray.isEmpty() ? ARRAY_FIRST + ARRAY_DELIMITER + utteranceId\n                : ARRAY_INTERIM + ARRAY_DELIMITER + utteranceId;\n    }\n\n    /**\n     * Store all sound effect names that have been added to the engine.\n     *\n     * @param updatedAddedItems the list of sound effect names\n     */\n    public static void setAddedItems(@NonNull final ArrayList<String> updatedAddedItems) {\n        addedItems = updatedAddedItems;\n    }\n\n    /**\n     * Get the list of sound effect names that have been added to the engine\n     *\n     * @return the {@link ArrayList} of sound effect names\n     */\n    public static ArrayList<String> getAddedItems() {\n        return addedItems;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/tts/sound/SoundEffectItem.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.tts.sound;\n\n/**\n * Created by benrandall76@gmail.com on 12/09/2016.\n */\n\npublic class SoundEffectItem {\n\n    public static final int SPEECH = 0;\n    public static final int SOUND = 1;\n    public static final int SILENCE = 2;\n\n    private String text;\n    private int itemType;\n    private String utteranceId;\n\n    /**\n     * Constructor\n     *\n     * @param text        the utterance or sound effect identifier\n     * @param itemType    one of {@link #SOUND} or {@link #SPEECH}\n     * @param utteranceId the id\n     */\n    public SoundEffectItem(final String text, final int itemType, final String utteranceId) {\n        this.text = text;\n        this.itemType = itemType;\n        this.utteranceId = utteranceId;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(final String text) {\n        this.text = text;\n    }\n\n    public int getItemType() {\n        return itemType;\n    }\n\n    public void setItemType(final int itemType) {\n        this.itemType = itemType;\n    }\n\n    public String getUtteranceId() {\n        return utteranceId;\n    }\n\n    public void setUtteranceId(final String utteranceId) {\n        this.utteranceId = utteranceId;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityAssistProxy.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.recognition.provider.saiy.assist.SaiyInteractionService;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 22/08/2016.\n */\n\npublic class ActivityAssistProxy extends Activity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityAssistProxy.class.getSimpleName();\n\n    long then;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        then = System.nanoTime();\n\n        final Intent intent = new Intent(this, SaiyInteractionService.class);\n        intent.setAction(Intent.ACTION_ASSIST);\n\n        final Bundle extras = new Bundle();\n        extras.putString(SaiyInteractionService.EXTRA_VOICE_KEYPHRASE_HINT_TEXT, SPH.getHotword(getApplicationContext()));\n        extras.putString(SaiyInteractionService.EXTRA_VOICE_KEYPHRASE_LOCALE,\n                SupportedLanguage.ENGLISH.getLanguageCountry());\n        intent.putExtras(extras);\n        getApplicationContext().startService(intent);\n\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityAssistSettings.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport ai.saiy.android.R;\n\n/**\n * Created by benrandall76@gmail.com on 22/08/2016.\n */\n\npublic class ActivityAssistSettings extends Activity {\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.assist_settings);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityChooserDialog.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionChooser;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsBundle;\n\n/**\n * Created by benrandall76@gmail.com on 12/06/2016.\n */\npublic class ActivityChooserDialog extends Activity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityChooserDialog.class.getSimpleName();\n\n    long then;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        this.setFinishOnTouchOutside(false);\n\n        then = System.nanoTime();\n\n        final Bundle bundle = getIntent().getExtras();\n\n        if (UtilsBundle.notNaked(bundle) && !UtilsBundle.isSuspicious(bundle)) {\n\n            final ArrayList<SongRecognitionChooser> chooserArray = bundle.getParcelableArrayList(\n                    SongRecognitionChooser.PARCEL_KEY);\n\n            if (UtilsList.notNaked(chooserArray)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"chooserArray: \" + chooserArray.size());\n                }\n\n                for (final SongRecognitionChooser src : chooserArray) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"PackageName: \" + src.getPackageName());\n                        MyLog.i(CLS_NAME, \"AppName: \" + src.getApplicationName());\n                        MyLog.i(CLS_NAME, \"Installed: \" + src.isInstalled());\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"chooserArray: naked\");\n                }\n            }\n        } else {\n            // TODO - no good\n        }\n\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityHome.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Vibrator;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.design.widget.NavigationView;\nimport android.support.design.widget.Snackbar;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.FragmentManager;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v4.widget.DrawerLayout;\nimport android.support.v7.app.ActionBarDrawerToggle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.Toolbar;\nimport android.util.Pair;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.ProgressBar;\nimport android.widget.Toast;\n\nimport java.util.Timer;\nimport java.util.TimerTask;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.applications.Install;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.localisation.SaiyResourcesHelper;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.ui.activity.helper.ActivityHomeHelper;\nimport ai.saiy.android.ui.fragment.FragmentAbout;\nimport ai.saiy.android.ui.fragment.FragmentAdvancedSettings;\nimport ai.saiy.android.ui.fragment.FragmentCustomisation;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.ui.fragment.FragmentSettings;\nimport ai.saiy.android.ui.fragment.FragmentSuperUser;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsBundle;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Main activity class that handles the fragment management\n * <p>\n * Created by benrandall76@gmail.com on 23/08/2016.\n */\n\npublic class ActivityHome extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,\n        FragmentManager.OnBackStackChangedListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityHome.class.getSimpleName();\n\n    private static final long ARBITRARY_WAIT = 2000L;\n    private static final long VIBRATE_MIN = 40L;\n\n    public static final int ANIMATION_NONE = 0;\n    public static final int ANIMATION_FADE = 1;\n    public static final int ANIMATION_FADE_DELAYED = 2;\n\n    public static final int INDEX_FRAGMENT_HOME = 0;\n    public static final int INDEX_FRAGMENT_SETTINGS = 1;\n    public static final int INDEX_FRAGMENT_CUSTOMISATION = 2;\n    public static final int INDEX_FRAGMENT_ADVANCED_SETTINGS = 3;\n    public static final int INDEX_FRAGMENT_SUPER_USER = 4;\n    public static final int INDEX_FRAGMENT_ABOUT = 5;\n    public static final int INDEX_FRAGMENT_BUGS = 6;\n\n    public static final int MENU_INDEX_HOME = 0;\n    public static final int MENU_INDEX_SETTINGS = 1;\n    public static final int MENU_INDEX_CUSTOMISATION = 2;\n    public static final int MENU_INDEX_ADVANCED_SETTINGS = 3;\n    public static final int MENU_INDEX_SUPER_USER = 4;\n    public static final int MENU_INDEX_ABOUT = 5;\n\n    public static final String FRAGMENT_INDEX = \"fragment_index\";\n\n    private DrawerLayout drawer;\n    private Toolbar toolbar;\n    private ProgressBar progressBar;\n    private NavigationView navigationView;\n    private Menu menu;\n\n    private ActivityHomeHelper helper = new ActivityHomeHelper();\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_home_layout);\n\n        getSupportFragmentManager().addOnBackStackChangedListener(this);\n\n        final Pair<Boolean, Integer> intentPair = startFragment(getIntent());\n\n        if (intentPair.first) {\n            final Pair<Fragment, String> fragmentPair = getFragmentAndTag(intentPair.second);\n            doFragmentReplaceTransaction(fragmentPair.first, fragmentPair.second, ANIMATION_NONE);\n        } else {\n            doFragmentReplaceTransaction(FragmentHome.newInstance(null), String.valueOf(INDEX_FRAGMENT_HOME),\n                    ANIMATION_NONE);\n        }\n\n        setupUI();\n    }\n\n    /**\n     * Give the UI some time to set up before we start work on any user interaction processes\n     */\n    @Override\n    public void onAttachedToWindow() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onAttachedToWindow\");\n        }\n\n        new Timer().schedule(new TimerTask() {\n            @Override\n            public void run() {\n\n                try {\n                    runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n                            runStartConfiguration();\n                        }\n                    });\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onAttachedToWindow:  NullPointerException\");\n                        e.printStackTrace();\n                    }\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onAttachedToWindow:  Exception\");\n                        e.printStackTrace();\n                    }\n                }\n            }\n        }, ARBITRARY_WAIT);\n    }\n\n    /**\n     * Check to see if we need to interact with the user\n     */\n    public void runStartConfiguration() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"runStartConfiguration\");\n        }\n\n        if (acceptedDisclaimer()) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"acceptedDisclaimer\");\n            }\n\n            if (hasSelectedLanguage()) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"hasSelectedLanguage\");\n                }\n\n                if (seenDeveloperNote()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"seenDeveloperNote\");\n                    }\n\n                    if (seenWhatsNew()) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"seenWhatsNew: all done\");\n                        }\n\n                        SelfAwareHelper.startSelfAwareIfRequired(getApplicationContext());\n\n                    } else {\n                        showWhatsNew();\n                    }\n                } else {\n                    showDeveloperNote();\n                }\n            } else {\n                showLanguageSelector();\n            }\n        } else {\n            showDisclaimer();\n        }\n    }\n\n    /**\n     * Check if the user has seen the most recent what's new message\n     *\n     * @return true if the message has been seen, false otherwise\n     */\n    private boolean seenWhatsNew() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"seenWhatsNew\");\n        }\n        return SPH.getWhatsNew(getApplicationContext());\n    }\n\n    /**\n     * Check if the user has seen the latest developer note\n     *\n     * @return true if the note has been seen, false otherwise\n     */\n    private boolean seenDeveloperNote() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"seenDeveloperNote\");\n        }\n        return SPH.getDeveloperNote(getApplicationContext());\n    }\n\n    /**\n     * Check if the user has chosen their supported language\n     *\n     * @return true if a language has been selected, false otherwise\n     */\n    private boolean hasSelectedLanguage() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"hasSelectedLanguage\");\n        }\n        return true;\n    }\n\n    /**\n     * Check if the user has accepted the application disclaimer\n     *\n     * @return true if the disclaimer has been accepted, false otherwise\n     */\n    private boolean acceptedDisclaimer() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"acceptedDisclaimer\");\n        }\n        return SPH.getAcceptedDisclaimer(getApplicationContext());\n    }\n\n    /**\n     * Self explanatory utility\n     */\n    private void setupUI() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setupUI\");\n        }\n\n        setupToolbar();\n        setupDrawer();\n        setupNavigation();\n    }\n\n    /**\n     * Show the user the what's new message\n     */\n    protected void showWhatsNew() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showWhatsNew\");\n        }\n        helper.showWhatsNew(this);\n    }\n\n    /**\n     * Show the user the developer note\n     */\n    protected void showDeveloperNote() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showDeveloperNote\");\n        }\n        helper.showDeveloperNote(this);\n    }\n\n    /**\n     * We need to get the user to select a supported language\n     */\n    public void showLanguageSelector() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showLanguageSelector\");\n        }\n        helper.showLanguageSelector(this);\n    }\n\n    /**\n     * Show the user the disclaimer dialog\n     */\n    protected void showDisclaimer() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showDisclaimer\");\n        }\n        helper.showDisclaimer(this);\n    }\n\n    /**\n     * Self explanatory utility\n     */\n    private void setupToolbar() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setupToolbar\");\n        }\n\n        toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        progressBar = (ProgressBar) findViewById(R.id.progress);\n    }\n\n    /**\n     * Self explanatory utility\n     */\n    private void setupDrawer() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setupDrawer\");\n        }\n\n        drawer = (DrawerLayout) findViewById(R.id.drawer_layout);\n        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(\n                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);\n        drawer.addDrawerListener(toggle);\n        toggle.syncState();\n    }\n\n    /**\n     * Self explanatory utility\n     */\n    private void setupNavigation() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setupNavigation\");\n        }\n\n        navigationView = (NavigationView) findViewById(R.id.nav_view);\n        navigationView.setNavigationItemSelectedListener(this);\n    }\n\n    /**\n     * Get the Fragment and its Tag\n     *\n     * @param fragmentIndex the fragment index constant\n     * @return an {@link Pair} with the first parameter containing the {@link Fragment} and the second the tag\n     */\n    private Pair<Fragment, String> getFragmentAndTag(final int fragmentIndex) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getFragmentAndTag\");\n        }\n\n        switch (fragmentIndex) {\n\n            case INDEX_FRAGMENT_HOME:\n                return new Pair<>((Fragment) FragmentHome.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_HOME));\n            case INDEX_FRAGMENT_SETTINGS:\n                return new Pair<>((Fragment) FragmentSettings.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_SETTINGS));\n            case INDEX_FRAGMENT_CUSTOMISATION:\n                return new Pair<>((Fragment) FragmentCustomisation.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_CUSTOMISATION));\n            case INDEX_FRAGMENT_ADVANCED_SETTINGS:\n                return new Pair<>((Fragment) FragmentAdvancedSettings.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_ADVANCED_SETTINGS));\n            case INDEX_FRAGMENT_SUPER_USER:\n                return new Pair<>((Fragment) FragmentSuperUser.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_SUPER_USER));\n            case INDEX_FRAGMENT_ABOUT:\n                return new Pair<>((Fragment) FragmentAbout.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_ABOUT));\n            default:\n                return new Pair<>((Fragment) FragmentHome.newInstance(null),\n                        String.valueOf(INDEX_FRAGMENT_HOME));\n        }\n    }\n\n    /**\n     * Check if an intent received contains extras to start a specific fragment\n     *\n     * @param intent the intent to examine\n     * @return a {@link Pair} with the first parameter denoting success and the second the fragment index constant\n     */\n    private Pair<Boolean, Integer> startFragment(@Nullable final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startFragment\");\n        }\n\n        if (intent != null) {\n\n            final Bundle bundle = intent.getExtras();\n\n            if (UtilsBundle.notNaked(bundle)) {\n                if (bundle.containsKey(FRAGMENT_INDEX)) {\n                    return new Pair<>(true, bundle.getInt(FRAGMENT_INDEX, INDEX_FRAGMENT_HOME));\n                }\n            }\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    @Override\n    protected void onNewIntent(final Intent intent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onNewIntent\");\n        }\n\n        final Pair<Boolean, Integer> intentPair = startFragment(intent);\n\n        if (intentPair.first) {\n            final Pair<Fragment, String> fragmentPair = getFragmentAndTag(intentPair.second);\n            doFragmentReplaceTransaction(fragmentPair.first, fragmentPair.second, ANIMATION_NONE);\n        }\n    }\n\n\n    @Override\n    public void onBackPressed() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBackPressed\");\n        }\n\n        if (drawer.isDrawerOpen(GravityCompat.START)) {\n            drawer.closeDrawer(GravityCompat.START);\n        } else {\n\n            if (getSupportFragmentManager().getBackStackEntryCount() == 0) {\n\n                try {\n\n                    switch (Integer.valueOf(getSupportFragmentManager().findFragmentById(R.id.fragmentContent).getTag())) {\n\n                        case INDEX_FRAGMENT_HOME:\n                            if (DEBUG) {\n                                MyLog.d(CLS_NAME, \"onBackPressed: INDEX_FRAGMENT_HOME\");\n                            }\n                            super.onBackPressed();\n                            break;\n                        default:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onBackPressed: default\");\n                            }\n\n                            doFragmentReplaceTransaction(FragmentHome.newInstance(null), String.valueOf(INDEX_FRAGMENT_HOME),\n                                    ANIMATION_FADE);\n                            navigationView.setCheckedItem(R.id.nav_home);\n                            navigationView.getMenu().getItem(INDEX_FRAGMENT_HOME).setChecked(true);\n                            break;\n                    }\n\n                } catch (final NullPointerException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onBackPressed: NullPointerException\");\n                        e.printStackTrace();\n                    }\n                    super.onBackPressed();\n                } catch (final NumberFormatException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onBackPressed: NumberFormatException\");\n                        e.printStackTrace();\n                    }\n                    super.onBackPressed();\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onBackPressed: have back stack\");\n                }\n\n                final String backStackTag = getSupportFragmentManager().getBackStackEntryAt(0).getName();\n                final int navId = getNavIdFromTag(backStackTag);\n                updateNavigationView(navId);\n                getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(\n                        R.id.fragmentContent)).commit();\n                getSupportFragmentManager().popBackStack(backStackTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);\n\n            }\n        }\n    }\n\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateOptionsMenu\");\n        }\n        this.menu = menu;\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onOptionsItemSelected\");\n        }\n\n        switch (item.getItemId()) {\n\n            case R.id.action_power:\n                if (SelfAwareHelper.selfAwareRunning(getApplicationContext())) {\n                    SPH.setSelfAwareEnabled(getApplicationContext(), false);\n                    SelfAwareHelper.stopService(getApplicationContext());\n                } else {\n                    SelfAwareHelper.startService(getApplicationContext());\n                    SPH.setSelfAwareEnabled(getApplicationContext(), true);\n                }\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n    }\n\n    public boolean isFragmentLoading(@NonNull final String tag) {\n        return getSupportFragmentManager().findFragmentById(R.id.fragmentContent).getTag().matches(tag);\n    }\n\n    @Override\n    public boolean onNavigationItemSelected(@NonNull final MenuItem item) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onNavigationItemSelected\");\n        }\n\n        drawer.closeDrawer(GravityCompat.START);\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final int currentTag = Integer.parseInt(ActivityHome.this.getSupportFragmentManager().findFragmentById(\n                        R.id.fragmentContent).getTag());\n                final int id = item.getItemId();\n                boolean proceed;\n\n                switch (currentTag) {\n\n                    case INDEX_FRAGMENT_HOME:\n                        proceed = (id != R.id.nav_home);\n                        break;\n                    case INDEX_FRAGMENT_SETTINGS:\n                        proceed = (id != R.id.nav_settings);\n                        break;\n                    case INDEX_FRAGMENT_CUSTOMISATION:\n                        proceed = (id != R.id.nav_customisation);\n                        break;\n                    case INDEX_FRAGMENT_ADVANCED_SETTINGS:\n                        proceed = (id != R.id.nav_advanced_settings);\n                        break;\n                    case INDEX_FRAGMENT_SUPER_USER:\n                        proceed = (id != R.id.nav_super_user);\n                        break;\n                    case INDEX_FRAGMENT_ABOUT:\n                        proceed = (id != R.id.nav_about);\n                        break;\n                    case INDEX_FRAGMENT_BUGS:\n                        proceed = true;\n                        break;\n                    default:\n                        proceed = false;\n                        break;\n                }\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onNavigationItemSelected: proceeding: \" + proceed);\n                }\n\n                if (proceed) {\n\n                    String tag = null;\n                    Fragment fragment = null;\n\n                    switch (id) {\n\n                        case R.id.nav_home:\n                            fragment = FragmentHome.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_HOME);\n                            break;\n                        case R.id.nav_settings:\n                            fragment = FragmentSettings.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_SETTINGS);\n                            break;\n                        case R.id.nav_customisation:\n                            fragment = FragmentCustomisation.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_CUSTOMISATION);\n                            break;\n                        case R.id.nav_advanced_settings:\n                            fragment = FragmentAdvancedSettings.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_ADVANCED_SETTINGS);\n                            break;\n                        case R.id.nav_super_user:\n                            fragment = FragmentSuperUser.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_SUPER_USER);\n                            break;\n                        case R.id.nav_about:\n                            fragment = FragmentAbout.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_ABOUT);\n                            break;\n                        case R.id.nav_share:\n                            if (!ExecuteIntent.shareIntent(getApplicationContext(),\n                                    Install.getSaiyInstallLink(getApplicationContext()))) {\n                                toast(getString(R.string.error_no_application), Toast.LENGTH_LONG);\n                            }\n                            break;\n                        case R.id.nav_feedback:\n                            if (!ExecuteIntent.sendDeveloperEmail(getApplicationContext())) {\n                                toast(getString(R.string.error_no_application), Toast.LENGTH_LONG);\n                            }\n                            break;\n                        case R.id.nav_twitter:\n                            ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_TWITTER_HANDLE);\n                            break;\n                        case R.id.nav_google_plus:\n                            ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_GOOGLE_PLUS_URL);\n                            break;\n                        case R.id.nav_forum:\n                            ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_XDA_URL);\n                            break;\n                        default:\n                            fragment = FragmentHome.newInstance(null);\n                            tag = String.valueOf(INDEX_FRAGMENT_HOME);\n                            break;\n                    }\n\n                    if (fragment != null) {\n\n                        final int backStackCount = getSupportFragmentManager().getBackStackEntryCount();\n\n                        if (backStackCount > 0) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onNavigationItemSelected: backStackCount: \" + backStackCount);\n                            }\n\n                            final String backStackTag = getSupportFragmentManager()\n                                    .getBackStackEntryAt(backStackCount - 1).getName();\n\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onNavigationItemSelected: comparing backStackTag: \" + backStackTag\n                                        + \" ~ tag: \" + tag);\n                            }\n\n                            if (backStackTag.matches(tag)) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onNavigationItemSelected: tags match\");\n                                }\n\n                                final int navId = getNavIdFromTag(backStackTag);\n                                updateNavigationView(navId);\n\n                                ActivityHome.this.runOnUiThread(new Runnable() {\n                                    @Override\n                                    public void run() {\n                                        ActivityHome.this.getSupportFragmentManager().beginTransaction().remove(ActivityHome.this.getSupportFragmentManager()\n                                                .findFragmentById(R.id.fragmentContent)).commit();\n\n                                        ActivityHome.this.getSupportFragmentManager().popBackStack(backStackTag,\n                                                FragmentManager.POP_BACK_STACK_INCLUSIVE);\n                                    }\n                                });\n\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"onNavigationItemSelected: tags different: removing stack\");\n                                }\n\n                                // TODO - remove unwanted fragment transaction from back stack. Unable to find a\n                                // TODO - way to do this without full fragment lifecycle being called.\n\n                                doFragmentReplaceTransaction(fragment, tag, ANIMATION_FADE_DELAYED);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"onNavigationItemSelected: backStackCount 0\");\n                            }\n\n                            ActivityHome.this.doFragmentReplaceTransaction(fragment, tag, ANIMATION_FADE_DELAYED);\n                        }\n                    }\n                }\n            }\n        });\n\n        return true;\n    }\n\n    /**\n     * Helper method to replace the current fragment\n     *\n     * @param fragment to transact\n     * @param tag      of the fragment\n     */\n    public void doFragmentReplaceTransaction(@NonNull final Fragment fragment, @NonNull final String tag,\n                                             final int fade) {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final int navId = getNavIdFromTag(tag);\n\n                ActivityHome.this.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        ActivityHome.this.updateNavigationView(navId);\n\n                        if (menu != null) {\n                            menu.findItem(R.id.action_power).setVisible(Integer.parseInt(tag) == INDEX_FRAGMENT_HOME);\n                        }\n\n                        switch (fade) {\n\n                            case ANIMATION_NONE:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .replace(R.id.fragmentContent, fragment, tag).commit();\n                                break;\n                            case ANIMATION_FADE:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .setCustomAnimations(R.anim.fade_in_slow, R.anim.none)\n                                        .replace(R.id.fragmentContent, fragment, tag).commit();\n                                break;\n                            case ANIMATION_FADE_DELAYED:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .setCustomAnimations(R.anim.fade_in_slow_delayed, R.anim.none)\n                                        .replace(R.id.fragmentContent, fragment, tag).commit();\n                                break;\n                        }\n                    }\n                });\n            }\n        });\n    }\n\n    /**\n     * Helper method to replace the current fragment\n     *\n     * @param fragment to transact\n     * @param tag      of the fragment\n     */\n    public void doFragmentAddTransaction(@NonNull final Fragment fragment, @NonNull final String tag,\n                                         final int fade, final int backStackTag) {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final int navId = ActivityHome.this.getNavIdFromTag(tag);\n\n                ActivityHome.this.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        ActivityHome.this.updateNavigationView(navId);\n\n                        if (menu != null) {\n                            menu.findItem(R.id.action_power).setVisible(Integer.parseInt(tag) == INDEX_FRAGMENT_HOME);\n                        }\n\n                        switch (fade) {\n\n                            case ANIMATION_NONE:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .replace(R.id.fragmentContent, fragment, tag)\n                                        .addToBackStack(String.valueOf(backStackTag)).commit();\n                                break;\n                            case ANIMATION_FADE:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .setCustomAnimations(R.anim.fade_in_slow, R.anim.none)\n                                        .replace(R.id.fragmentContent, fragment, tag)\n                                        .addToBackStack(String.valueOf(backStackTag)).commit();\n                                break;\n                            case ANIMATION_FADE_DELAYED:\n                                ActivityHome.this.getSupportFragmentManager().beginTransaction()\n                                        .setCustomAnimations(R.anim.fade_in_slow_delayed, R.anim.none)\n                                        .replace(R.id.fragmentContent, fragment, tag)\n                                        .addToBackStack(String.valueOf(backStackTag)).commit();\n                                break;\n                        }\n                    }\n                });\n            }\n        });\n    }\n\n    private void updateNavigationView(final int navId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"updateNavigationView: \" + navId);\n        }\n\n        if (isActive()) {\n\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n\n                    if (navId == 0) {\n\n                        switch (Integer.valueOf(ActivityHome.this.getSupportFragmentManager().findFragmentById(R.id.fragmentContent).getTag())) {\n\n                            case INDEX_FRAGMENT_HOME:\n                                navigationView.getMenu().getItem(MENU_INDEX_HOME).setChecked(false);\n                                break;\n                            case INDEX_FRAGMENT_SETTINGS:\n                                navigationView.getMenu().getItem(MENU_INDEX_SETTINGS).setChecked(false);\n                                break;\n                            case INDEX_FRAGMENT_CUSTOMISATION:\n                                navigationView.getMenu().getItem(MENU_INDEX_CUSTOMISATION).setChecked(false);\n                                break;\n                            case INDEX_FRAGMENT_ADVANCED_SETTINGS:\n                                navigationView.getMenu().getItem(MENU_INDEX_ADVANCED_SETTINGS).setChecked(false);\n                                break;\n                            case INDEX_FRAGMENT_SUPER_USER:\n                                navigationView.getMenu().getItem(MENU_INDEX_SUPER_USER).setChecked(false);\n                                break;\n                            case INDEX_FRAGMENT_ABOUT:\n                                navigationView.getMenu().getItem(MENU_INDEX_ABOUT).setChecked(false);\n                                break;\n                        }\n\n                    } else {\n\n                        switch (navId) {\n\n                            case R.id.nav_home:\n                                navigationView.getMenu().getItem(MENU_INDEX_HOME).setChecked(true);\n                                break;\n                            case R.id.nav_settings:\n                                navigationView.getMenu().getItem(MENU_INDEX_SETTINGS).setChecked(true);\n                                break;\n                            case R.id.nav_customisation:\n                                navigationView.getMenu().getItem(MENU_INDEX_CUSTOMISATION).setChecked(true);\n                                break;\n                            case R.id.nav_advanced_settings:\n                                navigationView.getMenu().getItem(MENU_INDEX_ADVANCED_SETTINGS).setChecked(true);\n                                break;\n                            case R.id.nav_super_user:\n                                navigationView.getMenu().getItem(MENU_INDEX_SUPER_USER).setChecked(true);\n                                break;\n                            case R.id.nav_about:\n                                navigationView.getMenu().getItem(MENU_INDEX_ABOUT).setChecked(true);\n                                break;\n                        }\n\n                        navigationView.setCheckedItem(navId);\n                    }\n                }\n            });\n        }\n    }\n\n    /**\n     * Get the navigation id corresponding to the Fragment's tag\n     *\n     * @param tag of the Fragment\n     * @return the navigation id\n     */\n    private int getNavIdFromTag(@NonNull final String tag) {\n\n        switch (Integer.parseInt(tag)) {\n\n            case INDEX_FRAGMENT_HOME:\n                return R.id.nav_home;\n            case INDEX_FRAGMENT_SETTINGS:\n                return R.id.nav_settings;\n            case INDEX_FRAGMENT_CUSTOMISATION:\n                return R.id.nav_customisation;\n            case INDEX_FRAGMENT_ADVANCED_SETTINGS:\n                return R.id.nav_advanced_settings;\n            case INDEX_FRAGMENT_SUPER_USER:\n                return R.id.nav_super_user;\n            case INDEX_FRAGMENT_ABOUT:\n                return R.id.nav_about;\n            default:\n                return 0;\n        }\n    }\n\n    /**\n     * Expose the {@link DrawerLayout}\n     *\n     * @return the layout\n     */\n    public DrawerLayout getDrawer() {\n        return this.drawer;\n    }\n\n    /**\n     * Utility method to show or hide the progress bar\n     *\n     * @param visible true to show, false to hide\n     */\n    public void showProgress(final boolean visible) {\n        if (isActive()) {\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    progressBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);\n                }\n            });\n        }\n    }\n\n    /**\n     * Utility method to vocalise any UI content or descriptions\n     *\n     * @param resId  to the String to speak\n     * @param action one of {@link LocalRequest#ACTION_SPEAK_ONLY} {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     */\n    public void speak(final int resId, final int action) {\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(SPH.getVRLocale(ActivityHome.this.getApplicationContext()));\n                ActivityHome.this.speak(SaiyResourcesHelper.getStringResource(getApplicationContext(), sl, resId), action);\n            }\n        }).start();\n    }\n\n    /**\n     * Utility method to vocalise any UI content or descriptions\n     *\n     * @param utterance to the String to speak\n     * @param action    one of {@link LocalRequest#ACTION_SPEAK_ONLY} {@link LocalRequest#ACTION_SPEAK_LISTEN}\n     */\n    public void speak(@NonNull final String utterance, final int action) {\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                final LocalRequest request = new LocalRequest(ActivityHome.this.getApplicationContext());\n                request.prepareDefault(action, utterance);\n                request.execute();\n            }\n        }).start();\n    }\n\n    /**\n     * Utility method to toast making sure it's on the main thread\n     *\n     * @param text   to toast\n     * @param length one of {@link Toast#LENGTH_SHORT} {@link Toast#LENGTH_LONG}\n     */\n    public void toast(@Nullable final String text, final int length) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"makeToast: \" + text);\n        }\n\n        if (UtilsString.notNaked(text)) {\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    Toast.makeText(ActivityHome.this.getApplicationContext(), text, length).show();\n                }\n            });\n        }\n    }\n\n    /**\n     * Utility method to show a snack bar object\n     *\n     * @param view     the parent view to attach\n     * @param text     to show\n     * @param toAction the action\n     * @param length   one of {@link Snackbar#LENGTH_SHORT} {@link Snackbar#LENGTH_LONG}\n     *                 {@link Snackbar#LENGTH_INDEFINITE}\n     */\n    public void snack(@Nullable final View view, @Nullable final String text, final int length, @Nullable final String toAction,\n                      @Nullable final View.OnClickListener onClickListener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"snack: \" + text);\n        }\n\n        if (view != null && UtilsString.notNaked(text)) {\n\n            if (UtilsString.notNaked(toAction)) {\n                runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Snackbar.make(view, text, length).setAction(toAction, onClickListener).show();\n                    }\n                });\n            } else {\n                runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Snackbar.make(view, text, length).show();\n                    }\n                });\n            }\n        }\n    }\n\n    /**\n     * Utility method to provide haptic feedback\n     */\n    public void vibrate() {\n        if (SPH.getVibrateCondition(getApplicationContext())) {\n            ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(VIBRATE_MIN);\n        }\n    }\n\n    /**\n     * Called whenever the contents of the back stack change.\n     */\n    @Override\n    public void onBackStackChanged() {\n\n        final int count = getSupportFragmentManager().getBackStackEntryCount();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onBackStackChanged: count: \" + count);\n        }\n\n        if (count > 0) {\n            switch (Integer.valueOf(getSupportFragmentManager().getBackStackEntryAt(count - 1).getName())) {\n\n                case INDEX_FRAGMENT_HOME:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_HOME\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_SETTINGS:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_SETTINGS\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_CUSTOMISATION:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_CUSTOMISATION\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_ADVANCED_SETTINGS:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_ADVANCED_SETTINGS\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_SUPER_USER:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_SUPER_USER\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_ABOUT:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_ABOUT\");\n                    }\n                    break;\n                case INDEX_FRAGMENT_BUGS:\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onBackStackChanged: INDEX_FRAGMENT_BUGS\");\n                    }\n                    break;\n                default:\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onBackStackChanged: default\");\n                    }\n                    break;\n            }\n        }\n    }\n\n    public boolean isActive() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return !isDestroyed() && !isFinishing();\n        } else {\n            return !isFinishing();\n        }\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n\n        if (!SPH.getSelfAwareEnabled(getApplicationContext()) &&\n                SelfAwareHelper.selfAwareRunning(getApplicationContext())) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onDestroy: stopping service\");\n            }\n            SelfAwareHelper.stopService(getApplicationContext());\n        }\n\n        System.gc();\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityIssue.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport ai.saiy.android.error.Issue;\nimport ai.saiy.android.error.IssueContent;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 16/04/2016.\n */\npublic class ActivityIssue extends Activity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityIssue.class.getSimpleName();\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        final Bundle bundle = getIntent().getExtras();\n\n        if (bundle != null && !bundle.isEmpty()) {\n            if (bundle.containsKey(Issue.ISSUE_CONTENT)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"bundle contains: IssueContent.ISSUE_CONTENT\");\n                }\n\n                final IssueContent issueContent = (IssueContent) bundle.getSerializable(Issue.ISSUE_CONTENT);\n\n                if (issueContent != null) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"issueContent: \" + issueContent.getIssueText());\n                    }\n\n                    switch (issueContent.getIssueConstant()) {\n\n                        case Issue.ISSUE_NO_VR:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"ISSUE_NO_VR\");\n                            }\n                            break;\n                        case Issue.ISSUE_NO_TTS_ENGINE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"ISSUE_NO_TTS_ENGINE\");\n                            }\n                            break;\n                        case Issue.ISSUE_NO_TTS_LANGUAGE:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"ISSUE_NO_TTS_LANGUAGE\");\n                            }\n                            break;\n                        case Issue.ISSUE_VLINGO:\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"ISSUE_VLINGO\");\n                            }\n                            break;\n                        default:\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"Issue default\");\n                            }\n                            break;\n                    }\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"bundle missing: IssueContent null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"bundle missing: IssueContent.ISSUE_CONTENT\");\n            }\n        }\n\n        finish();\n    }\n\n\n    @Override\n    protected void onDestroy() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n        super.onDestroy();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityLauncherShortcut.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport ai.saiy.android.service.helper.AssistantIntentService;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic class ActivityLauncherShortcut extends Activity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityLauncherShortcut.class.getSimpleName();\n\n    long then;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        then = System.nanoTime();\n\n        final Intent intent = new Intent(getApplicationContext(), AssistantIntentService.class);\n        intent.setAction(getIntent().getAction());\n        intent.putExtras(getIntent());\n        getApplicationContext().startService(intent);\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityPermissionDialog.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.ActivityCompat;\n\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.ui.notification.NotificationHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsBundle;\n\n/**\n * Short-lived Activity class to handle the permission requests from the user.\n * <p>\n * Created by benrandall76@gmail.com on 12/04/2016.\n */\npublic class ActivityPermissionDialog extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityPermissionDialog.class.getSimpleName();\n\n    private long then;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        this.setFinishOnTouchOutside(false);\n\n        then = System.nanoTime();\n\n        final Bundle bundle = getIntent().getExtras();\n\n        if (UtilsBundle.notNaked(bundle) && !UtilsBundle.isSuspicious(bundle)) {\n\n            if (bundle.containsKey(PermissionHelper.REQUESTED_PERMISSION)) {\n\n                final String[] permissions = bundle.getStringArray(PermissionHelper.REQUESTED_PERMISSION);\n\n                if (permissions != null && permissions.length > 0) {\n\n                    if (bundle.containsKey(PermissionHelper.REQUESTED_PERMISSION_ID)) {\n\n                        final int permissionsId = bundle.getInt(PermissionHelper.REQUESTED_PERMISSION_ID, 0);\n\n                        if (permissionsId > 0) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"extractPermission: extract successful\");\n                            }\n\n                            if (shouldShowRequestPermissionRationale(permissions)) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"User has previously denied\");\n                                }\n                                createPermissionsNotification(permissionsId);\n                            } else {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"First time request\");\n                                }\n                                ActivityCompat.requestPermissions(ActivityPermissionDialog.this, permissions, permissionsId);\n                            }\n                        } else {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"extractPermission: bundle REQUESTED_PERMISSION_ID 0\");\n                            }\n                            createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"extractPermission: bundle missing REQUESTED_PERMISSION_ID\");\n                        }\n                        createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"extractPermission: bundle REQUESTED_PERMISSION naked\");\n                    }\n                    createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n                }\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"extractPermission: bundle missing REQUESTED_PERMISSION\");\n                }\n                createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"extractPermission: bundle null or empty\");\n            }\n            createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(final int requestCode, @NonNull final String permissions[],\n                                           @NonNull final int[] grantResults) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onRequestPermissionsResult\");\n        }\n\n        switch (requestCode) {\n\n            case PermissionHelper.REQUEST_AUDIO:\n\n                if (granted(grantResults)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onRequestPermissionsResult: REQUEST_AUDIO: PERMISSION_GRANTED\");\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onRequestPermissionsResult: REQUEST_AUDIO: PERMISSION_DENIED\");\n                    }\n                    createPermissionsNotification(requestCode);\n                }\n                break;\n            case PermissionHelper.REQUEST_FILE:\n\n                if (granted(grantResults)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onRequestPermissionsResult: REQUEST_FILE: PERMISSION_GRANTED\");\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onRequestPermissionsResult: REQUEST_FILE: PERMISSION_DENIED\");\n                    }\n                    createPermissionsNotification(requestCode);\n                }\n                break;\n            case PermissionHelper.REQUEST_GROUP_CONTACTS:\n\n                if (granted(grantResults)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"onRequestPermissionsResult: REQUEST_GROUP_CONTACTS: PERMISSION_GRANTED\");\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"onRequestPermissionsResult: REQUEST_GROUP_CONTACTS: PERMISSION_DENIED\");\n                    }\n                    createPermissionsNotification(requestCode);\n                }\n                break;\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onRequestPermissionsResult: Unknown request?\");\n                }\n                createPermissionsNotification(PermissionHelper.REQUEST_UNKNOWN);\n                break;\n        }\n\n        finish();\n    }\n\n    /**\n     * Loop through the permissions we are about to request and check if a rationale is required\n     * for any of them\n     *\n     * @param permissions the String array of permissions that will be requested\n     * @return true if any of the permissions require a rationale to be shown.\n     */\n    private boolean shouldShowRequestPermissionRationale(@NonNull final String[] permissions) {\n        for (final String permission : permissions) {\n            if (ActivityCompat.shouldShowRequestPermissionRationale(ActivityPermissionDialog.this, permission)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    /**\n     * Helper method to confirm if the permissions were granted or denied\n     *\n     * @param grantResults from the permission listener\n     * @return true if the permissions were granted. False otherwise\n     */\n    private boolean granted(int[] grantResults) {\n        for (final int result : grantResults) {\n            if (result != PackageManager.PERMISSION_GRANTED) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * We deal with permission rationale via speech, rather than a dialog.\n     *\n     * @param permissionId constant id from {@link PermissionHelper} of the requested permission\n     */\n    private void createPermissionsNotification(final int permissionId) {\n        NotificationHelper.createPermissionsNotification(getApplicationContext(), permissionId);\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/ActivityTilePreferences.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 13/07/2016.\n */\n\npublic class ActivityTilePreferences extends Activity {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityTilePreferences.class.getSimpleName();\n\n    long then;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        then = System.nanoTime();\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n            MyLog.getElapsed(CLS_NAME, then);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/activity/helper/ActivityHomeHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.activity.helper;\n\nimport android.app.Activity;\nimport android.content.DialogInterface;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.text.Html;\nimport android.view.View;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 26/08/2016.\n */\n\npublic class ActivityHomeHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = ActivityHomeHelper.class.getSimpleName();\n\n    /**\n     * Show the applications disclaimer\n     *\n     * @param act the Activity context in which to display the dialog\n     */\n    @SuppressWarnings(\"deprecation, ConstantConditions\")\n    public void showDisclaimer(@NonNull final Activity act) {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(act)\n                .title(R.string.menu_application_disclaimer)\n                .content(Html.fromHtml(act.getApplicationContext().getString(R.string.content_disclaimer)))\n                .positiveText(R.string.menu_accept)\n                .negativeText(R.string.menu_uninstall)\n                .iconRes(R.drawable.ic_gavel)\n                .autoDismiss(false)\n                .canceledOnTouchOutside(false)\n                .backgroundColorRes(R.color.colorTint)\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showDisclaimer: onPositive\");\n                        }\n\n                        SPH.setAcceptedDisclaimer(act.getApplicationContext());\n                        dialog.dismiss();\n                        ((ActivityHome) act).runStartConfiguration();\n                    }\n                })\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showDisclaimer: onNegative\");\n                        }\n\n                        SelfAwareHelper.stopService(act.getApplicationContext());\n                        ExecuteIntent.uninstallApp(act.getApplicationContext(), act.getPackageName());\n                        dialog.dismiss();\n                        act.finish();\n                    }\n                })\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showDisclaimer: onCancel\");\n                        }\n\n                        SelfAwareHelper.stopService(act.getApplicationContext());\n                        ExecuteIntent.uninstallApp(act.getApplicationContext(), act.getPackageName());\n                        dialog.dismiss();\n                        act.finish();\n                    }\n                }).build();\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    /**\n     * Show the developer note\n     *\n     * @param act the Activity context in which to display the dialog\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showDeveloperNote(@NonNull final Activity act) {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(act)\n                .title(R.string.menu_developer_note)\n                .content(R.string.content_developer_note)\n                .positiveText(R.string.menu_lets_do_it)\n                .iconRes(R.drawable.ic_note_text)\n                .backgroundColorRes(R.color.colorTint)\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showDeveloperNote: onPositive\");\n                        }\n\n                        SPH.setDeveloperNote(act.getApplicationContext());\n                        dialog.dismiss();\n                        ((ActivityHome) act).runStartConfiguration();\n                    }\n                })\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showDeveloperNote: onCancel\");\n                        }\n\n                        SPH.setDeveloperNote(act.getApplicationContext());\n                        dialog.dismiss();\n                        ((ActivityHome) act).runStartConfiguration();\n                    }\n                }).build();\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_right;\n        materialDialog.show();\n    }\n\n    /**\n     * Show the what's new information\n     *\n     * @param act the Activity context in which to display the dialog\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showWhatsNew(@NonNull final Activity act) {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(act)\n                .title(R.string.menu_whats_new)\n                .content(R.string.content_whats_new)\n                .positiveText(R.string.menu_excited)\n                .iconRes(R.drawable.ic_info)\n                .backgroundColorRes(R.color.colorTint)\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showWhatsNew: onPositive\");\n                        }\n\n                        SPH.setWhatsNew(act.getApplicationContext());\n                        dialog.dismiss();\n                        ((ActivityHome) act).runStartConfiguration();\n                    }\n                })\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showWhatsNew: onCancel\");\n                        }\n\n                        SPH.setWhatsNew(act.getApplicationContext());\n                        dialog.dismiss();\n                        ((ActivityHome) act).runStartConfiguration();\n                    }\n                }).build();\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    /**\n     * Show the supported language selector\n     *\n     * @param act the Activity context in which to display the dialog\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showLanguageSelector(@NonNull final Activity act) {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final String[] languages = act.getResources().getStringArray(R.array.array_supported_languages);\n\n                for (int i = 0; i < languages.length; i++) {\n                    languages[i] = StringUtils.capitalize(languages[i]);\n                }\n\n                act.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        final MaterialDialog materialDialog = new MaterialDialog.Builder(act)\n                                .autoDismiss(false)\n                                .alwaysCallSingleChoiceCallback()\n                                .title(R.string.menu_supported_languages)\n                                .items((CharSequence[]) languages)\n                                .itemsDisabledIndices(1, 2, 3, 4, 5, 6, 7)\n                                .content(R.string.content_supported_languages)\n                                .positiveText(R.string.menu_select)\n                                .iconRes(R.drawable.ic_language)\n                                .backgroundColorRes(R.color.colorTint)\n                                .itemsCallbackSingleChoice(0, new MaterialDialog.ListCallbackSingleChoice() {\n                                    @Override\n                                    public boolean onSelection(final MaterialDialog dialog, final View view, final int which, final CharSequence text) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showLanguageSelector: onSelection: \" + which + \": \" + text);\n                                        }\n                                        return true;\n                                    }\n                                })\n                                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showLanguageSelector: onPositive: \" + dialog.getSelectedIndex());\n                                        }\n\n                                        dialog.dismiss();\n                                    }\n                                })\n                                .cancelListener(new DialogInterface.OnCancelListener() {\n                                    @Override\n                                    public void onCancel(final DialogInterface dialog) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showLanguageSelector: onCancel\");\n                                        }\n\n                                        dialog.dismiss();\n                                    }\n                                }).build();\n\n                        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                        materialDialog.show();\n                    }\n                });\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/components/DividerItemDecoration.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 ai.saiy.android.ui.components;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n\n/**\n * Class adapted from https://gist.github.com/zokipirlo/82336d89249e05bba5aa originally taken from\n * https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src/com/example/android/supportv7/widget/decorator/DividerItemDecoration.java\n *\n * @author @zokipirlo\n *         Created by benrandall76@gmail.com on 25/07/2016.\n */\npublic class DividerItemDecoration extends RecyclerView.ItemDecoration {\n\n    private final Drawable mDivider;\n    private final boolean mShowFirstDivider = false;\n    private final boolean mShowLastDivider = false;\n\n    private int mOrientation = -1;\n\n    public DividerItemDecoration(Context context, AttributeSet attrs) {\n        final TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider});\n        mDivider = a.getDrawable(0);\n        a.recycle();\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {\n        super.getItemOffsets(outRect, view, parent, state);\n        if (mDivider == null) {\n            return;\n        }\n\n        int position = parent.getChildAdapterPosition(view);\n        if (position == RecyclerView.NO_POSITION || (position == 0 && !mShowFirstDivider)) {\n            return;\n        }\n\n        if (mOrientation == -1)\n            getOrientation(parent);\n\n        if (mOrientation == LinearLayoutManager.VERTICAL) {\n            outRect.top = mDivider.getIntrinsicHeight();\n            if (mShowLastDivider && position == (state.getItemCount() - 1)) {\n                outRect.bottom = outRect.top;\n            }\n        } else {\n            outRect.left = mDivider.getIntrinsicWidth();\n            if (mShowLastDivider && position == (state.getItemCount() - 1)) {\n                outRect.right = outRect.left;\n            }\n        }\n    }\n\n    @Override\n    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        if (mDivider == null) {\n            super.onDrawOver(c, parent, state);\n            return;\n        }\n\n        // Initialization needed to avoid compiler warning\n        int left = 0, right = 0, top = 0, bottom = 0, size;\n        int orientation = mOrientation != -1 ? mOrientation : getOrientation(parent);\n        int childCount = parent.getChildCount();\n\n        if (orientation == LinearLayoutManager.VERTICAL) {\n            size = mDivider.getIntrinsicHeight();\n            left = parent.getPaddingLeft();\n            right = parent.getWidth() - parent.getPaddingRight();\n        } else { //horizontal\n            size = mDivider.getIntrinsicWidth();\n            top = parent.getPaddingTop();\n            bottom = parent.getHeight() - parent.getPaddingBottom();\n        }\n\n        for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) {\n            View child = parent.getChildAt(i);\n            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();\n\n            if (orientation == LinearLayoutManager.VERTICAL) {\n                top = child.getTop() - params.topMargin - size;\n                bottom = top + size;\n            } else { //horizontal\n                left = child.getLeft() - params.leftMargin;\n                right = left + size;\n            }\n            mDivider.setBounds(left, top, right, bottom);\n            mDivider.draw(c);\n        }\n\n        // show last divider\n        if (mShowLastDivider && childCount > 0) {\n            View child = parent.getChildAt(childCount - 1);\n            if (parent.getChildAdapterPosition(child) == (state.getItemCount() - 1)) {\n                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();\n                if (orientation == LinearLayoutManager.VERTICAL) {\n                    top = child.getBottom() + params.bottomMargin;\n                    bottom = top + size;\n                } else { // horizontal\n                    left = child.getRight() + params.rightMargin;\n                    right = left + size;\n                }\n                mDivider.setBounds(left, top, right, bottom);\n                mDivider.draw(c);\n            }\n        }\n    }\n\n    private int getOrientation(RecyclerView parent) {\n        if (mOrientation == -1) {\n            if (parent.getLayoutManager() instanceof LinearLayoutManager) {\n                LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();\n                mOrientation = layoutManager.getOrientation();\n            } else {\n                throw new IllegalStateException(\n                        \"DividerItemDecoration can only be used with a LinearLayoutManager.\");\n            }\n        }\n        return mOrientation;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/components/UIBugsAdapter.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.components;\n\nimport android.support.annotation.NonNull;\nimport android.support.v7.widget.CardView;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.containers.ContainerUI;\n\n/**\n * Utility class for the main user interface layout\n * <p>\n * Created by benrandall76@gmail.com on 19/07/2016.\n */\n\npublic class UIBugsAdapter extends RecyclerView.Adapter<UIBugsAdapter.ViewHolder> {\n\n    private final ArrayList<ContainerUI> mObjects;\n    private final View.OnClickListener onClickListener;\n    private final View.OnLongClickListener onLongClickListener;\n\n    /**\n     * Constructor\n     *\n     * @param mObjects            list of {@link ContainerUI} elements\n     * @param onClickListener     the listener of the parent\n     * @param onLongClickListener the long click listener of the parent\n     */\n    public UIBugsAdapter(@NonNull final ArrayList<ContainerUI> mObjects,\n                         @NonNull final View.OnClickListener onClickListener,\n                         @NonNull final View.OnLongClickListener onLongClickListener) {\n        this.mObjects = mObjects;\n        this.onClickListener = onClickListener;\n        this.onLongClickListener = onLongClickListener;\n    }\n\n    /**\n     * Set the contents of the view holder\n     */\n    @SuppressWarnings(\"WeakerAccess\")\n    protected class ViewHolder extends RecyclerView.ViewHolder {\n\n        private CardView itemContainer;\n        private TextView title;\n        private TextView subtitle;\n        private ImageView iconMain;\n        private ImageView iconExtra;\n\n        /**\n         * Constructor\n         *\n         * @param view containing our layout items\n         */\n        private ViewHolder(@NonNull final View view) {\n            super(view);\n\n            itemContainer = (CardView) view.findViewById(R.id.cardView);\n            title = (TextView) view.findViewById(R.id.title);\n            subtitle = (TextView) view.findViewById(R.id.subtitle);\n            iconMain = (ImageView) view.findViewById(R.id.iconMain);\n            iconExtra = (ImageView) view.findViewById(R.id.iconExtra);\n        }\n    }\n\n    @Override\n    public UIBugsAdapter.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {\n        return new ViewHolder(\n                LayoutInflater.from(\n                        parent.getContext()).inflate(R.layout.cardview_bugs_item, parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(final ViewHolder holder, final int position) {\n\n        holder.title.setText(mObjects.get(position).getTitle());\n        holder.subtitle.setText(mObjects.get(position).getSubtitle());\n        holder.iconMain.setImageResource(mObjects.get(position).getIconMain());\n        holder.iconExtra.setImageResource(mObjects.get(position).getIconExtra());\n        holder.itemContainer.setOnClickListener(onClickListener);\n        holder.itemContainer.setOnLongClickListener(onLongClickListener);\n        holder.itemContainer.setTag(position);\n    }\n\n    @Override\n    public int getItemCount() {\n        return mObjects.size();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/components/UIMainAdapter.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.components;\n\nimport android.support.annotation.NonNull;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.containers.ContainerUI;\n\n/**\n * Utility class for the main user interface layout\n * <p>\n * Created by benrandall76@gmail.com on 19/07/2016.\n */\n\npublic class UIMainAdapter extends RecyclerView.Adapter<UIMainAdapter.ViewHolder> {\n\n    private final ArrayList<ContainerUI> mObjects;\n    private final View.OnClickListener onClickListener;\n    private final View.OnLongClickListener onLongClickListener;\n\n    /**\n     * Constructor\n     *\n     * @param mObjects            list of {@link ContainerUI} elements\n     * @param onClickListener     the listener of the parent\n     * @param onLongClickListener the long click listener of the parent\n     */\n    public UIMainAdapter(@NonNull final ArrayList<ContainerUI> mObjects,\n                         @NonNull final View.OnClickListener onClickListener,\n                         @NonNull final View.OnLongClickListener onLongClickListener) {\n        this.mObjects = mObjects;\n        this.onClickListener = onClickListener;\n        this.onLongClickListener = onLongClickListener;\n    }\n\n    /**\n     * Set the contents of the view holder\n     */\n    @SuppressWarnings(\"WeakerAccess\")\n    protected class ViewHolder extends RecyclerView.ViewHolder {\n\n        private final LinearLayout itemContainer;\n        private final TextView title;\n        private final TextView subtitle;\n        private final ImageView iconMain;\n        private final ImageView iconExtra;\n\n        /**\n         * Constructor\n         *\n         * @param view containing our layout items\n         */\n        private ViewHolder(@NonNull final View view) {\n            super(view);\n\n            itemContainer = (LinearLayout) view.findViewById(R.id.itemContainer);\n            title = (TextView) view.findViewById(R.id.title);\n            subtitle = (TextView) view.findViewById(R.id.subtitle);\n            iconMain = (ImageView) view.findViewById(R.id.iconMain);\n            iconExtra = (ImageView) view.findViewById(R.id.iconExtra);\n        }\n    }\n\n    @Override\n    public UIMainAdapter.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {\n        return new ViewHolder(\n                LayoutInflater.from(\n                        parent.getContext()).inflate(R.layout.layout_item_ui_main, parent, false));\n    }\n\n    @Override\n    public void onBindViewHolder(final ViewHolder holder, final int position) {\n\n        holder.title.setText(mObjects.get(position).getTitle());\n        holder.subtitle.setText(mObjects.get(position).getSubtitle());\n        holder.iconMain.setImageResource(mObjects.get(position).getIconMain());\n        holder.iconExtra.setImageResource(mObjects.get(position).getIconExtra());\n        holder.itemContainer.setOnClickListener(onClickListener);\n        holder.itemContainer.setOnLongClickListener(onLongClickListener);\n        holder.itemContainer.setTag(position);\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return mObjects.size();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/containers/ContainerCustomisation.java",
    "content": "/*\n * Copyright (c) 2017. Saiy® Ltd. All Rights Reserved.\n *\n * Unauthorised copying of this file, via any medium is strictly prohibited. Proprietary and confidential\n */\n\npackage ai.saiy.android.ui.containers;\n\nimport android.support.annotation.NonNull;\n\nimport java.io.Serializable;\n\nimport ai.saiy.android.custom.Custom;\n\n/**\n * Created by benrandall76@gmail.com on 27/01/2017.\n */\n\npublic class ContainerCustomisation implements Serializable {\n\n    private static final long serialVersionUID = -630509878907795203L;\n\n    private final Custom custom;\n    private String serialised;\n\n    private String title;\n    private String subtitle;\n\n    private int iconMain;\n    private int iconExtra;\n\n    private long rowId;\n\n\n    /**\n     * Constructor\n     *\n     * @param custom     the {@link Custom} type\n     * @param serialised the serialised string of the customisation\n     * @param title      of the element\n     * @param subtitle   of the element\n     * @param iconMain   main icon\n     * @param iconExtra  secondary icon\n     */\n    public ContainerCustomisation(@NonNull final Custom custom, @NonNull final String serialised, @NonNull final String title,\n                                  @NonNull final String subtitle, final long rowId, final int iconMain, final int iconExtra) {\n        this.custom = custom;\n        this.serialised = serialised;\n        this.title = title;\n        this.subtitle = subtitle;\n        this.iconMain = iconMain;\n        this.iconExtra = iconExtra;\n        this.rowId = rowId;\n    }\n\n    public long getRowId() {\n        return rowId;\n    }\n\n    public Custom getCustom() {\n        return custom;\n    }\n\n    public String getSerialised() {\n        return serialised;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getSubtitle() {\n        return subtitle;\n    }\n\n    public int getIconMain() {\n        return iconMain;\n    }\n\n    public int getIconExtra() {\n        return iconExtra;\n    }\n\n    public void setIconExtra(final int iconExtra) {\n        this.iconExtra = iconExtra;\n    }\n\n    public void setIconMain(final int iconMain) {\n        this.iconMain = iconMain;\n    }\n\n    public void setRowId(final long rowId) {\n        this.rowId = rowId;\n    }\n\n    public void setSerialised(final String serialised) {\n        this.serialised = serialised;\n    }\n\n    public void setSubtitle(final String subtitle) {\n        this.subtitle = subtitle;\n    }\n\n    public void setTitle(final String title) {\n        this.title = title;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/containers/ContainerUI.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.containers;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Class for the most commonly used UI list elements\n * <p>\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class ContainerUI {\n\n    private String title;\n    private String subtitle;\n\n    private int iconMain;\n    private int iconExtra;\n\n    public ContainerUI() {\n    }\n\n    /**\n     * Constructor\n     *\n     * @param title     of the element\n     * @param subtitle  of the element\n     * @param iconMain  main icon\n     * @param iconExtra secondary icon\n     */\n    public ContainerUI(@NonNull final String title, @NonNull final String subtitle, final int iconMain,\n                       final int iconExtra) {\n        this.title = title;\n        this.subtitle = subtitle;\n        this.iconMain = iconMain;\n        this.iconExtra = iconExtra;\n\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(@NonNull final String title) {\n        this.title = title;\n    }\n\n    public String getSubtitle() {\n        return subtitle;\n    }\n\n    public void setSubtitle(@NonNull final String subtitle) {\n        this.subtitle = subtitle;\n    }\n\n    public int getIconMain() {\n        return iconMain;\n    }\n\n    public void setIconMain(final int iconMain) {\n        this.iconMain = iconMain;\n    }\n\n    public int getIconExtra() {\n        return iconExtra;\n    }\n\n    public void setIconExtra(final int iconExtra) {\n        this.iconExtra = iconExtra;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentAbout.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentAboutHelper;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport de.psdev.licensesdialog.LicensesDialog;\nimport de.psdev.licensesdialog.model.Notices;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentAbout extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentAbout.class.getSimpleName();\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentAboutHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentAbout() {\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    public static FragmentAbout newInstance(@Nullable final Bundle args) {\n        return new FragmentAbout();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        helper = new FragmentAboutHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_about));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        getParentActivity().vibrate();\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n\n            case 0:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_WEB_URL);\n                break;\n            case 1:\n                getParentActivity().toast(getString(R.string.menu_release_notes), Toast.LENGTH_SHORT);\n                break;\n            case 2:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_PRIVACY_URL);\n                break;\n            case 3:\n                prepareLicenses();\n                break;\n            case 4:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_GITHUB_URL);\n                break;\n            case 5:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.SAIY_GITHUB_URL);\n                break;\n            case 6:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.STUDIO_AHREMARK_WEB_URL);\n                break;\n            case 7:\n                ExecuteIntent.webSearch(getApplicationContext(), Constants.SCHLAPA_WEB_URL);\n                break;\n            case 8:\n                getParentActivity().toast(getString(R.string.menu_special_thanks), Toast.LENGTH_SHORT);\n                break;\n            case 9:\n                if (!ExecuteIntent.sendEmail(getApplicationContext(), new String[]{Constants.SAIY_ENQUIRIES_EMAIL},\n                        getString(R.string.menu_commercial_enquiry), null)) {\n                    getParentActivity().toast(getString(R.string.error_no_application),\n                            Toast.LENGTH_LONG);\n                }\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    /**\n     * Prepare the license information to show to the user\n     */\n    private void prepareLicenses() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"prepareLicenses\");\n        }\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final Notices notices = helper.getLicenses();\n                FragmentAbout.this.getParentActivity().runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        new LicensesDialog.Builder(FragmentAbout.this.getParentActivity())\n                                .setNotices(notices).setIncludeOwnLicense(true)\n                                .setShowFullLicenseText(false)\n                                .setCloseText(R.string.close).build().show();\n                    }\n                });\n            }\n        });\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentAdvancedSettings.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentAdvancedSettingsHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentAdvancedSettings extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentAdvancedSettings.class.getSimpleName();\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentAdvancedSettingsHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentAdvancedSettings() {\n    }\n\n    public static FragmentAdvancedSettings newInstance(@Nullable final Bundle args) {\n        return new FragmentAdvancedSettings();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        helper = new FragmentAdvancedSettingsHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_advanced));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n\n            case 0:\n                getParentActivity().vibrate();\n                SPH.setToastUnknown(getApplicationContext(), !SPH.getToastUnknown(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getToastUnknown(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 1:\n                getParentActivity().vibrate();\n                SPH.setVibrateCondition(getApplicationContext(), !SPH.getVibrateCondition(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getVibrateCondition(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 2:\n                getParentActivity().vibrate();\n                SPH.setUseOffline(getApplicationContext(), !SPH.getUseOffline(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getUseOffline(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 3:\n                //noinspection NewApi\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP\n                        && !PermissionHelper.checkUsageStatsPermission(getApplicationContext())) {\n                    getParentActivity().speak(R.string.app_speech_usage_stats, LocalRequest.ACTION_SPEAK_ONLY);\n                    ExecuteIntent.settingsIntent(getApplicationContext(), IntentConstants.SETTINGS_USAGE_STATS);\n                } else {\n                    helper.showHotwordSelector();\n                }\n                break;\n            case 4:\n                helper.showGenderSelector();\n                break;\n            case 5:\n                getParentActivity().vibrate();\n                SPH.setMotionEnabled(getApplicationContext(), !SPH.getMotionEnabled(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getMotionEnabled(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 6:\n                helper.showPauseDetectionSlider();\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentBugs.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.EditText;\nimport android.widget.ImageButton;\nimport android.widget.TextView;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.recognition.TestRecognitionAction;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentBugsHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentBugs extends Fragment implements View.OnClickListener, View.OnLongClickListener,\n        TextView.OnEditorActionListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentBugs.class.getSimpleName();\n\n    private static final int IME_GO = 99;\n\n    private EditText editText;\n    private ImageButton imageButton;\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentBugsHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentBugs() {\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    public static FragmentBugs newInstance(@Nullable final Bundle args) {\n        return new FragmentBugs();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        helper = new FragmentBugsHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    /**\n     * The RecyclerView in all fragments is initialised with an empty adapter. We start any heavy lifting\n     * here to ensure that the transition between any fragments does not cause any visual lag (stuttering).\n     * <p>\n     * As our fragments don't contain any dynamic content, we only need to check if the adapter content is\n     * empty, to know this is the first time we've received this callback. We synchronise the process, just\n     * to avoid any weird and wonderful situations that never happen....\n     */\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_troubleshooting));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.fragment_bugs_layout, container, false);\n        editText = helper.getEditText(rootView);\n        imageButton = helper.getImageButton(rootView);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        switch (view.getId()) {\n\n            case R.id.ibRun:\n                testCommand();\n                break;\n            default:\n\n                switch ((int) view.getTag()) {\n\n                    case 0:\n                        if (!ExecuteIntent.settingsIntent(getApplicationContext(),\n                                IntentConstants.SETTINGS_VOICE_SEARCH)) {\n                            if (DEBUG) {\n                                MyLog.w(CLS_NAME, \"onClick: SETTINGS_VOICE_SEARCH\");\n                            }\n                            ExecuteIntent.settingsIntent(getApplicationContext(),\n                                    IntentConstants.SETTINGS_INPUT_METHOD);\n                        }\n                        break;\n                    case 1:\n                        ExecuteIntent.settingsIntent(getApplicationContext(),\n                                IntentConstants.SETTINGS_TEXT_TO_SPEECH);\n                        break;\n                    case 2:\n                        getParentActivity().doFragmentAddTransaction(FragmentSettings.newInstance(null),\n                                String.valueOf(ActivityHome.INDEX_FRAGMENT_SETTINGS), ActivityHome.ANIMATION_FADE,\n                                ActivityHome.INDEX_FRAGMENT_BUGS);\n                        break;\n                    default:\n                        break;\n                }\n\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        switch ((int) view.getTag()) {\n\n            case 0:\n                return false;\n            case 1:\n                ExecuteIntent.settingsIntent(getApplicationContext(), IntentConstants.SETTINGS_VOLUME);\n                break;\n            case 2:\n                getParentActivity().doFragmentAddTransaction(FragmentSuperUser.newInstance(null),\n                        String.valueOf(ActivityHome.INDEX_FRAGMENT_SUPER_USER), ActivityHome.ANIMATION_FADE,\n                        ActivityHome.INDEX_FRAGMENT_BUGS);\n                break;\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    /**\n     * Called when an action is being performed.\n     *\n     * @param v        The view that was clicked.\n     * @param actionId Identifier of the action.  This will be either the\n     *                 identifier you supplied, or {@link EditorInfo#IME_NULL\n     *                 EditorInfo.IME_NULL} if being called due to the enter key\n     *                 being pressed.\n     * @param event    If triggered by an enter key, this is the event;\n     *                 otherwise, this is null.\n     * @return Return true if you have consumed the action, else false.\n     */\n    @Override\n    public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onEditorAction: \" + actionId);\n        }\n\n        switch (actionId) {\n\n            case EditorInfo.IME_ACTION_GO:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onEditorAction: IME_ACTION_GO\");\n                }\n                testCommand();\n                return true;\n            case IME_GO:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onEditorAction: IME_GO\");\n                }\n                testCommand();\n                return true;\n            default:\n                break;\n\n        }\n\n        return false;\n    }\n\n    /**\n     * Run a test command input by the user\n     */\n    private void testCommand() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"testCommand\");\n        }\n\n        if (editText.getText() != null) {\n\n            final String commandText = editText.getText().toString();\n\n            if (UtilsString.notNaked(commandText)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"testCommand: executing: \" + commandText);\n                }\n\n                hideIME();\n\n                new TestRecognitionAction(getApplicationContext(), commandText.trim());\n\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"testCommand: text naked\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"testCommand: getText null\");\n            }\n        }\n    }\n\n    /**\n     * Hide the IME once the input is complete\n     */\n    private void hideIME() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"hideIME\");\n        }\n        ((InputMethodManager)\n                getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE))\n                .hideSoftInputFromWindow(editText.getApplicationWindowToken(),\n                        InputMethodManager.HIDE_NOT_ALWAYS);\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentCustomisation.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentCustomisationHelper;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentCustomisation extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentCustomisation.class.getSimpleName();\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentCustomisationHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentCustomisation() {\n    }\n\n    public static FragmentCustomisation newInstance(@Nullable final Bundle args) {\n        return new FragmentCustomisation();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        helper = new FragmentCustomisationHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_customisation));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n\n            case 0:\n                helper.showCustomIntroDialog();\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentHome.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\nimport java.util.Locale;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.language.nlu.NLULanguageAPIAI;\nimport ai.saiy.android.configuration.APIAIConfiguration;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.nlu.apiai.RemoteAPIAI;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentHomeHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentHome extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentHome.class.getSimpleName();\n\n    /**\n     * This seemingly, arbitrary delay is to allow the animation of the drawer closing to complete\n     * prior to doing any heavy UI work, which can cause the drawer to stutter. The consequence of this,\n     * is the appearance of a blank fragment, albeit momentarily, negated somewhat by the fade-in animation.\n     * Regardless, I would consider it more preferable than visual lag?\n     */\n    public static final long DRAWER_CLOSE_DELAY = 200L;\n\n    public static final int CHECKED = R.drawable.ic_toggle_switch_on;\n    public static final int UNCHECKED = R.drawable.ic_toggle_switch_off;\n    public static final int CHEVRON = R.drawable.chevron;\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentHomeHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentHome() {\n    }\n\n    public static FragmentHome newInstance(@Nullable final Bundle args) {\n        return new FragmentHome();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        helper = new FragmentHomeHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    /**\n     * The RecyclerView in all fragments is initialised with an empty adapter. We start any heavy lifting\n     * here to ensure that the transition between any fragments does not cause any visual lag (stuttering).\n     * <p>\n     * As our fragments don't contain any dynamic content, we only need to check if the adapter content is\n     * empty, to know this is the first time we've received this callback. We synchronise the process, just\n     * to avoid any weird and wonderful situations that never happen....\n     */\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_home));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n\n        switch ((int) view.getTag()) {\n\n            case 0:\n                getParentActivity().vibrate();\n                getParentActivity().toast(getString(R.string.menu_voice_tutorial), Toast.LENGTH_SHORT);\n\n                AsyncTask.execute(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        final Locale vrLocale = SPH.getVRLocale(getApplicationContext());\n\n                        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(vrLocale);\n                        final Locale ttsLocale = SPH.getTTSLocale(getApplicationContext());\n\n                        final float[] floatsArray = new float[1];\n                        floatsArray[0] = 1F;\n\n                        final ArrayList<String> resultsArray = new ArrayList<>();\n                        resultsArray.add(\"What is my battery temperature\");\n\n\n                    }\n                });\n\n                break;\n            case 1:\n                getParentActivity().vibrate();\n                getParentActivity().toast(getString(R.string.menu_user_guide), Toast.LENGTH_SHORT);\n                break;\n            case 2:\n                getParentActivity().vibrate();\n                getParentActivity().toast(getString(R.string.menu_development), Toast.LENGTH_SHORT);\n                break;\n            case 3:\n                getParentActivity().doFragmentReplaceTransaction(FragmentSettings.newInstance(null),\n                        String.valueOf(ActivityHome.INDEX_FRAGMENT_SETTINGS), ActivityHome.ANIMATION_FADE);\n                break;\n            case 4:\n                getParentActivity().doFragmentReplaceTransaction(FragmentCustomisation.newInstance(null),\n                        String.valueOf(ActivityHome.INDEX_FRAGMENT_CUSTOMISATION), ActivityHome.ANIMATION_FADE);\n                break;\n            case 5:\n                getParentActivity().doFragmentReplaceTransaction(FragmentAdvancedSettings.newInstance(null),\n                        String.valueOf(ActivityHome.INDEX_FRAGMENT_ADVANCED_SETTINGS), ActivityHome.ANIMATION_FADE);\n                break;\n            case 6:\n                getParentActivity().doFragmentReplaceTransaction(FragmentBugs.newInstance(null),\n                        String.valueOf(ActivityHome.INDEX_FRAGMENT_BUGS), ActivityHome.ANIMATION_FADE);\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentSettings.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentSettingsHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentSettings extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentSettings.class.getSimpleName();\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentSettingsHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentSettings() {\n    }\n\n    public static FragmentSettings newInstance(@Nullable final Bundle args) {\n        return new FragmentSettings();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n        helper = new FragmentSettingsHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_settings));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n\n            case 0:\n                getParentActivity().showLanguageSelector();\n                break;\n            case 1:\n                helper.showUnknownCommandSelector();\n                break;\n            case 2:\n                helper.showVolumeSettingsSlider();\n                break;\n            case 3:\n                getParentActivity().vibrate();\n                SPH.setNetworkSynthesis(getApplicationContext(), !SPH.getNetworkSynthesis(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getNetworkSynthesis(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 4:\n                helper.showTemperatureUnitsSelector();\n                break;\n            case 5:\n                getParentActivity().toast(getString(R.string.menu_default_apps), Toast.LENGTH_SHORT);\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/FragmentSuperUser.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareHelper;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.helper.FragmentSuperuserHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Created by benrandall76@gmail.com on 18/07/2016.\n */\n\npublic class FragmentSuperUser extends Fragment implements View.OnClickListener, View.OnLongClickListener {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentSuperUser.class.getSimpleName();\n\n    private RecyclerView mRecyclerView;\n    private RecyclerView.Adapter mAdapter;\n    private ArrayList<ContainerUI> mObjects;\n    private FragmentSuperuserHelper helper;\n\n    private static final Object lock = new Object();\n\n    private Context mContext;\n\n    public FragmentSuperUser() {\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    public static FragmentSuperUser newInstance(@Nullable final Bundle args) {\n        return new FragmentSuperUser();\n    }\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreate\");\n        }\n\n        helper = new FragmentSuperuserHelper(this);\n    }\n\n    @Override\n    public void onAttach(final Context context) {\n        super.onAttach(context);\n        this.mContext = context.getApplicationContext();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void onAttach(final Activity activity) {\n        super.onAttach(activity);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            this.mContext = activity.getApplicationContext();\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStart\");\n        }\n\n        synchronized (lock) {\n            if (mObjects.isEmpty()) {\n                getParentActivity().setTitle(getString(R.string.title_superuser));\n                helper.finaliseUI();\n            }\n        }\n    }\n\n    @Override\n    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onCreateView\");\n        }\n\n        final View rootView = inflater.inflate(R.layout.layout_common_fragment_parent, container, false);\n        mRecyclerView = helper.getRecyclerView(rootView);\n        mObjects = new ArrayList<>();\n        mAdapter = helper.getAdapter(mObjects);\n        mRecyclerView.setAdapter(mAdapter);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick: \" + view.getTag());\n        }\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n\n            case 0:\n                getParentActivity().toast(getString(R.string.menu_root), Toast.LENGTH_SHORT);\n                break;\n            case 1:\n                getParentActivity().vibrate();\n                SPH.setStartAtBoot(getApplicationContext(), !SPH.getStartAtBoot(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getStartAtBoot(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 2:\n                helper.showAccountPicker();\n                break;\n            case 3:\n                getParentActivity().vibrate();\n                SPH.setPingCheck(getApplicationContext(), !SPH.getPingCheck(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getPingCheck(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 4:\n                AsyncTask.execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        if (!helper.showBlackListSelector()) {\n                            FragmentSuperUser.this.getParentActivity().toast(FragmentSuperUser.this.getString(R.string.blacklist_no_apps),\n                                    Toast.LENGTH_LONG);\n                        }\n                    }\n                });\n                break;\n            case 5:\n                helper.showMemorySlider();\n                break;\n            case 6:\n                getParentActivity().vibrate();\n\n                final boolean enabled = SPH.getInterceptGoogle(getApplicationContext());\n\n                SPH.setInterceptGoogle(getApplicationContext(), !enabled);\n                mObjects.get(position).setIconExtra(!enabled ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n\n                AsyncTask.execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        if (!enabled && !SelfAwareHelper.saiyAccessibilityRunning(FragmentSuperUser.this.getApplicationContext())) {\n                            ExecuteIntent.settingsIntent(FragmentSuperUser.this.getApplicationContext(), IntentConstants.SETTINGS_ACCESSIBILITY);\n                            FragmentSuperUser.this.getParentActivity().speak(R.string.accessibility_enable, LocalRequest.ACTION_SPEAK_ONLY);\n                        } else if (!enabled) {\n                            SelfAwareHelper.startAccessibilityService(FragmentSuperUser.this.getApplicationContext());\n                        }\n                    }\n                });\n                break;\n            case 7:\n                getParentActivity().toast(getString(R.string.menu_algorithms), Toast.LENGTH_SHORT);\n                break;\n            case 8:\n                getParentActivity().vibrate();\n                SPH.setRecogniserBusyFix(getApplicationContext(), !SPH.getRecogniserBusyFix(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getRecogniserBusyFix(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            case 9:\n                getParentActivity().vibrate();\n                SPH.setOkayGoogleFix(getApplicationContext(), !SPH.getOkayGoogleFix(getApplicationContext()));\n                mObjects.get(position).setIconExtra(SPH.getOkayGoogleFix(getApplicationContext()) ?\n                        FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n                mAdapter.notifyItemChanged(position);\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public boolean onLongClick(final View view) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onLongClick: \" + view.getTag());\n        }\n\n        getParentActivity().toast(\"long press!\", Toast.LENGTH_SHORT);\n\n        final int position = (int) view.getTag();\n\n        switch (position) {\n            default:\n                break;\n        }\n\n        return true;\n    }\n\n    public boolean isActive() {\n        return getActivity() != null && getParentActivity().isActive() && isAdded() && !isRemoving();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this fragment will\n     * never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return (ActivityHome) getActivity();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    public Context getApplicationContext() {\n        return this.mContext;\n    }\n\n    /**\n     * Get the current adapter\n     *\n     * @return the current adapter\n     */\n    public RecyclerView.Adapter getAdapter() {\n        return mAdapter;\n    }\n\n    /**\n     * Get the current objects in the adapter\n     *\n     * @return the current objects\n     */\n    public ArrayList<ContainerUI> getObjects() {\n        return mObjects;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onPause\");\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onResume\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n\n        helper.enrollmentCancelled.set(true);\n        helper.cancelTimer();\n        getParentActivity().showProgress(false);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentAboutHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.BuildConfig;\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentAbout;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport de.psdev.licensesdialog.licenses.ApacheSoftwareLicense20;\nimport de.psdev.licensesdialog.licenses.BSD2ClauseLicense;\nimport de.psdev.licensesdialog.licenses.CreativeCommonsAttributionShareAlike30Unported;\nimport de.psdev.licensesdialog.licenses.MITLicense;\nimport de.psdev.licensesdialog.model.Notice;\nimport de.psdev.licensesdialog.model.Notices;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentAboutHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentAboutHelper.class.getSimpleName();\n\n    private final FragmentAbout parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentAboutHelper(@NonNull final FragmentAbout parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.app_name)\n                + \" V\" + BuildConfig.VERSION_NAME + \"A\");\n        containerUI.setSubtitle(getString(R.string.menu_saiy));\n        containerUI.setIconMain(R.drawable.ic_beta);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_release_notes));\n        containerUI.setSubtitle(getString(R.string.menu_tap_view));\n        containerUI.setIconMain(R.drawable.ic_note_text);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_privacy_policy));\n        containerUI.setSubtitle(getString(R.string.menu_tap_view));\n        containerUI.setIconMain(R.drawable.ic_blinds);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_legal));\n        containerUI.setSubtitle(getString(R.string.menu_tap_view));\n        containerUI.setIconMain(R.drawable.ic_gavel);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_source_code));\n        containerUI.setSubtitle(getString(R.string.menu_tap_view));\n        containerUI.setIconMain(R.drawable.ic_github);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_developer_api));\n        containerUI.setSubtitle(getString(R.string.menu_tap_view));\n        containerUI.setIconMain(R.drawable.ic_github);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_hannes_ahremark) + \" - \"\n                + getString(R.string.menu_app_icon));\n        containerUI.setSubtitle(getString(R.string.submenu_studio_ahremark));\n        containerUI.setIconMain(R.drawable.ic_palette);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_daniel_schlapa) + \" - \"\n                + getString(R.string.menu_testing));\n        containerUI.setSubtitle(getString(R.string.submenu_daniel_schlapa));\n        containerUI.setIconMain(R.drawable.ic_emoticon_cool);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_special_thanks));\n        containerUI.setSubtitle(getString(R.string.menu_tap_legends));\n        containerUI.setIconMain(R.drawable.ic_xda);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_commercial_enquiries));\n        containerUI.setSubtitle(getString(R.string.menu_tap_send));\n        containerUI.setIconMain(R.drawable.ic_email);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView) parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentAboutHelper.this.getUIComponents();\n\n                try {\n                    Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (FragmentAboutHelper.this.getParent().isActive()) {\n\n                    FragmentAboutHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                            FragmentAboutHelper.this.getParent().getObjects().addAll(tempArray);\n                            FragmentAboutHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentAboutHelper.this.getParent().getObjects().size());\n                        }\n                    });\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Get the license information we need to display\n     *\n     * @return the {@link Notices} object containing the required {@link Notice} elements\n     */\n    public Notices getLicenses() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getLicenses\");\n        }\n\n        final Notices notices = new Notices();\n\n        notices.addNotice(new Notice(getString(R.string.google_play_services),\n                Constants.LICENSE_URL_GOOGLE_PLAY_SERVICES,\n                getString(R.string.license_android_open_source),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.gson),\n                Constants.LICENSE_URL_GSON,\n                getString(R.string.license_google_inc),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.volley),\n                Constants.LICENSE_URL_VOLLEY,\n                getString(R.string.license_volley),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.two_forty_four),\n                Constants.LICENSE_URL_TWO_FORTY_FOUR,\n                getString(R.string.license_two_forty_four),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.speechutils),\n                Constants.LICENSE_URL_KAAREL_KALJURAND,\n                getString(R.string.license_kaarel_kaljurand),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.microsoft_translator),\n                Constants.LICENSE_URL_MICROSOFT_TRANSLATOR,\n                getString(R.string.license_microsoft_translator),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.apache_commons),\n                Constants.LICENSE_URL_APACHE_COMMONS,\n                getString(R.string.license_apache_commons),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.simmetrics),\n                Constants.LICENSE_URL_SIMMETRICS,\n                getString(R.string.license_simmetrics),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.nuance_speechkit),\n                Constants.LICENSE_URL_NUANCE_SPEECHKIT,\n                getString(R.string.license_nuance_speechkit),\n                new BSD2ClauseLicense()));\n\n        notices.addNotice(new Notice(getString(R.string.guava),\n                Constants.LICENSE_URL_GUAVA,\n                getString(R.string.license_guava),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.microsoft_cognitive),\n                Constants.LICENSE_URL_MICROSOFT_COGNITIVE,\n                getString(R.string.license_microsoft_cognitive),\n                new MITLicense()));\n\n        notices.addNotice(new Notice(getString(R.string.api_ai),\n                Constants.LICENSE_URL_API_AI,\n                getString(R.string.license_api_ai),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.simple_xml),\n                Constants.LICENSE_URL_SIMPLE_XML,\n                getString(R.string.license_simple_xml),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.material_icons),\n                Constants.LICENSE_MATERIAL_ICONS,\n                getString(R.string.license_material_icons),\n                new ApacheSoftwareLicense20()));\n\n        notices.addNotice(new Notice(getString(R.string.material_dialogs),\n                Constants.LICENSE_MATERIAL_DIALOGS,\n                getString(R.string.license_material_dialogs),\n                new MITLicense()));\n\n        notices.addNotice(new Notice(getString(R.string.pocketsphinx),\n                Constants.LICENSE_POCKETSPHINX,\n                getString(R.string.license_pocketsphinx),\n                new BSD2ClauseLicense()));\n\n        notices.addNotice(new Notice(getString(R.string.sound_bible),\n                Constants.LICENSE_SOUND_BIBLE,\n                getString(R.string.license_sound_bible),\n                new CreativeCommonsAttributionShareAlike30Unported()));\n\n        return notices;\n    }\n\n    private String getString(final int id) {\n        return getApplicationContext().getString(id);\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentAbout getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentAdvancedSettingsHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.tts.attributes.Gender;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentAdvancedSettings;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentAdvancedSettingsHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentAdvancedSettingsHelper.class.getSimpleName();\n\n    private final FragmentAdvancedSettings parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentAdvancedSettingsHelper(@NonNull final FragmentAdvancedSettings parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_toast));\n        containerUI.setSubtitle(getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_comment_alert_outline);\n        containerUI.setIconExtra(SPH.getToastUnknown(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_haptic));\n        containerUI.setSubtitle(getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_vibration);\n        containerUI.setIconExtra(SPH.getVibrateCondition(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_offline));\n        containerUI.setSubtitle(getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_cloud_outline_off);\n        containerUI.setIconExtra(SPH.getUseOffline(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_hotword_detection));\n        containerUI.setSubtitle(getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_blur);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_tts_gender));\n        containerUI.setSubtitle(getString(R.string.menu_tap_options));\n        containerUI.setIconMain(R.drawable.ic_gender_transgender);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_motion));\n        containerUI.setSubtitle(getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_bike);\n        containerUI.setIconExtra(SPH.getMotionEnabled(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getString(R.string.menu_pause));\n        containerUI.setSubtitle(getString(R.string.menu_tap_set));\n        containerUI.setIconMain(R.drawable.ic_pause_octagon_outline);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView) parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentAdvancedSettingsHelper.this.getUIComponents();\n\n                if (FragmentAdvancedSettingsHelper.this.getParentActivity().getDrawer().isDrawerOpen(GravityCompat.START)) {\n\n                    try {\n                        Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n\n                if (FragmentAdvancedSettingsHelper.this.getParent().isActive()) {\n\n                    FragmentAdvancedSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                            FragmentAdvancedSettingsHelper.this.getParent().getObjects().addAll(tempArray);\n                            FragmentAdvancedSettingsHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentAdvancedSettingsHelper.this.getParent().getObjects().size());\n                        }\n                    });\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Show the gender selector\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showGenderSelector() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final String[] gender = FragmentAdvancedSettingsHelper.this.getParent().getResources().getStringArray(R.array.array_gender);\n\n                for (int i = 0; i < gender.length; i++) {\n                    gender[i] = StringUtils.capitalize(gender[i]);\n                }\n\n                FragmentAdvancedSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        final MaterialDialog materialDialog = new MaterialDialog.Builder(FragmentAdvancedSettingsHelper.this.getParentActivity())\n                                .autoDismiss(false)\n                                .alwaysCallSingleChoiceCallback()\n                                .title(R.string.menu_tts_gender)\n                                .content(R.string.tts_gender_text)\n                                .items((CharSequence[]) gender)\n                                .positiveText(R.string.menu_select)\n                                .negativeText(android.R.string.cancel)\n                                .iconRes(R.drawable.ic_gender_transgender)\n                                .backgroundColorRes(R.color.colorTint)\n\n                                .itemsCallbackSingleChoice(SPH.getDefaultTTSGender(FragmentAdvancedSettingsHelper.this.getApplicationContext()).ordinal(),\n                                        new MaterialDialog.ListCallbackSingleChoice() {\n                                            @Override\n                                            public boolean onSelection(final MaterialDialog dialog, final View view, final int which, final CharSequence text) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showGenderSelector: onSelection: \" + which + \": \" + text);\n                                                }\n                                                return true;\n                                            }\n                                        })\n\n                                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                                        switch (dialog.getSelectedIndex()) {\n\n                                            case 0:\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showGenderSelector: onPositive: MALE\");\n                                                }\n                                                SPH.setDefaultTTSGender(FragmentAdvancedSettingsHelper.this.getApplicationContext(), Gender.MALE);\n                                                SPH.setDefaultTTSVoice(FragmentAdvancedSettingsHelper.this.getApplicationContext(), null);\n                                                break;\n                                            case 1:\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showGenderSelector: onPositive: FEMALE\");\n                                                }\n                                                SPH.setDefaultTTSGender(FragmentAdvancedSettingsHelper.this.getApplicationContext(), Gender.FEMALE);\n                                                SPH.setDefaultTTSVoice(FragmentAdvancedSettingsHelper.this.getApplicationContext(), null);\n                                                break;\n\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showGenderSelector: onNegative\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .cancelListener(new DialogInterface.OnCancelListener() {\n                                    @Override\n                                    public void onCancel(final DialogInterface dialog) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showGenderSelector: onCancel\");\n                                        }\n\n                                        dialog.dismiss();\n                                    }\n                                }).build();\n\n                        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                        materialDialog.show();\n                    }\n                });\n            }\n        });\n    }\n\n    /**\n     * Show the Hotword selector\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showHotwordSelector() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final String[] hotwordActions = FragmentAdvancedSettingsHelper.this.getParent().getResources().getStringArray(R.array.array_hotword);\n\n                final ArrayList<Integer> selectedList = new ArrayList<>();\n\n                if (SPH.getHotwordBoot(FragmentAdvancedSettingsHelper.this.getApplicationContext())) {\n                    selectedList.add(0);\n                }\n\n                if (SPH.getHotwordDriving(getApplicationContext())) {\n                    selectedList.add(1);\n                }\n\n                if (SPH.getHotwordWakelock(getApplicationContext())) {\n                    selectedList.add(2);\n                }\n\n                if (SPH.getHotwordSecure(getApplicationContext())) {\n                    selectedList.add(3);\n                }\n\n                FragmentAdvancedSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        final MaterialDialog materialDialog = new MaterialDialog.Builder(FragmentAdvancedSettingsHelper.this.getParentActivity())\n                                .autoDismiss(false)\n                                .title(R.string.menu_hotword_detection)\n                                .content(R.string.hotword_intro_text)\n                                .iconRes(R.drawable.ic_blur)\n                                .positiveText(R.string.save)\n                                .neutralText(R.string.clear)\n                                .negativeText(android.R.string.cancel)\n                                .items((CharSequence[]) hotwordActions)\n\n                                .itemsCallbackMultiChoice(selectedList.toArray(new Integer[0]), new MaterialDialog.ListCallbackMultiChoice() {\n                                    @Override\n                                    public boolean onSelection(final MaterialDialog dialog, final Integer[] which, final CharSequence[] text) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showHotwordSelector: onSelection: \" + which.length);\n                                        }\n                                        return true;\n                                    }\n                                })\n\n                                .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        dialog.clearSelectedIndices();\n                                    }\n                                })\n\n                                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showHotwordSelector: onPositive\");\n                                        }\n\n                                        final Integer[] selected = dialog.getSelectedIndices();\n\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showHotwordSelector: onPositive: length: \" + selected.length);\n                                            for (final Integer aSelected : selected) {\n                                                MyLog.i(CLS_NAME, \"showHotwordSelector: onPositive: \" + aSelected);\n                                            }\n                                        }\n\n                                        SPH.setHotwordBoot(getApplicationContext(),\n                                                ArrayUtils.contains(selected, 0));\n\n                                        SPH.setHotwordDriving(getApplicationContext(),\n                                                ArrayUtils.contains(selected, 1));\n\n                                        if (SPH.getHotwordDriving(getApplicationContext())) {\n                                            SPH.setMotionEnabled(getApplicationContext(), true);\n                                        }\n\n                                        SPH.setHotwordWakelock(getApplicationContext(),\n                                                ArrayUtils.contains(selected, 2));\n\n                                        SPH.setHotwordSecure(getApplicationContext(),\n                                                ArrayUtils.contains(selected, 3));\n\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showHotwordSelector: onNegative\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .cancelListener(new DialogInterface.OnCancelListener() {\n                                    @Override\n                                    public void onCancel(final DialogInterface dialog) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showHotwordSelector: onCancel\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                }).build();\n\n                        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                        materialDialog.show();\n\n                    }\n                });\n            }\n        });\n    }\n\n\n    /**\n     * Show the pause detection slider\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showPauseDetectionSlider() {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                .customView(R.layout.pause_detection_dialog_layout, false)\n                .autoDismiss(false)\n                .title(R.string.menu_pause)\n                .iconRes(R.drawable.ic_pause_octagon_outline)\n                .positiveText(R.string.save)\n                .neutralText(R.string.text_default)\n                .negativeText(android.R.string.cancel)\n\n                .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        ((SeekBar) dialog.getCustomView().findViewById(R.id.pauseSeekBar))\n                                .setProgress((int) (RecognitionNative.PAUSE_TIMEOUT / 1000));\n                    }\n                })\n\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showPauseDetectionSlider: onPositive\");\n                        }\n\n                        SPH.setPauseTimeout(FragmentAdvancedSettingsHelper.this.getApplicationContext(),\n                                (long) ((SeekBar) dialog.getCustomView().findViewById(R.id.pauseSeekBar))\n                                        .getProgress() * 1000);\n                        dialog.dismiss();\n                    }\n                })\n\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showPauseDetectionSlider: onNegative\");\n                        }\n                        dialog.dismiss();\n                    }\n                })\n\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showPauseDetectionSlider: onCancel\");\n                        }\n                        dialog.dismiss();\n                    }\n                }).build();\n\n        final int currentTimeout = (int) (SPH.getPauseTimeout(getApplicationContext()) / 1000);\n        final TextView seekText = (TextView) materialDialog.getCustomView().findViewById(R.id.pauseSeekBarText);\n\n        switch (currentTimeout) {\n            case 0:\n                seekText.setText(getString(R.string.pause_detection_text)\n                        + \" \" + getString(R.string.provider_default));\n                break;\n            case 1:\n                seekText.setText(getString(R.string.pause_detection_text)\n                        + \" \" + currentTimeout + \" \" + getString(R.string.second));\n                break;\n            default:\n                seekText.setText(getString(R.string.pause_detection_text)\n                        + \" \" + currentTimeout + \" \" + getString(R.string.seconds));\n                break;\n        }\n\n        final SeekBar seekbar = (SeekBar) materialDialog.getCustomView().findViewById(R.id.pauseSeekBar);\n        seekbar.setProgress(currentTimeout);\n\n        seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n\n            @Override\n            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {\n\n                switch (progress) {\n                    case 0:\n                        seekText.setText(getString(R.string.pause_detection_text)\n                                + \" \" + getString(R.string.provider_default));\n                        break;\n                    case 1:\n                        seekText.setText(getString(R.string.pause_detection_text)\n                                + \" \" + progress + \" \" + getString(R.string.second));\n                        break;\n                    default:\n                        seekText.setText(getString(R.string.pause_detection_text)\n                                + \" \" + progress + \" \" + getString(R.string.seconds));\n                        break;\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(final SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(final SeekBar seekBar) {\n            }\n        });\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    private String getString(final int id) {\n        return getApplicationContext().getString(id);\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentAdvancedSettings getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentBugsHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport android.view.inputmethod.EditorInfo;\nimport android.widget.EditText;\nimport android.widget.ImageButton;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.UIBugsAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentBugs;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentBugsHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentHomeHelper.class.getSimpleName();\n\n    private final FragmentBugs parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentBugsHelper(@NonNull final FragmentBugs parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.bug_title_offline_recognition));\n        containerUI.setSubtitle(getParent().getString(R.string.bug_content_offline_recognition));\n        containerUI.setIconMain(R.drawable.ic_bug);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.bug_title_no_speech));\n        containerUI.setSubtitle(getParent().getString(R.string.bug_content_no_speech));\n        containerUI.setIconMain(R.drawable.ic_bug);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.bug_title_delay_speech));\n        containerUI.setSubtitle(getParent().getString(R.string.bug_content_delay_speech));\n        containerUI.setIconMain(R.drawable.ic_bug);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView)\n                parent.findViewById(R.id.layout_bugs_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the Edit Text for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link EditText}\n     */\n    public EditText getEditText(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getEditText\");\n        }\n\n        final EditText editText = (EditText) parent.findViewById(R.id.etCommand);\n        editText.setOnEditorActionListener(getParent());\n        editText.setImeActionLabel(getParent().getString(R.string.menu_run), EditorInfo.IME_ACTION_GO);\n        return editText;\n    }\n\n    /**\n     * Get the Image Button for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link ImageButton}\n     */\n    public ImageButton getImageButton(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getImageButton\");\n        }\n        final ImageButton imageButton = (ImageButton) parent.findViewById(R.id.ibRun);\n        imageButton.setOnClickListener(getParent());\n        return imageButton;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIBugsAdapter}\n     */\n    public UIBugsAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIBugsAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components. If the drawer is not open in the parent\n     * Activity, we can assume this method is called as a result of the back button being pressed, or\n     * the first initialisation of the application - neither of which require a delay.\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentBugsHelper.this.getUIComponents();\n\n                if (FragmentBugsHelper.this.getParentActivity().getDrawer().isDrawerOpen(GravityCompat.START)) {\n\n                    try {\n                        Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n\n                if (FragmentBugsHelper.this.getParent().isActive()) {\n\n                    FragmentBugsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                        FragmentBugsHelper.this.getParent().getObjects().addAll(tempArray);\n                        FragmentBugsHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentBugsHelper.this.getParent().getObjects().size());\n                    }});\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentBugs getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentCustomisationHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.text.InputType;\nimport android.view.View;\n\nimport com.afollestad.materialdialogs.MaterialDialog;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentCustomisation;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentCustomisationHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentCustomisationHelper.class.getSimpleName();\n\n    private final FragmentCustomisation parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentCustomisationHelper(@NonNull final FragmentCustomisation parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n        final int chevronResource = R.drawable.chevron;\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_custom_intro));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_crown);\n        containerUI.setIconExtra(chevronResource);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView) parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n\n        return mRecyclerView;\n    }\n\n    /**\n     * Update the parent fragment with the UI components\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentCustomisationHelper.this.getUIComponents();\n\n                if (FragmentCustomisationHelper.this.getParentActivity().getDrawer().isDrawerOpen(GravityCompat.START)) {\n\n                    try {\n                        Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n\n                if (FragmentCustomisationHelper.this.getParent().isActive()) {\n\n                    FragmentCustomisationHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                        FragmentCustomisationHelper.this.getParent().getObjects().addAll(tempArray);\n                        FragmentCustomisationHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentCustomisationHelper.this.getParent().getObjects().size());\n                    }});\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Show the custom intro dialog\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showCustomIntroDialog() {\n\n        final String currentIntro = SPH.getCustomIntro(getApplicationContext());\n        String hint = null;\n        String content = null;\n\n        if (currentIntro != null) {\n            if (currentIntro.isEmpty()) {\n                hint = getApplicationContext().getString(R.string.silence);\n            } else {\n                content = currentIntro;\n            }\n        } else {\n            hint = getApplicationContext().getString(R.string.custom_intro_hint);\n        }\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                .title(R.string.menu_custom_intro)\n                .positiveText(R.string.save)\n                .iconRes(R.drawable.ic_crown)\n                .backgroundColorRes(R.color.colorTint)\n                .content(R.string.custom_intro_text)\n                .inputType(InputType.TYPE_CLASS_TEXT)\n                .positiveText(R.string.save)\n                .negativeText(android.R.string.cancel)\n                .checkBoxPromptRes(R.string.custom_intro_checkbox_title,\n                        SPH.getCustomIntroRandom(getApplicationContext()), null)\n\n                .input(hint, content, true, new MaterialDialog.InputCallback() {\n                    @Override\n                    public void onInput(@NonNull final MaterialDialog dialog, final CharSequence input) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showCustomIntroDialog: input: \" + input);\n                        }\n\n                        if (input != null) {\n                            if (UtilsString.notNaked(input.toString())) {\n                                SPH.setCustomIntroRandom(FragmentCustomisationHelper.this.getApplicationContext(),\n                                        dialog.isPromptCheckBoxChecked());\n                                SPH.setCustomIntro(FragmentCustomisationHelper.this.getApplicationContext(), input.toString().trim());\n                            } else {\n                                SPH.setCustomIntroRandom(FragmentCustomisationHelper.this.getApplicationContext(), false);\n                                SPH.setCustomIntro(FragmentCustomisationHelper.this.getApplicationContext(), \"\");\n                            }\n                        }\n                    }\n                }).build();\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentCustomisation getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentHomeHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentHomeHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentHomeHelper.class.getSimpleName();\n\n    private final FragmentHome parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentHomeHelper(@NonNull final FragmentHome parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_voice_tutorial));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_begin));\n        containerUI.setIconMain(R.drawable.ic_text_to_speech);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_user_guide));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_topics));\n        containerUI.setIconMain(R.drawable.ic_library);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_development));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_contribute));\n        containerUI.setIconMain(R.drawable.ic_pulse);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.title_settings));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_cog);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.title_customisation));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_tweak));\n        containerUI.setIconMain(R.drawable.ic_fingerprint);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.title_advanced));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_pill);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.title_troubleshooting_bugs));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_help));\n        containerUI.setIconMain(R.drawable.ic_bug);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView)\n                parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components. If the drawer is not open in the parent\n     * Activity, we can assume this method is called as a result of the back button being pressed, or\n     * the first initialisation of the application - neither of which require a delay.\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentHomeHelper.this.getUIComponents();\n\n                if (FragmentHomeHelper.this.getParentActivity().getDrawer().isDrawerOpen(GravityCompat.START)) {\n\n                    try {\n                        Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n\n                if (FragmentHomeHelper.this.getParent().isActive()) {\n\n                    FragmentHomeHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                            FragmentHomeHelper.this.getParent().getObjects().addAll(tempArray);\n                            FragmentHomeHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentHomeHelper.this.getParent().getObjects().size());\n                        }\n                    });\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentHome getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentSettingsHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.AsyncTask;\nimport android.support.annotation.NonNull;\nimport android.support.v4.view.GravityCompat;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.command.battery.BatteryInformation;\nimport ai.saiy.android.command.unknown.Unknown;\nimport ai.saiy.android.thirdparty.tasker.TaskerHelper;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.ui.fragment.FragmentSettings;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Utility class to assist its parent fragment and avoid clutter there\n * <p>\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\npublic class FragmentSettingsHelper {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentSettingsHelper.class.getSimpleName();\n\n    private final FragmentSettings parentFragment;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentSettingsHelper(@NonNull final FragmentSettings parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_language));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_options));\n        containerUI.setIconMain(R.drawable.ic_language);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_unknown_commands));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_options));\n        containerUI.setIconMain(R.drawable.ic_not_equal);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_volume_settings));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_set));\n        containerUI.setIconMain(R.drawable.ic_volume_high);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_synthesised_voice));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_google);\n        containerUI.setIconExtra(SPH.getNetworkSynthesis(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_temperature_units));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_options));\n        containerUI.setIconMain(R.drawable.ic_thermometer);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_default_apps));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_set));\n        containerUI.setIconMain(R.drawable.ic_apps);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView)\n                parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentSettingsHelper.this.getUIComponents();\n\n                if (FragmentSettingsHelper.this.getParentActivity().getDrawer().isDrawerOpen(GravityCompat.START)) {\n\n                    try {\n                        Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                    } catch (final InterruptedException e) {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                            e.printStackTrace();\n                        }\n                    }\n                }\n\n                if (FragmentSettingsHelper.this.getParent().isActive()) {\n\n                    FragmentSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                            FragmentSettingsHelper.this.getParent().getObjects().addAll(tempArray);\n                            FragmentSettingsHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentSettingsHelper.this.getParent().getObjects().size());\n                        }\n                    });\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Show the temperature units selector\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showTemperatureUnitsSelector() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final String[] units = FragmentSettingsHelper.this.getParent().getResources().getStringArray(R.array.array_temperature_units);\n\n                for (int i = 0; i < units.length; i++) {\n                    units[i] = StringUtils.capitalize(units[i]);\n                }\n\n                FragmentSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                        final MaterialDialog materialDialog = new MaterialDialog.Builder(FragmentSettingsHelper.this.getParentActivity())\n                                .autoDismiss(false)\n                                .alwaysCallSingleChoiceCallback()\n                                .title(R.string.menu_temperature_units)\n                                .items((CharSequence[]) units)\n                                .positiveText(R.string.menu_select)\n                                .negativeText(android.R.string.cancel)\n                                .iconRes(R.drawable.ic_thermometer)\n                                .backgroundColorRes(R.color.colorTint)\n\n                                .itemsCallbackSingleChoice(SPH.getDefaultTemperatureUnits(FragmentSettingsHelper.this.getApplicationContext()),\n                                        new MaterialDialog.ListCallbackSingleChoice() {\n                                            @Override\n                                            public boolean onSelection(final MaterialDialog dialog, final View view, final int which, final CharSequence text) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showTemperatureUnitsSelector: onSelection: \" + which + \": \" + text);\n                                                }\n                                                return true;\n                                            }\n                                        })\n\n                                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                                        switch (dialog.getSelectedIndex()) {\n\n                                            case BatteryInformation.CELSIUS:\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showTemperatureUnitsSelector: onPositive: CELSIUS\");\n                                                }\n                                                SPH.setDefaultTemperatureUnits(FragmentSettingsHelper.this.getApplicationContext(),\n                                                        BatteryInformation.CELSIUS);\n                                                break;\n                                            case BatteryInformation.FAHRENHEIT:\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showTemperatureUnitsSelector: onPositive: FAHRENHEIT\");\n                                                }\n                                                SPH.setDefaultTemperatureUnits(FragmentSettingsHelper.this.getApplicationContext(),\n                                                        BatteryInformation.FAHRENHEIT);\n                                                break;\n\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showTemperatureUnitsSelector: onNegative\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .cancelListener(new DialogInterface.OnCancelListener() {\n                                    @Override\n                                    public void onCancel(final DialogInterface dialog) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showTemperatureUnitsSelector: onCancel\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                }).build();\n\n                        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                        materialDialog.show();\n                    }\n                });\n            }\n        });\n    }\n\n    /**\n     * Show the unknown command action selector\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showUnknownCommandSelector() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n\n                final String[] actions = FragmentSettingsHelper.this.getParent().getResources().getStringArray(R.array.array_unknown_action);\n\n                for (int i = 0; i < actions.length; i++) {\n\n                    switch (i) {\n\n                    case Unknown.UNKNOWN_STATE:\n                        break;\n                    case Unknown.UNKNOWN_REPEAT:\n                        break;\n                    case Unknown.UNKNOWN_GOOGLE_SEARCH:\n                        actions[i] = getParent().getString(R.string.menu_send_to) + \" \" + actions[i];\n                        break;\n                    case Unknown.UNKNOWN_WOLFRAM_ALPHA:\n                        actions[i] = getParent().getString(R.string.menu_send_to) + \" \" + actions[i];\n                        break;\n                    case Unknown.UNKNOWN_TASKER:\n                        actions[i] = getParent().getString(R.string.menu_send_to) + \" \" + actions[i];\n                        break;\n                }\n            }\n\n                final ArrayList<Integer> disabledIndicesList = new ArrayList<>();\n\n                if (!Installed.isPackageInstalled(FragmentSettingsHelper.this.getApplicationContext(),\n                        Installed.PACKAGE_WOLFRAM_ALPHA)) {\n                    disabledIndicesList.add(Unknown.UNKNOWN_WOLFRAM_ALPHA);\n                }\n\n                if (!new TaskerHelper().isTaskerInstalled(FragmentSettingsHelper.this.getApplicationContext()).first) {\n                    disabledIndicesList.add(Unknown.UNKNOWN_TASKER);\n                }\n\n                FragmentSettingsHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n\n                final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                        .autoDismiss(false)\n                        .alwaysCallSingleChoiceCallback()\n                        .title(R.string.menu_unknown_commands)\n                        .items((CharSequence[]) actions)\n                        .itemsDisabledIndices(disabledIndicesList.toArray(new Integer[0]))\n                        .content(R.string.content_unknown_command)\n                        .positiveText(R.string.menu_select)\n                        .negativeText(android.R.string.cancel)\n                        .iconRes(R.drawable.ic_not_equal)\n                        .backgroundColorRes(R.color.colorTint)\n\n                                .itemsCallbackSingleChoice(SPH.getCommandUnknownAction(FragmentSettingsHelper.this.getApplicationContext()),\n                                        new MaterialDialog.ListCallbackSingleChoice() {\n                                            @Override\n                                            public boolean onSelection(final MaterialDialog dialog, final View view, final int which, final CharSequence text) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showUnknownCommandSelector: onSelection: \" + which + \": \" + text);\n                                                }\n                                                return true;\n                                            }\n                                        })\n\n                                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showUnknownCommandSelector: onPositive: \" + dialog.getSelectedIndex());\n                                        }\n\n                                        SPH.setCommandUnknownAction(FragmentSettingsHelper.this.getApplicationContext(),\n                                                dialog.getSelectedIndex());\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                                    @Override\n                                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showUnknownCommandSelector: onNegative\");\n                                        }\n                                        dialog.dismiss();\n                                    }\n                                })\n\n                                .cancelListener(new DialogInterface.OnCancelListener() {\n                                    @Override\n                                    public void onCancel(final DialogInterface dialog) {\n                                        if (DEBUG) {\n                                            MyLog.i(CLS_NAME, \"showUnknownCommandSelector: onCancel\");\n                                        }\n\n                                        dialog.dismiss();\n                                    }\n                                }).build();\n\n                        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                        materialDialog.show();\n                    }\n                });\n            }\n        });\n    }\n\n    /**\n     * Show the pause detection slider\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showVolumeSettingsSlider() {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                .customView(R.layout.tts_volume_dialog_layout, false)\n                .autoDismiss(false)\n                .title(R.string.menu_volume_settings)\n                .iconRes(R.drawable.ic_pause_octagon_outline)\n                .positiveText(R.string.save)\n                .neutralText(R.string.text_default)\n                .negativeText(android.R.string.cancel)\n\n                .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        ((SeekBar) dialog.getCustomView().findViewById(R.id.volumeSeekBar))\n                                .setProgress(4);\n                    }\n                })\n\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                    final int volume = ((SeekBar) dialog.getCustomView().findViewById(R.id.volumeSeekBar))\n                            .getProgress() * 10 - 40;\n\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"showVolumeSettingsSlider: onPositive: setting: \" + volume);\n                    }\n\n                    SPH.setTTSVolume(getApplicationContext(), volume);\n\n                        dialog.dismiss();\n                    }\n                })\n\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showVolumeSettingsSlider: onNegative\");\n                        }\n                        dialog.dismiss();\n                    }\n                })\n\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showVolumeSettingsSlider: onCancel\");\n                        }\n                        dialog.dismiss();\n                    }\n                }).build();\n\n        final int userVolume = SPH.getTTSVolume(getApplicationContext());\n        final TextView seekText = (TextView) materialDialog.getCustomView().findViewById(R.id.volumeSeekBarText);\n        final SeekBar seekbar = (SeekBar) materialDialog.getCustomView().findViewById(R.id.volumeSeekBar);\n\n        switch (userVolume) {\n            case -40:\n                seekText.setText(\"40% \" + getParent().getString(R.string.below) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(0);\n                break;\n            case -30:\n                seekText.setText(\"30% \" + getParent().getString(R.string.below) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(1);\n                break;\n            case -20:\n                seekText.setText(\"20% \" + getParent().getString(R.string.below) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(2);\n                break;\n            case -10:\n                seekText.setText(\"10% \" + getParent().getString(R.string.below) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(3);\n                break;\n            case 0:\n                seekText.setText(StringUtils.capitalize(getParent().getString(R.string.adhere_to)) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(4);\n                break;\n            case 10:\n                seekText.setText(\"10% \" + getParent().getString(R.string.above) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(5);\n                break;\n            case 20:\n                seekText.setText(\"20% \" + getParent().getString(R.string.above) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(6);\n                break;\n            case 30:\n                seekText.setText(\"30% \" + getParent().getString(R.string.above) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(7);\n                break;\n            case 40:\n                seekText.setText(\"40% \" + getParent().getString(R.string.above) + \" \"\n                        + getParent().getString(R.string.media_stream));\n                seekbar.setProgress(8);\n        }\n\n        seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n\n            @Override\n            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {\n\n                switch (progress) {\n                    case 0:\n                        seekText.setText(\"40% \" + getParent().getString(R.string.below) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 1:\n                        seekText.setText(\"30% \" + getParent().getString(R.string.below) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 2:\n                        seekText.setText(\"20% \" + getParent().getString(R.string.below) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 3:\n                        seekText.setText(\"10% \" + getParent().getString(R.string.below) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 4:\n                        seekText.setText(StringUtils.capitalize(getParent().getString(R.string.adhere_to))\n                                + \" \" + getParent().getString(R.string.media_stream));\n                        break;\n                    case 5:\n                        seekText.setText(\"10% \" + getParent().getString(R.string.above) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 6:\n                        seekText.setText(\"20% \" + getParent().getString(R.string.above) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 7:\n                        seekText.setText(\"30% \" + getParent().getString(R.string.above) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                    case 8:\n                        seekText.setText(\"40% \" + getParent().getString(R.string.above) + \" \"\n                                + getParent().getString(R.string.media_stream));\n                        break;\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(final SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(final SeekBar seekBar) {\n            }\n        });\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentSettings getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/fragment/helper/FragmentSuperuserHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.fragment.helper;\n\nimport android.accounts.Account;\nimport android.accounts.AccountManager;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.AsyncTask;\nimport android.speech.tts.TextToSpeech;\nimport android.support.annotation.NonNull;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.Pair;\nimport android.view.View;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.api.helper.BlackListHelper;\nimport ai.saiy.android.applications.Install;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.intent.ExecuteIntent;\nimport ai.saiy.android.intent.IntentConstants;\nimport ai.saiy.android.localisation.SupportedLanguage;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.personality.PersonalityHelper;\nimport ai.saiy.android.personality.PersonalityResponse;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.service.helper.SelfAwareConditions;\nimport ai.saiy.android.ui.activity.ActivityHome;\nimport ai.saiy.android.ui.components.DividerItemDecoration;\nimport ai.saiy.android.ui.components.UIMainAdapter;\nimport ai.saiy.android.ui.containers.ContainerUI;\nimport ai.saiy.android.ui.fragment.FragmentHome;\nimport ai.saiy.android.ui.fragment.FragmentSuperUser;\nimport ai.saiy.android.user.ISaiyAccount;\nimport ai.saiy.android.user.SaiyAccount;\nimport ai.saiy.android.user.SaiyAccountHelper;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsList;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 25/07/2016.\n */\n\npublic class FragmentSuperuserHelper implements ISaiyAccount {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = FragmentSuperuserHelper.class.getSimpleName();\n\n    private static final long ARBITRARY_DELAY = 7000;\n    private final AtomicBoolean accountInitialised = new AtomicBoolean();\n    public final AtomicBoolean enrollmentCancelled = new AtomicBoolean();\n\n    private final FragmentSuperUser parentFragment;\n    private volatile Timer timer;\n\n    /**\n     * Constructor\n     *\n     * @param parentFragment the parent fragment for this helper class\n     */\n    public FragmentSuperuserHelper(@NonNull final FragmentSuperUser parentFragment) {\n        this.parentFragment = parentFragment;\n    }\n\n    /**\n     * Get the components for this fragment\n     *\n     * @return a list of {@link ContainerUI} elements\n     */\n    private ArrayList<ContainerUI> getUIComponents() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getUIComponents\");\n        }\n\n        final ArrayList<ContainerUI> mObjects = new ArrayList<>();\n\n        ContainerUI containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_root));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_linux);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_start_boot));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_fan);\n        containerUI.setIconExtra(SPH.getStartAtBoot(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_vocal_verification));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_account_key);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_ping));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_radio_tower);\n        containerUI.setIconExtra(SPH.getPingCheck(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_blacklist));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_traffic_light);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_memory_usage));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_memory);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_intercept_google));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_google);\n        containerUI.setIconExtra(SPH.getInterceptGoogle(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_algorithms));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_configure));\n        containerUI.setIconMain(R.drawable.ic_function);\n        containerUI.setIconExtra(FragmentHome.CHEVRON);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_recogniser_busy_fix));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_block_helper);\n        containerUI.setIconExtra(SPH.getRecogniserBusyFix(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        containerUI = new ContainerUI();\n        containerUI.setTitle(getParent().getString(R.string.menu_okay_google_fix));\n        containerUI.setSubtitle(getParent().getString(R.string.menu_tap_toggle));\n        containerUI.setIconMain(R.drawable.ic_google);\n        containerUI.setIconExtra(SPH.getOkayGoogleFix(getApplicationContext())\n                ? FragmentHome.CHECKED : FragmentHome.UNCHECKED);\n        mObjects.add(containerUI);\n\n        return mObjects;\n    }\n\n    /**\n     * Get the recycler view for this fragment\n     *\n     * @param parent the view parent\n     * @return the {@link RecyclerView}\n     */\n    public RecyclerView getRecyclerView(@NonNull final View parent) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getRecyclerView\");\n        }\n\n        final RecyclerView mRecyclerView = (RecyclerView) parent.findViewById(R.id.layout_common_fragment_recycler_view);\n        mRecyclerView.setHasFixedSize(true);\n        mRecyclerView.setLayoutManager(new LinearLayoutManager(getParentActivity()));\n        mRecyclerView.addItemDecoration(new DividerItemDecoration(getParentActivity(), null));\n\n        return mRecyclerView;\n    }\n\n    /**\n     * Get the adapter for this fragment\n     *\n     * @param mObjects list of {@link ContainerUI} elements\n     * @return the {@link UIMainAdapter}\n     */\n    public UIMainAdapter getAdapter(@NonNull final ArrayList<ContainerUI> mObjects) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAdapter\");\n        }\n        return new UIMainAdapter(mObjects, getParent(), getParent());\n    }\n\n    /**\n     * Update the parent fragment with the UI components\n     */\n    public void finaliseUI() {\n\n        AsyncTask.execute(new Runnable() {\n            @Override\n            public void run() {\n                final ArrayList<ContainerUI> tempArray = FragmentSuperuserHelper.this.getUIComponents();\n\n                try {\n                    Thread.sleep(FragmentHome.DRAWER_CLOSE_DELAY);\n                } catch (final InterruptedException e) {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI InterruptedException\");\n                        e.printStackTrace();\n                    }\n                }\n\n                if (FragmentSuperuserHelper.this.getParent().isActive()) {\n\n                    FragmentSuperuserHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                        @Override\n                        public void run() {\n\n                            FragmentSuperuserHelper.this.getParent().getObjects().addAll(tempArray);\n                            FragmentSuperuserHelper.this.getParent().getAdapter().notifyItemRangeInserted(0, FragmentSuperuserHelper.this.getParent().getObjects().size());\n                        }\n                    });\n\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"finaliseUI Fragment detached\");\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * Show the Blacklist selector\n     *\n     * @return true if the applications installed to populate the selector, false otherwise\n     */\n    public boolean showBlackListSelector() {\n\n        final BlackListHelper blackListHelper = new BlackListHelper();\n        final ArrayList<String> blackListArray = blackListHelper.fetch(getApplicationContext());\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showBlackListSelector: blackListArray: \" + blackListArray.size());\n        }\n\n        final ArrayList<Pair<String, String>> installedPackages = Installed.declaresSaiyPermission(\n                getApplicationContext());\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showBlackListSelector: installedPackages: \" + installedPackages.size());\n        }\n\n        if (!UtilsList.notNaked(installedPackages)) {\n            return false;\n        }\n\n        final ArrayList<String> appNames = new ArrayList<>(installedPackages.size());\n        final ArrayList<Integer> selectedList = new ArrayList<>();\n\n        for (final Pair appPair : installedPackages) {\n            appNames.add((String) appPair.first);\n\n            //noinspection SuspiciousMethodCalls\n            if (blackListArray.contains(appPair.second)) {\n                selectedList.add((appNames.size() - 1));\n            }\n        }\n\n        getParentActivity().runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n\n            final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                    .autoDismiss(false)\n                    .title(R.string.menu_blacklist)\n                    .content(R.string.blacklist_intro_text)\n                    .iconRes(R.drawable.ic_traffic_light)\n                    .positiveText(R.string.save)\n                    .neutralText(R.string.clear)\n                    .negativeText(android.R.string.cancel)\n                    .items(appNames)\n\n                        .itemsCallbackMultiChoice(selectedList.toArray(new Integer[0]), new MaterialDialog.ListCallbackMultiChoice() {\n                            @Override\n                            public boolean onSelection(final MaterialDialog dialog, final Integer[] which, final CharSequence[] text) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onSelection: \" + which.length);\n                                }\n                                return true;\n                            }\n                        })\n\n                        .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                            @Override\n                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                dialog.clearSelectedIndices();\n                            }\n                        })\n\n                        .onPositive(new MaterialDialog.SingleButtonCallback() {\n                            @Override\n                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onPositive\");\n                                }\n\n                                final Integer[] selected = dialog.getSelectedIndices();\n                                assert selected != null;\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onPositive: \" + selected.length);\n                                }\n\n                                final ArrayList<String> userBlackListed = new ArrayList<>(selected.length);\n\n                                for (final Integer aSelected : selected) {\n                                    if (DEBUG) {\n                                        MyLog.i(CLS_NAME, \"showBlackListSelector: onPositive: \"\n                                                + installedPackages.get(aSelected).second);\n                                    }\n                                    userBlackListed.add(installedPackages.get(aSelected).second);\n                                }\n\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onPositive: would save: \"\n                                            + userBlackListed.toString());\n                                }\n\n                                blackListHelper.save(FragmentSuperuserHelper.this.getApplicationContext(), userBlackListed);\n\n                                dialog.dismiss();\n                            }\n                        })\n\n                        .onNegative(new MaterialDialog.SingleButtonCallback() {\n                            @Override\n                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onNegative\");\n                                }\n                                dialog.dismiss();\n                            }\n                        })\n\n                        .cancelListener(new DialogInterface.OnCancelListener() {\n                            @Override\n                            public void onCancel(final DialogInterface dialog) {\n                                if (DEBUG) {\n                                    MyLog.i(CLS_NAME, \"showBlackListSelector: onCancel\");\n                                }\n                                dialog.dismiss();\n                            }\n                        }).build();\n\n                materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                materialDialog.show();\n\n            }\n        });\n\n        return true;\n    }\n\n    /**\n     * Show the memory slider\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    public void showMemorySlider() {\n\n        final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                .customView(R.layout.memory_dialog_layout, false)\n                .autoDismiss(false)\n                .title(R.string.menu_memory_usage)\n                .iconRes(R.drawable.ic_memory)\n                .positiveText(R.string.save)\n                .neutralText(R.string.text_default)\n                .negativeText(android.R.string.cancel)\n\n                .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        ((SeekBar) dialog.getCustomView().findViewById(R.id.memorySeekBar))\n                                .setProgress((int) ((SelfAwareConditions.DEFAULT_INACTIVITY_TIMEOUT / 60000L) - 1));\n                    }\n                })\n\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                        final int position = ((SeekBar) dialog.getCustomView().findViewById(R.id.memorySeekBar)).getProgress();\n                        final long timeout = (position + 1) * 60000L;\n\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showMemorySlider: onPositive: position: \" + position);\n                            MyLog.i(CLS_NAME, \"showMemorySlider: onPositive: timeout: \" + timeout);\n                        }\n\n                        SPH.setInactivityTimeout(FragmentSuperuserHelper.this.getApplicationContext(), timeout);\n                        dialog.dismiss();\n                    }\n                })\n\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showMemorySlider: onNegative\");\n                        }\n                        dialog.dismiss();\n                    }\n                })\n\n                .cancelListener(new DialogInterface.OnCancelListener() {\n                    @Override\n                    public void onCancel(final DialogInterface dialog) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showMemorySlider: onCancel\");\n                        }\n                        dialog.dismiss();\n                    }\n                }).build();\n\n        final int currentTimeout = (int) (SPH.getInactivityTimeout(getApplicationContext()) / 60000L);\n        final TextView seekText = (TextView) materialDialog.getCustomView().findViewById(R.id.memorySeekBarText);\n\n        switch (currentTimeout) {\n            case 1:\n                seekText.setText(getParent().getString(R.string.memory_usage_text)\n                        + \" \" + currentTimeout + \" \" + getParent().getString(R.string.minute));\n                break;\n            default:\n                seekText.setText(getParent().getString(R.string.memory_usage_text)\n                        + \" \" + currentTimeout + \" \" + getParent().getString(R.string.minutes));\n                break;\n        }\n\n        final SeekBar seekbar = (SeekBar) materialDialog.getCustomView().findViewById(R.id.memorySeekBar);\n        seekbar.setProgress(currentTimeout - 1);\n\n        seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n\n            @Override\n            public void onProgressChanged(final SeekBar seekBar, int progress, final boolean fromUser) {\n\n                progress++;\n\n                switch (progress) {\n                    case 1:\n                        seekText.setText(getParent().getString(R.string.memory_usage_text)\n                                + \" \" + progress + \" \" + getParent().getString(R.string.minute));\n                        break;\n                    default:\n                        seekText.setText(getParent().getString(R.string.memory_usage_text)\n                                + \" \" + progress + \" \" + getParent().getString(R.string.minutes));\n                        break;\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(final SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(final SeekBar seekBar) {\n            }\n        });\n\n        materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n        materialDialog.show();\n    }\n\n    /**\n     * Show the account picker dialog\n     */\n    @SuppressWarnings(\"MissingPermission, ConstantConditions\")\n    public void showAccountPicker() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"showAccountPicker\");\n        }\n\n        if (PermissionHelper.checkContactGroupPermissions(getApplicationContext())) {\n\n            AsyncTask.execute(new Runnable() {\n                @Override\n                public void run() {\n\n                    final AccountManager accountManager = AccountManager.get(FragmentSuperuserHelper.this.getApplicationContext());\n                    final Account[] accounts = accountManager.getAccountsByType(Install.getAccountType());\n\n                    if (accounts.length > 0) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"showAccountPicker: accounts: \" + accounts.length);\n                        }\n\n                    final String[] accountNames = new String[accounts.length];\n\n                    for (int i = 0; i < accounts.length; i++) {\n                        if (DEBUG) {\n                            MyLog.v(CLS_NAME, \"account : \" + accounts[i].toString());\n                            MyLog.v(CLS_NAME, \"name : \" + accounts[i].name);\n                            MyLog.v(CLS_NAME, \"type : \" + accounts[i].type);\n                        }\n\n                            accountNames[i] = accounts[i].name;\n                        }\n\n                        FragmentSuperuserHelper.this.getParentActivity().runOnUiThread(new Runnable() {\n                            @Override\n                            public void run() {\n\n                        final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                                .autoDismiss(false)\n                                .alwaysCallSingleChoiceCallback()\n                                .title(R.string.dialog_id_verification)\n                                .content(R.string.dialog_id_verification_content)\n                                .items((CharSequence[]) accountNames)\n                                .positiveText(R.string.menu_select)\n                                .negativeText(android.R.string.cancel)\n                                .neutralText(StringUtils.capitalize(getParent().getString(R.string.add_new)))\n                                .iconRes(R.drawable.ic_account_key)\n                                .backgroundColorRes(R.color.colorTint)\n\n                                        .onNeutral(new MaterialDialog.SingleButtonCallback() {\n                                            @Override\n                                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                                                ExecuteIntent.settingsIntent(FragmentSuperuserHelper.this.getApplicationContext(),\n                                                        IntentConstants.SETTINGS_ADD_ACCOUNT);\n                                                dialog.dismiss();\n                                            }\n                                        })\n\n                                        .itemsCallbackSingleChoice(0,\n                                                new MaterialDialog.ListCallbackSingleChoice() {\n                                                    @Override\n                                                    public boolean onSelection(final MaterialDialog dialog, final View view, final int which, final CharSequence text) {\n                                                        if (DEBUG) {\n                                                            MyLog.i(CLS_NAME, \"showAccountPicker: onSelection: \" + which + \": \" + text);\n                                                        }\n                                                        return true;\n                                                    }\n                                                })\n\n                                        .onPositive(new MaterialDialog.SingleButtonCallback() {\n                                            @Override\n                                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n\n                                                final int selected = dialog.getSelectedIndex();\n\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showAccountPicker: onPositive: \" + selected + \": \" + which);\n                                                }\n\n                                                final Account account = accounts[selected];\n\n                                                if (DEBUG) {\n                                                    MyLog.v(CLS_NAME, \"account : \" + account.toString());\n                                                    MyLog.v(CLS_NAME, \"name : \" + account.name);\n                                                    MyLog.v(CLS_NAME, \"type : \" + account.type);\n                                                }\n                                                FragmentSuperuserHelper.this.startEnrollment(account.name);\n                                                dialog.dismiss();\n                                            }\n                                        })\n\n                                        .onNegative(new MaterialDialog.SingleButtonCallback() {\n                                            @Override\n                                            public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showAccountPicker: onNegative\");\n                                                }\n                                                dialog.dismiss();\n                                            }\n                                        })\n\n                                        .cancelListener(new DialogInterface.OnCancelListener() {\n                                            @Override\n                                            public void onCancel(final DialogInterface dialog) {\n                                                if (DEBUG) {\n                                                    MyLog.i(CLS_NAME, \"showAccountPicker: onCancel\");\n                                                }\n                                                dialog.dismiss();\n                                            }\n                                        }).build();\n\n                                materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n                                materialDialog.show();\n                            }\n                        });\n\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"showAccountPicker: no accounts\");\n                        }\n\n                        ExecuteIntent.settingsIntent(FragmentSuperuserHelper.this.getApplicationContext(), IntentConstants.SETTINGS_ADD_ACCOUNT);\n                        FragmentSuperuserHelper.this.getParentActivity().speak(R.string.error_vi_no_account,\n                                LocalRequest.ACTION_SPEAK_ONLY);\n\n                    }\n\n                }\n            });\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"showAccountPicker: permission denied\");\n            }\n\n            ExecuteIntent.openApplicationSpecificSettings(getApplicationContext(),\n                    getApplicationContext().getPackageName());\n            getParentActivity().speak(R.string.permission_group_contacts_denied,\n                    LocalRequest.ACTION_SPEAK_ONLY);\n        }\n    }\n\n    /**\n     * Start the process of enrolling the user's voice against the given account, first ensuring that\n     * an association does not already exist.\n     *\n     * @param accountName the account name\n     */\n    @SuppressWarnings(\"ConstantConditions\")\n    private void startEnrollment(@NonNull final String accountName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"startEnrollment\");\n        }\n\n        if (SaiyAccountHelper.accountExists(getApplicationContext(), accountName, null)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"startEnrollment: account exists\");\n            }\n\n            final MaterialDialog materialDialog = new MaterialDialog.Builder(getParentActivity())\n                    .autoDismiss(false)\n                    .limitIconToDefaultSize()\n                    .title(R.string.menu_unlink_association)\n                    .content(getParent().getString(R.string.content_unlink_association, accountName))\n                    .positiveText(android.R.string.ok)\n                    .negativeText(android.R.string.cancel)\n                    .iconRes(R.drawable.ic_account_switch)\n                    .backgroundColorRes(R.color.colorTint)\n\n                    .onPositive(new MaterialDialog.SingleButtonCallback() {\n                        @Override\n                        public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"startEnrollment: onPositive\");\n                            }\n\n                            if (SaiyAccountHelper.deleteAccount(FragmentSuperuserHelper.this.getApplicationContext(), accountName, null)) {\n                                FragmentSuperuserHelper.this.proceedEnrollment(accountName);\n                            }\n\n                            dialog.dismiss();\n                        }\n                    })\n\n                    .onNegative(new MaterialDialog.SingleButtonCallback() {\n                        @Override\n                        public void onClick(@NonNull final MaterialDialog dialog, @NonNull final DialogAction which) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"startEnrollment: onNegative\");\n                            }\n                            dialog.dismiss();\n                        }\n                    })\n\n                    .cancelListener(new DialogInterface.OnCancelListener() {\n                        @Override\n                        public void onCancel(final DialogInterface dialog) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"startEnrollment: onCancel\");\n                            }\n                            dialog.dismiss();\n                        }\n                    }).build();\n\n            materialDialog.getWindow().getAttributes().windowAnimations = R.style.dialog_animation_left;\n            materialDialog.show();\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"startEnrollment: account does not exist\");\n            }\n\n            proceedEnrollment(accountName);\n        }\n    }\n\n    /**\n     * Proceed with the enrollment process knowing that any existing association to the\n     * given account has been deleted consensually along with any remote profile.\n     * <p>\n     * Once the account set up is complete a callback will be provided from the\n     * {@link ISaiyAccount} interface. Any issue in the account creation will be\n     * handled by {@link #monitorAccountCreation(String)}\n     *\n     * @param accountName the account name\n     */\n    private void proceedEnrollment(@NonNull final String accountName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"proceedEnrollment\");\n        }\n\n        final SaiyAccount saiyAccount = new SaiyAccount(accountName, true);\n        saiyAccount.setAccountId(getApplicationContext(), this);\n        saiyAccount.setPseudonym(SPH.getUserName(getApplicationContext()), true);\n\n        getParentActivity().showProgress(true);\n        monitorAccountCreation(accountName);\n\n    }\n\n    @Override\n    public void onAccountInitialisation(@NonNull final SaiyAccount saiyAccount) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onAccountInitialisation\");\n        }\n\n        if (!enrollmentCancelled.get()) {\n            accountInitialised.set(true);\n            cancelTimer();\n\n            getParentActivity().showProgress(false);\n\n            if (accountSetUpComplete(saiyAccount)) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"onAccountInitialisation: set up complete\");\n                }\n                audioEnroll(saiyAccount.getProfileItem().getId());\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"onAccountInitialisation: set up failed\");\n                }\n                enrollmentFailed(saiyAccount.getAccountName());\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"onAccountInitialisation: enrollment cancelled\");\n            }\n        }\n    }\n\n    /**\n     * Method to monitor the background set up process of the {@link SaiyAccount}. Due to any\n     * condition that could cause a failure, most probably network related, we draw a line\n     * at {@link #ARBITRARY_DELAY} and notify the user that things have gone wrong.\n     *\n     * @param accountName the account name associated with the account being created\n     */\n    private void monitorAccountCreation(@NonNull final String accountName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"monitorAccountCreation\");\n        }\n\n        timer = new Timer();\n        final TimerTask timerTask = new TimerTask() {\n            @Override\n            public void run() {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"timerTask: checking account status\");\n                }\n\n                getParentActivity().showProgress(false);\n\n                if (accountInitialised.get()) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"account set up successfully\");\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"account set up failed\");\n                    }\n\n                    enrollmentCancelled.set(true);\n                    enrollmentFailed(accountName);\n                }\n            }\n        };\n\n        timer.schedule(timerTask, ARBITRARY_DELAY);\n    }\n\n    /**\n     * Notify the user that the account creation failed\n     *\n     * @param accountName the account name\n     */\n    private void enrollmentFailed(@NonNull final String accountName) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"enrollmentFailed\");\n        }\n\n        final SupportedLanguage sl = SupportedLanguage.getSupportedLanguage(SPH.getVRLocale(getApplicationContext()));\n\n        getParentActivity().speak(PersonalityResponse.getEnrollmentAPIError(getApplicationContext(), sl),\n                LocalRequest.ACTION_SPEAK_ONLY);\n        SaiyAccountHelper.deleteAccount(getApplicationContext(), accountName, null);\n    }\n\n\n    /**\n     * All is well - begin the audio enrollment\n     */\n    private void audioEnroll(@NonNull final String profileId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"audioEnroll\");\n        }\n\n\n        final String utterance;\n\n        if (SPH.getEnrollmentVerbose(getApplicationContext())) {\n            utterance = getApplicationContext().getString(R.string.speech_enroll_instructions_40);\n        } else {\n            SPH.setEnrollmentVerbose(getApplicationContext());\n            utterance = getApplicationContext().getString(R.string.speech_enroll_instructions_first);\n        }\n\n        final LocalRequest request = new LocalRequest(getApplicationContext());\n        request.prepareDefault(LocalRequest.ACTION_SPEAK_LISTEN, utterance);\n        request.setQueueType(TextToSpeech.QUEUE_ADD);\n        request.setCondition(Condition.CONDITION_IDENTITY);\n        request.setIdentityProfile(profileId);\n        request.execute();\n    }\n\n    /**\n     * Utility method to check if the asynchronous operations have completed.\n     *\n     * @param saiyAccount the {@link SaiyAccount} object\n     * @return true if the asynchronous operations have completed, false otherwise\n     */\n    private boolean accountSetUpComplete(@NonNull final SaiyAccount saiyAccount) {\n        return UtilsString.notNaked(saiyAccount.getAccountId())\n                && saiyAccount.getProfileItem() != null\n                && UtilsString.notNaked(saiyAccount.getProfileItem().getId());\n    }\n\n    /**\n     * Cancel the timer, as it's no longer needed.\n     */\n    public void cancelTimer() {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelTimer\");\n        }\n\n        if (timer != null) {\n\n            try {\n                timer.cancel();\n                timer.purge();\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"cancelTimer: NullPointerException\");\n                    e.printStackTrace();\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"cancelTimer: Exception\");\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * Utility method to ensure we double check the context being used.\n     *\n     * @return the application context\n     */\n    private Context getApplicationContext() {\n        return parentFragment.getApplicationContext();\n    }\n\n    /**\n     * Utility to return the parent activity neatly cast. No need for instanceOf as this\n     * fragment helper will never be attached to another activity.\n     *\n     * @return the {@link ActivityHome} parent\n     */\n\n    public ActivityHome getParentActivity() {\n        return parentFragment.getParentActivity();\n    }\n\n    /**\n     * Utility method to return the parent fragment this helper is helping.\n     *\n     * @return the parent fragment\n     */\n    public FragmentSuperUser getParent() {\n        return parentFragment;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/notification/NotificationHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.notification;\n\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.Resources;\nimport android.graphics.Color;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.NotificationCompat;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.Speaker;\nimport ai.saiy.android.permissions.PermissionHelper;\nimport ai.saiy.android.personality.AI;\nimport ai.saiy.android.processing.Condition;\nimport ai.saiy.android.service.NotificationService;\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Helper class to deal with notification configuration and updates. Static access for ease.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic final class NotificationHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = NotificationHelper.class.getSimpleName();\n\n    public static final int MIN_NOTIFICATION_SPEECH_LENGTH = 35;\n\n    public static final int NOTIFICATION_SELF_AWARE = 0;\n    public static final int NOTIFICATION_HOTWORD = 1;\n    public static final int NOTIFICATION_DRIVING_PROFILE = 2;\n    public static final int NOTIFICATION_TUTORIAL = 3;\n\n    public static final String NOTIFICATION_CHANNEL_PERMANENT = \"not_channel_permanent\";\n    public static final String NOTIFICATION_CHANNEL_PRIORITY = \"not_channel_priority\";\n    public static final String NOTIFICATION_CHANNEL_INTERACTION = \"not_channel_interaction\";\n    public static final String NOTIFICATION_CHANNEL_INFORMATION = \"not_channel_information\";\n\n    /**\n     * Prevent instantiation\n     */\n    public NotificationHelper() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static void createNotificationChannels(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createNotificationChannels\");\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n\n            if (UtilsList.notNaked(notificationManager.getNotificationChannels())) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"createNotificationChannels: already exist\");\n                }\n\n                return;\n            }\n\n            NotificationChannel mChannel = new NotificationChannel(NOTIFICATION_CHANNEL_PERMANENT,\n                    ctx.getString(R.string.menu_not_channel_permanent), NotificationManager.IMPORTANCE_LOW);\n\n            mChannel.setDescription(ctx.getString(R.string.menu_not_channel_permanent_description));\n            mChannel.enableLights(false);\n            mChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);\n            notificationManager.createNotificationChannel(mChannel);\n\n            mChannel = new NotificationChannel(NOTIFICATION_CHANNEL_PRIORITY,\n                    ctx.getString(R.string.menu_not_channel_priority), NotificationManager.IMPORTANCE_DEFAULT);\n\n            mChannel.setDescription(ctx.getString(R.string.menu_not_channel_priority_description));\n            mChannel.enableLights(false);\n            mChannel.enableVibration(false);\n            mChannel.setSound(null, null);\n            notificationManager.createNotificationChannel(mChannel);\n\n            mChannel = new NotificationChannel(NOTIFICATION_CHANNEL_INTERACTION,\n                    ctx.getString(R.string.menu_not_channel_interaction), NotificationManager.IMPORTANCE_LOW);\n\n            mChannel.setDescription(ctx.getString(R.string.menu_not_channel_interaction_description));\n            mChannel.enableLights(false);\n            notificationManager.createNotificationChannel(mChannel);\n\n            mChannel = new NotificationChannel(NOTIFICATION_CHANNEL_INFORMATION,\n                    ctx.getString(R.string.menu_not_channel_information), NotificationManager.IMPORTANCE_HIGH);\n\n            mChannel.setDescription(ctx.getString(R.string.menu_not_channel_information_description));\n            mChannel.enableLights(true);\n            mChannel.setLightColor(Color.BLUE);\n            notificationManager.createNotificationChannel(mChannel);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"createNotificationChannels: not supported\");\n            }\n        }\n    }\n\n    /**\n     * Start the foreground notification\n     *\n     * @param ctx                  the application context\n     * @param notificationConstant integer constant denoting if a notification action button should be displayed\n     */\n    @SuppressWarnings(\"deprecation\")\n    public static Notification getForegroundNotification(@NonNull final Context ctx, final int notificationConstant) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getForegroundNotification\");\n        }\n\n        final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n        actionIntent.setPackage(ctx.getPackageName());\n        actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_FOREGROUND);\n\n        final PendingIntent pendingIntent;\n\n        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {\n            pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_FOREGROUND,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n        } else {\n            pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_FOREGROUND,\n                    actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n        }\n\n        String channel;\n\n        switch (notificationConstant) {\n\n            case NOTIFICATION_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_HOTWORD\");\n                }\n                channel = NOTIFICATION_CHANNEL_PRIORITY;\n                break;\n            case NOTIFICATION_DRIVING_PROFILE:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_DRIVING_PROFILE\");\n                }\n                channel = NOTIFICATION_CHANNEL_PRIORITY;\n                break;\n            case NOTIFICATION_TUTORIAL:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_TUTORIAL\");\n                }\n                channel = NOTIFICATION_CHANNEL_PRIORITY;\n                break;\n            case NOTIFICATION_SELF_AWARE:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_SELF_AWARE\");\n                }\n                channel = NOTIFICATION_CHANNEL_PERMANENT;\n                break;\n\n        }\n\n        final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, channel);\n\n        final String contentTitle;\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            contentTitle = ctx.getString(R.string.app_name);\n        } else {\n            contentTitle = ctx.getString(R.string.app_name);\n        }\n\n        builder.setContentIntent(pendingIntent).setSmallIcon(R.mipmap.ic_launcher)\n                .setTicker(ctx.getString(ai.saiy.android.R.string.notification_ticker))\n                .setWhen(System.currentTimeMillis())\n                .setContentTitle(contentTitle)\n                .setOngoing(true)\n                .setContentText(String.format(ctx.getString(ai.saiy.android.R.string.notification_ai_level),\n                        AI.getAILevel()));\n\n        PendingIntent actionPendingIntent;\n\n        switch (notificationConstant) {\n\n            case NOTIFICATION_HOTWORD:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_HOTWORD\");\n                }\n\n                actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_HOTWORD);\n\n                actionPendingIntent = PendingIntent.getService(ctx,\n                        NotificationService.NOTIFICATION_HOTWORD, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n\n                builder.addAction(R.drawable.ic_blur, ctx.getString(R.string.notification_stop_hotword),\n                        actionPendingIntent);\n\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n                    builder.setPriority(Notification.PRIORITY_MAX);\n                } else {\n                    builder.setColorized(true);\n                    builder.setColor(Color.RED);\n                }\n\n                break;\n\n            case NOTIFICATION_SELF_AWARE:\n            default:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getForegroundNotification: NOTIFICATION_SELF_AWARE\");\n                }\n\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n                    builder.setPriority(Notification.PRIORITY_MIN);\n                }\n\n                break;\n\n        }\n\n        return builder.build();\n    }\n\n    /**\n     * Show a listening notification\n     *\n     * @param ctx the application context\n     */\n    public static void createListeningNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createListeningNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_LISTENING);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_LISTENING,\n                    actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INTERACTION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_btn_speak_now)\n                    .setTicker(ctx.getString(ai.saiy.android.R.string.notification_listening)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(ai.saiy.android.R.string.notification_listening) + \"... \"\n                            + ctx.getString(ai.saiy.android.R.string.notification_tap_cancel))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_LISTENING, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createListeningNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Remove the listening notification\n     *\n     * @param ctx the application context\n     */\n    public static void cancelListeningNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelListeningNotification\");\n        }\n\n        final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n        notificationManager.cancel(NotificationService.NOTIFICATION_LISTENING);\n    }\n\n    /**\n     * Show a speaking notification\n     *\n     * @param ctx    the application context\n     * @param length the length of the utterance\n     */\n    public static void createSpeakingNotification(@NonNull final Context ctx, final int length) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createSpeakingNotification\");\n        }\n\n        if (length > MIN_NOTIFICATION_SPEECH_LENGTH) {\n\n            try {\n\n                final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n                actionIntent.setPackage(ctx.getPackageName());\n                actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_SPEAKING);\n\n                final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_SPEAKING,\n                        actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n\n                final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INTERACTION);\n\n                builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_media_pause)\n                        .setTicker(ctx.getString(ai.saiy.android.R.string.notification_speaking)).setWhen(System.currentTimeMillis())\n                        .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                        .setContentText(ctx.getString(ai.saiy.android.R.string.notification_speaking) + \"... \"\n                                + ctx.getString(ai.saiy.android.R.string.notification_tap_stop))\n                        .setAutoCancel(true);\n\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                    builder.setColorized(false);\n                    builder.setColor(Color.RED);\n                }\n\n                final Notification not = builder.build();\n                final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n                notificationManager.notify(NotificationService.NOTIFICATION_SPEAKING, not);\n\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"createSpeakingNotification failure\");\n                    e.printStackTrace();\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"createSpeakingNotification: length \" + length + \" too short ignoring\");\n            }\n        }\n    }\n\n    /**\n     * Remove the speaking notification\n     *\n     * @param ctx the application context\n     */\n    public static void cancelSpeakingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelSpeakingNotification\");\n        }\n\n        final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n        notificationManager.cancel(NotificationService.NOTIFICATION_SPEAKING);\n    }\n\n    /**\n     * Show a fetching notification.\n     *\n     * @param ctx the application context\n     */\n    public static void createFetchingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createFetchingNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_FETCHING);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_FETCHING,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INTERACTION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.stat_sys_upload)\n                    .setTicker(ctx.getString(ai.saiy.android.R.string.notification_fetching)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(ai.saiy.android.R.string.notification_fetching) + \"... \"\n                            + ctx.getString(ai.saiy.android.R.string.notification_tap_cancel))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_FETCHING, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createFetchingNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Remove the fetching notification\n     *\n     * @param ctx the application context\n     */\n    public static void cancelFetchingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelFetchingNotification\");\n        }\n\n        final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n        notificationManager.cancel(NotificationService.NOTIFICATION_FETCHING);\n    }\n\n    /**\n     * Show an initialising notification.\n     *\n     * @param ctx the application context\n     */\n    public static void createInitialisingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createInitialisingNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_INITIALISING);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_INITIALISING,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INTERACTION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_popup_sync)\n                    .setTicker(ctx.getString(ai.saiy.android.R.string.notification_initialising_tts))\n                    .setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(ai.saiy.android.R.string.notification_initialising_tts) + \"... \"\n                            + ctx.getString(ai.saiy.android.R.string.notification_tap_cancel))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_INITIALISING, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createInitialisingNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Remove the initialising notification\n     *\n     * @param ctx the application context\n     */\n    public static void cancelInitialisingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelInitialisingNotification\");\n        }\n\n        final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n        notificationManager.cancel(NotificationService.NOTIFICATION_INITIALISING);\n    }\n\n    /**\n     * Show a computing notification.\n     *\n     * @param ctx the application context\n     */\n    public static void createComputingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createComputingNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_COMPUTING);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_COMPUTING,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INTERACTION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_popup_sync)\n                    .setTicker(ctx.getString(ai.saiy.android.R.string.notification_computing)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(ai.saiy.android.R.string.notification_computing) + \"... \"\n                            + ctx.getString(ai.saiy.android.R.string.notification_tap_cancel))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_COMPUTING, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createComputingNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Remove the computing notification\n     *\n     * @param ctx the application context\n     */\n    public static void cancelComputingNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"cancelComputingNotification\");\n        }\n\n        final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n        notificationManager.cancel(NotificationService.NOTIFICATION_COMPUTING);\n    }\n\n    /**\n     * Show a permissions notification.\n     *\n     * @param ctx          the application context\n     * @param permissionId the permission id constant\n     */\n    public static void createPermissionsNotification(@NonNull final Context ctx, final int permissionId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createPermissionsNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_PERMISSIONS);\n            actionIntent.putExtra(PermissionHelper.REQUESTED_PERMISSION, permissionId);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_PERMISSIONS,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INFORMATION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_menu_info_details)\n                    .setTicker(ctx.getString(ai.saiy.android.R.string.permission_notification_ticker)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(ai.saiy.android.R.string.permission_notification_text))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_PERMISSIONS, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createPermissionsNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Show an emotion analysis notification.\n     *\n     * @param ctx the application context\n     */\n    public static void createEmotionAnalysisNotification(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createEmotionAnalysisNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION, NotificationService.NOTIFICATION_EMOTION);\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx, NotificationService.NOTIFICATION_EMOTION,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INFORMATION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_menu_info_details)\n                    .setTicker(ctx.getString(R.string.emotion_notification_ticker)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(R.string.emotion_notification_text))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_EMOTION, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createEmotionAnalysisNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Show an emotion analysis notification.\n     *\n     * @param ctx the application context\n     */\n    public static void createIdentificationNotification(@NonNull final Context ctx, final int condition,\n                                                        final boolean success, @Nullable final Speaker.Confidence confidence) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"createIdentificationNotification\");\n        }\n\n        try {\n\n            final Intent actionIntent = new Intent(NotificationService.INTENT_CLICK);\n            actionIntent.setPackage(ctx.getPackageName());\n            actionIntent.putExtra(NotificationService.CLICK_ACTION,\n                    NotificationService.NOTIFICATION_IDENTIFICATION);\n            actionIntent.putExtra(LocalRequest.EXTRA_CONDITION, condition);\n\n            switch (condition) {\n\n                case Condition.CONDITION_IDENTIFY:\n                    actionIntent.putExtra(Speaker.EXTRA_IDENTIFY_OUTCOME, confidence);\n                    break;\n                case Condition.CONDITION_IDENTITY:\n                    actionIntent.putExtra(Speaker.EXTRA_IDENTITY_OUTCOME, success);\n                    break;\n            }\n\n            final PendingIntent pendingIntent = PendingIntent.getService(ctx,\n                    NotificationService.NOTIFICATION_IDENTIFICATION,\n                    actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);\n\n            final NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_INFORMATION);\n\n            builder.setContentIntent(pendingIntent).setSmallIcon(android.R.drawable.ic_menu_info_details)\n                    .setTicker(ctx.getString(R.string.vocal_notification_ticker)).setWhen(System.currentTimeMillis())\n                    .setContentTitle(ctx.getString(ai.saiy.android.R.string.app_name))\n                    .setContentText(ctx.getString(R.string.vocal_notification_text))\n                    .setAutoCancel(true);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder.setColorized(false);\n                builder.setColor(Color.RED);\n            }\n\n            final Notification not = builder.build();\n            final NotificationManager notificationManager = (NotificationManager)\n                    ctx.getSystemService(Context.NOTIFICATION_SERVICE);\n            notificationManager.notify(NotificationService.NOTIFICATION_IDENTIFICATION, not);\n\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"createIdentificationNotification failure\");\n                e.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/ui/service/SaiyTileService.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.ui.service;\n\nimport android.content.Intent;\nimport android.os.Build;\nimport android.service.quicksettings.TileService;\nimport android.support.annotation.RequiresApi;\n\nimport ai.saiy.android.service.helper.LocalRequest;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Class to handle the Tile that can be added in API 24+\n * <p>\n * Created by benrandall76@gmail.com on 13/07/2016.\n */\n\n@RequiresApi(api = Build.VERSION_CODES.N)\npublic class SaiyTileService extends TileService {\n\n    private final boolean DEBUG = MyLog.DEBUG;\n    private final String CLS_NAME = SaiyTileService.class.getSimpleName();\n\n    public SaiyTileService() {\n    }\n\n    @Override\n    public void onClick() {\n        super.onClick();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onClick\");\n        }\n\n        final LocalRequest lr = new LocalRequest(getApplicationContext());\n        lr.prepareIntro();\n        lr.execute();\n\n        final Intent closeShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);\n        sendBroadcast(closeShadeIntent);\n\n//        final Intent preferenceIntent = new Intent(getApplicationContext(), ActivityTilePreferences.class);\n//        startActivityAndCollapse(preferenceIntent);\n    }\n\n    @Override\n    public void onStartListening() {\n        super.onStartListening();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStartListening\");\n        }\n    }\n\n    @Override\n    public void onStopListening() {\n        super.onStopListening();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onStopListening\");\n        }\n    }\n\n    @Override\n    public void onTileAdded() {\n        super.onTileAdded();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTileAdded\");\n        }\n    }\n\n    @Override\n    public void onTileRemoved() {\n        super.onTileRemoved();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onTileRemoved\");\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"onDestroy\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/user/ISaiyAccount.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.user;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Created by benrandall76@gmail.com on 10/09/2016.\n */\n\npublic interface ISaiyAccount {\n    void onAccountInitialisation(@NonNull final SaiyAccount saiyAccount);\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/user/SaiyAccount.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.user;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport java.util.Locale;\n\nimport ai.saiy.android.applications.Install;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.EnrollmentID;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileItem;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.http.CreateIDProfile;\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.utils.MyLog;\n\n/**\n * Created by benrandall76@gmail.com on 08/09/2016.\n */\n\npublic class SaiyAccount {\n\n    private transient final boolean DEBUG = MyLog.DEBUG;\n    private transient final String CLS_NAME = SaiyAccount.class.getSimpleName();\n\n    private String accountName;\n    private volatile String accountId;\n    private String pseudonym;\n    private volatile ProfileItem profileItem;\n    private boolean pseudonymLinked;\n    private final boolean autoEnroll;\n\n    /**\n     * Constructor\n     * <p>\n     * Create a basic account using the email address\n     *\n     * @param accountName the email address\n     * @param autoEnroll  true if the account should be saved automatically into the shared preferences\n     *                    once the {@link #accountId} has been asynchronously returned and a profile id\n     *                    should be fetched.\n     */\n    public SaiyAccount(@NonNull final String accountName, final boolean autoEnroll) {\n        this.autoEnroll = autoEnroll;\n        this.accountName = accountName;\n    }\n\n\n    /**\n     * Asynchronously set the account id associated with the email address and create a\n     * profile id if {@link #autoEnroll} is set to true;\n     *\n     * @param ctx the application context\n     */\n    public void setAccountId(@NonNull final Context ctx, @NonNull final ISaiyAccount listener) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"setAccountId: autoEnroll: \" + autoEnroll);\n        }\n\n        if (autoEnroll) {\n\n            new Thread() {\n                public void run() {\n\n                    final Pair<Boolean, EnrollmentID> enrollmentPair = new CreateIDProfile(ctx,\n                            MicrosoftConfiguration.OCP_APIM_KEY_1, Locale.getDefault()).getID();\n\n                    if (enrollmentPair.first) {\n                        profileItem = new ProfileItem(enrollmentPair.second.getId());\n                    }\n\n                    accountId = Install.getAccountId(ctx, accountName);\n\n                    listener.onAccountInitialisation(SaiyAccount.this);\n                    SaiyAccountHelper.addSaiyAccount(ctx, SaiyAccount.this);\n\n                }\n            }.start();\n        }\n    }\n\n    public String getPseudonym() {\n        return pseudonym;\n    }\n\n    /**\n     * Set the pseudonym associated with this user.\n     *\n     * @param pseudonym       the associated pseudonym\n     * @param pseudonymLinked true if this is derived from the vocal user name, false otherwise\n     */\n    public void setPseudonym(@NonNull final String pseudonym, final boolean pseudonymLinked) {\n        this.pseudonym = pseudonym;\n        this.pseudonymLinked = pseudonymLinked;\n    }\n\n    public boolean isPseudonymLinked() {\n        return pseudonymLinked;\n    }\n\n    public ProfileItem getProfileItem() {\n        return profileItem;\n    }\n\n    public void setProfileItem(@NonNull final ProfileItem profileItem) {\n        this.profileItem = profileItem;\n    }\n\n    public String getAccountId() {\n        return accountId;\n    }\n\n    public String getAccountName() {\n        return accountName;\n    }\n\n    public void setAccountName(final String accountName) {\n        this.accountName = accountName;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/user/SaiyAccountHelper.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.user;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Base64;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.regex.Pattern;\n\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.OperationStatus;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.containers.ProfileItem;\nimport ai.saiy.android.cognitive.identity.provider.microsoft.http.DeleteIDProfile;\nimport ai.saiy.android.configuration.MicrosoftConfiguration;\nimport ai.saiy.android.utils.Constants;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\nimport ai.saiy.android.utils.UtilsString;\n\n/**\n * Created by benrandall76@gmail.com on 09/09/2016.\n */\n\npublic class SaiyAccountHelper {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = SaiyAccountHelper.class.getSimpleName();\n\n    /**\n     * Check if we have Saiy accounts stored\n     *\n     * @param ctx the application context\n     * @return true if an {@link SaiyAccountList} is stored\n     */\n    public static boolean haveAccounts(@NonNull final Context ctx) {\n        return SPH.getSaiyAccounts(ctx) != null;\n    }\n\n    /**\n     * Get the current Saiy account list. Must not be called unless accounts have been confirmed\n     * to exists by {@link #haveAccounts(Context)}\n     *\n     * @param ctx the application context\n     * @return the {@link SaiyAccountList}\n     */\n    public static SaiyAccountList getAccountList(@NonNull final Context ctx) {\n        final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n        return decode(gson.fromJson(SPH.getSaiyAccounts(ctx), SaiyAccountList.class));\n    }\n\n    /**\n     * Get all {@link SaiyAccount}\n     *\n     * @param ctx the application context\n     * @return the {@link SaiyAccountList} or null if there are no accounts\n     */\n    public static SaiyAccountList getAccounts(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAccounts\");\n        }\n\n        if (haveAccounts(ctx)) {\n            return getAccountList(ctx);\n        }\n\n        return null;\n    }\n\n    /**\n     * Get the {@link SaiyAccount} from the account name or id.\n     *\n     * @param ctx         the application context\n     * @param accountName the name of the account to check\n     * @param accountId   the account id\n     * @return the {@link SaiyAccount} if one exists, null otherwise\n     */\n    public static SaiyAccount getAccount(@NonNull final Context ctx, @Nullable final String accountName,\n                                         @Nullable final String accountId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getAccount\");\n        }\n\n        if (haveAccounts(ctx)) {\n\n            final SaiyAccountList accountList = getAccountList(ctx);\n\n            for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n\n                if (accountName != null && accountId != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))\n                            || (UtilsString.notNaked(account.getAccountId())\n                            && account.getAccountId().matches(Pattern.quote(accountId)))) {\n                        return account;\n                    }\n                } else if (accountName != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))) {\n                        return account;\n                    }\n                } else {\n                    if (accountId != null && UtilsString.notNaked(account.getAccountId())) {\n                        if (account.getAccountId().matches(Pattern.quote(accountId))) {\n                            return account;\n                        }\n                    }\n                }\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Add a Saiy account to the current list, is one exists\n     *\n     * @param ctx         the application context\n     * @param saiyAccount the {@link SaiyAccount} object\n     */\n    public static void addSaiyAccount(@NonNull final Context ctx, @NonNull final SaiyAccount saiyAccount) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"addSaiyAccount\");\n        }\n\n        final SaiyAccountList accountList;\n\n        if (haveAccounts(ctx)) {\n\n            accountList = getAccountList(ctx);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"accountList: \" + accountList.size());\n\n                for (SaiyAccount account : accountList.getSaiyAccountList()) {\n                    MyLog.v(CLS_NAME, \"getAccountName: \" + account.getAccountName());\n                    MyLog.v(CLS_NAME, \"getAccountId: \" + account.getAccountId());\n                    MyLog.v(CLS_NAME, \"getPseudonym: \" + account.getPseudonym());\n                }\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"addSaiyAccount: no current\");\n            }\n\n            final List<SaiyAccount> list = new ArrayList<>(1);\n            list.add(saiyAccount);\n\n            accountList = new SaiyAccountList(list);\n        }\n\n        saveSaiyAccountList(ctx, accountList);\n    }\n\n    /**\n     * Save the Saiy account list to the shared preferences\n     *\n     * @param ctx         the application context\n     * @param accountList the {@link SaiyAccountList}\n     */\n    public static boolean saveSaiyAccountList(@NonNull final Context ctx,\n                                              @NonNull final SaiyAccountList accountList) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"saveSaiyAccountList\");\n        }\n\n        if (accountList.size() > 0) {\n\n            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n            final String gsonString = gson.toJson(encode(accountList));\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"saveSaiyAccountList: gsonString: \" + gsonString);\n            }\n\n            SPH.setSaiyAccounts(ctx, gsonString);\n\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"saveSaiyAccountList empty\");\n            }\n            SPH.setSaiyAccounts(ctx, null);\n        }\n\n        return true;\n    }\n\n    /**\n     * Update the enrollment status of a profile\n     *\n     * @param ctx             the application context\n     * @param operationStatus the {@link OperationStatus} object\n     * @param profileId       of the user\n     * @return true if the profile was successfully updated, false otherwise\n     */\n    public static boolean updateEnrollmentStatus(@NonNull final Context ctx,\n                                                 @Nullable final OperationStatus operationStatus,\n                                                 @NonNull final String profileId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"updateEnrollmentStatus\");\n        }\n\n        if (operationStatus == null || operationStatus.getProcessingResult() == null) {\n            return false;\n        }\n\n        if (haveAccounts(ctx)) {\n\n            boolean save = false;\n            final SaiyAccountList accountList = getAccountList(ctx);\n\n            for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n\n                if (account.getProfileItem() != null) {\n\n                    final ProfileItem item = account.getProfileItem();\n                    final String id = item.getId();\n\n                    if (UtilsString.notNaked(id)) {\n                        if (id.matches(Pattern.quote(profileId))) {\n                            if (DEBUG) {\n                                MyLog.i(CLS_NAME, \"updateEnrollmentStatus: have match\");\n                            }\n\n                            item.setCreated(operationStatus.getCreated());\n                            item.setLastAction(operationStatus.getLastAction());\n                            item.setRemainingSpeechTime(operationStatus.getProcessingResult().getRemainingSpeechTime());\n                            item.setSpeechTime(operationStatus.getProcessingResult().getSpeechTime());\n                            item.setStatus(operationStatus.getStatus());\n\n                            save = true;\n                            break;\n                        }\n                    } else {\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"updateEnrollmentStatus: id naked\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"updateEnrollmentStatus: getProfileItem null\");\n                    }\n                }\n            }\n\n            if (save) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"updateEnrollmentStatus: saving\");\n                }\n                saveSaiyAccountList(ctx, accountList);\n\n                if (DEBUG) {\n                    debugAccountList(ctx);\n                }\n\n                return true;\n            }\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"updateEnrollmentStatus: no account\");\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Verbosely debug all accounts\n     *\n     * @param ctx the application context\n     */\n    private static void debugAccountList(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"debugAccountList\");\n\n            if (haveAccounts(ctx)) {\n\n                final SaiyAccountList accountList = getAccountList(ctx);\n\n                for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n\n                    final ProfileItem item = account.getProfileItem();\n\n                    MyLog.i(CLS_NAME, \"debugAccountList getAccountId: \" + account.getAccountId());\n                    MyLog.i(CLS_NAME, \"debugAccountList getAccountName: \" + account.getAccountName());\n                    MyLog.i(CLS_NAME, \"debugAccountList getPseudonym: \" + account.getPseudonym());\n\n                    if (item != null) {\n                        MyLog.i(CLS_NAME, \"debugAccountList getId: \" + item.getId());\n                        MyLog.i(CLS_NAME, \"debugAccountList getStatus: \" + item.getStatus());\n                        MyLog.i(CLS_NAME, \"debugAccountList getSpeechTime: \" + item.getSpeechTime());\n                        MyLog.i(CLS_NAME, \"debugAccountList getRemainingSpeechTime: \" + item.getRemainingSpeechTime());\n                        MyLog.i(CLS_NAME, \"debugAccountList getLastAction: \" + item.getLastAction());\n                        MyLog.i(CLS_NAME, \"debugAccountList getCreated: \" + item.getCreated());\n                    } else {\n                        MyLog.w(CLS_NAME, \"debugAccountList: getProfileItem null\");\n                    }\n                }\n            } else {\n                MyLog.w(CLS_NAME, \"debugAccountList: no account\");\n            }\n        }\n    }\n\n\n    /**\n     * Check if we already have an existing Saiy account saved.\n     *\n     * @param ctx         the application context\n     * @param accountName the name of the account to check\n     * @param accountId   the account id\n     * @return true if the account exist, false otherwise\n     */\n    public static boolean accountExists(@NonNull final Context ctx, @Nullable final String accountName,\n                                        @Nullable final String accountId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"accountExists\");\n        }\n\n        if (haveAccounts(ctx)) {\n\n            final SaiyAccountList accountList = getAccountList(ctx);\n\n            for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n\n                if (accountName != null && accountId != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))\n                            || (UtilsString.notNaked(account.getAccountId())\n                            && account.getAccountId().matches(Pattern.quote(accountId)))) {\n                        return true;\n                    }\n                } else if (accountName != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))) {\n                        return true;\n                    }\n                } else {\n                    if (accountId != null && UtilsString.notNaked(account.getAccountId())) {\n                        if (account.getAccountId().matches(Pattern.quote(accountId))) {\n                            return true;\n                        }\n                    }\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Delete a current {@link SaiyAccount}\n     *\n     * @param ctx         the application context\n     * @param accountName the account name\n     * @param accountId   the account id\n     */\n    public static boolean deleteAccount(@NonNull final Context ctx, @Nullable final String accountName,\n                                        @Nullable final String accountId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"deleteAccount\");\n        }\n\n        if (haveAccounts(ctx)) {\n\n            final SaiyAccountList saiyAccountList = getAccountList(ctx);\n            final List<SaiyAccount> accountList = saiyAccountList.getSaiyAccountList();\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"deleteAccount have: \" + accountList.size());\n            }\n\n            final ListIterator<SaiyAccount> itr = accountList.listIterator();\n\n            SaiyAccount account;\n            ProfileItem profileItem;\n            String profileId;\n            while (itr.hasNext()) {\n                account = itr.next();\n\n                if (accountName != null && accountId != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))\n                            || (UtilsString.notNaked(account.getAccountId())\n                            && account.getAccountId().matches(Pattern.quote(accountId)))) {\n\n                        if (account.getProfileItem() != null) {\n                            profileItem = account.getProfileItem();\n                            profileId = profileItem.getId();\n                            removeProfile(ctx, profileId);\n                        }\n\n                        itr.remove();\n                    }\n                } else if (accountName != null) {\n                    if (account.getAccountName().matches(Pattern.quote(accountName))) {\n\n                        if (account.getProfileItem() != null) {\n                            profileItem = account.getProfileItem();\n                            profileId = profileItem.getId();\n                            removeProfile(ctx, profileId);\n                        }\n\n                        itr.remove();\n                    }\n                } else {\n                    if (accountId != null && UtilsString.notNaked(account.getAccountId())) {\n                        if (account.getAccountId().matches(Pattern.quote(accountId))) {\n\n                            if (account.getProfileItem() != null) {\n                                profileItem = account.getProfileItem();\n                                profileId = profileItem.getId();\n                                removeProfile(ctx, profileId);\n                            }\n\n                            itr.remove();\n                        }\n                    }\n                }\n            }\n\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"deleteAccount post have: \" + accountList.size());\n            }\n\n            return saveSaiyAccountList(ctx, new SaiyAccountList(accountList));\n        }\n\n        return true;\n    }\n\n    /**\n     * Asynchronously remove any remote profile. If for some reason there are multiple profiles,\n     * the requests will queue without blocking the parent loop.\n     *\n     * @param ctx       the application context\n     * @param profileId the remote profile id\n     */\n    private static void removeProfile(@NonNull final Context ctx, @Nullable final String profileId) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"removeProfile: \" + profileId);\n        }\n\n        if (UtilsString.notNaked(profileId)) {\n\n            new Thread() {\n                public void run() {\n\n                    new DeleteIDProfile(ctx, MicrosoftConfiguration.OCP_APIM_KEY_1,\n                            profileId).delete();\n\n                }\n            }.start();\n        }\n    }\n\n\n    /**\n     * As we are storing the user's email address, we'll do it in Base64, just because. If a rouge\n     * application wanted the user's email address, there are easier ways than hacking into our\n     * shared preferences...\n     *\n     * @param accountList the {@link SaiyAccountList} object\n     * @return the updated {@link SaiyAccountList}\n     */\n    private static SaiyAccountList encode(@NonNull final SaiyAccountList accountList) {\n\n        try {\n\n            for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n                account.setAccountName(Base64.encodeToString(account.getAccountName().getBytes(\n                        Constants.ENCODING_UTF8), Base64.NO_WRAP));\n            }\n\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"encode UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        }\n\n        return accountList;\n    }\n\n    /**\n     * Decode the user's email address from Base64\n     *\n     * @param accountList the {@link SaiyAccountList} object\n     * @return the updated {@link SaiyAccountList}\n     */\n    private static SaiyAccountList decode(@NonNull final SaiyAccountList accountList) {\n\n        try {\n\n            for (final SaiyAccount account : accountList.getSaiyAccountList()) {\n                account.setAccountName(new String(Base64.decode(account.getAccountName(), Base64.NO_WRAP),\n                        Constants.ENCODING_UTF8));\n            }\n\n        } catch (final UnsupportedEncodingException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"encode UnsupportedEncodingException\");\n                e.printStackTrace();\n            }\n        }\n\n        return accountList;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/user/SaiyAccountList.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.user;\n\nimport java.util.List;\n\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 09/09/2016.\n */\n\npublic class SaiyAccountList {\n\n    private final List<SaiyAccount> saiyAccountList;\n\n    public SaiyAccountList(final List<SaiyAccount> saiyAccountList) {\n        this.saiyAccountList = saiyAccountList;\n    }\n\n    public List<SaiyAccount> getSaiyAccountList() {\n        return saiyAccountList;\n    }\n\n    public int size() {\n        if (UtilsList.notNaked(saiyAccountList)) {\n            return saiyAccountList.size();\n        } else {\n            return 0;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/Conditions/Network.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils.Conditions;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.WorkerThread;\nimport android.telephony.TelephonyManager;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.SocketTimeoutException;\nimport java.net.URL;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport ai.saiy.android.command.helper.CC;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.SPH;\n\n/**\n * Class to check the device's network connectivity and speed. This is made of up of code samples\n * collected from many places, but particularly from the author below.\n * <p>\n * Created by benrandall76@gmail.com on 20/03/2016.\n *\n * @author emil http://stackoverflow.com/users/220710/emil\n */\npublic final class Network {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = Network.class.getSimpleName();\n\n    private static final String PING_URL = \"http://connectivitycheck.gstatic.com/generate_204\";\n    private static final String USER_AGENT = \"User-Agent\";\n    private static final String USER_AGENT_ANDROID = \"Android\";\n    private static final String CONNECTION = \"Connection\";\n    private static final String CLOSE = \"close\";\n    public static final int PING_TIMEOUT = 2000;\n\n    private static final int CONNECTION_TYPE_UNKNOWN = 0;\n    private static final int CONNECTION_TYPE_2G = 1;\n    public static final int CONNECTION_TYPE_3G = 2;\n    private static final int CONNECTION_TYPE_4G = 98;\n    private static final int CONNECTION_TYPE_WIFI = 99;\n\n    private static final Object lock = new Object();\n\n    /**\n     * Prevent instantiation\n     */\n    public Network() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static boolean isNetworkAvailable(final Context ctx) {\n        final NetworkInfo activeNetworkInfo = getActiveNetworkInfo(ctx);\n        return activeNetworkInfo != null && activeNetworkInfo.isConnected();\n    }\n\n    /**\n     * Check if there is any connectivity to a WiFi network\n     *\n     * @param ctx the application Context\n     * @return true if the device is connected to WiFi\n     */\n    public static boolean isNetworkWiFi(final Context ctx) {\n        final NetworkInfo info = getActiveNetworkInfo(ctx);\n\n        return info != null && (info.getType() == ConnectivityManager.TYPE_WIFI\n                || info.getType() == ConnectivityManager.TYPE_ETHERNET);\n\n    }\n\n    /**\n     * Check if the current connection type and speed is above those required by the current action,\n     * as well as any user defaults.\n     *\n     * @param ctx the application Context\n     * @return true if the connection is sufficient\n     */\n    public static boolean connectivityPass(final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"connectivityPass\");\n        }\n\n        if (isNetworkAvailable(ctx)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"connectivityPass: isConnected: true\");\n            }\n\n            final int connectionMinimum = SPH.getConnectionMinimum(ctx);\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"connectionMinimum: \" + connectionMinimum);\n            }\n\n            final int connectionType = getConnectionType(ctx);\n\n            if (DEBUG) {\n                switch (connectionType) {\n\n                    case CONNECTION_TYPE_WIFI:\n                        MyLog.i(CLS_NAME, \"CONNECTION_TYPE_WIFI\");\n                        break;\n                    case CONNECTION_TYPE_4G:\n                        MyLog.i(CLS_NAME, \"CONNECTION_TYPE_4G\");\n                        break;\n                    case CONNECTION_TYPE_3G:\n                        MyLog.i(CLS_NAME, \"CONNECTION_TYPE_3G\");\n                        break;\n                    case CONNECTION_TYPE_2G:\n                        MyLog.i(CLS_NAME, \"CONNECTION_TYPE_2G\");\n                        break;\n                    case CONNECTION_TYPE_UNKNOWN:\n                    default:\n                        MyLog.i(CLS_NAME, \"CONNECTION_TYPE_UNKNOWN\");\n                        break;\n                }\n            }\n\n            return connectionType >= connectionMinimum;\n\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"connectivityPass: isConnected: false\");\n            }\n\n            return false;\n        }\n    }\n\n    /**\n     * Get the current connection type from the {@link ConnectivityManager}\n     *\n     * @param ctx the application Context\n     * @return the integer constant of the connection type\n     */\n    public static int getConnectionType(final Context ctx) {\n        final NetworkInfo info = getActiveNetworkInfo(ctx);\n        final int infoType = info.getType();\n\n        switch (infoType) {\n\n            case ConnectivityManager.TYPE_WIFI:\n            case ConnectivityManager.TYPE_ETHERNET:\n            case ConnectivityManager.TYPE_BLUETOOTH:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_WIFI\");\n                }\n                return CONNECTION_TYPE_WIFI;\n\n            case ConnectivityManager.TYPE_WIMAX:\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_4G\");\n                }\n                return CONNECTION_TYPE_4G;\n\n            case ConnectivityManager.TYPE_MOBILE:\n\n                final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);\n                final int networkType = tm.getNetworkType();\n\n                switch (networkType) {\n\n                    case TelephonyManager.NETWORK_TYPE_GPRS: // ~ 100 kbps\n                    case TelephonyManager.NETWORK_TYPE_EDGE: // ~ 50-100 kbps\n                    case TelephonyManager.NETWORK_TYPE_CDMA: // ~ 14-64 kbps\n                    case TelephonyManager.NETWORK_TYPE_1xRTT: // ~ 50-100 kbps\n                    case TelephonyManager.NETWORK_TYPE_IDEN: // ~25 kbps\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_2G\");\n                        }\n                        return CONNECTION_TYPE_2G;\n\n                    case TelephonyManager.NETWORK_TYPE_UMTS: // ~ 400-7000 kbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_0: // ~ 400-1000 kbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_A: // ~ 600-1400 kbps\n                    case TelephonyManager.NETWORK_TYPE_HSDPA: // ~ 2-14 Mbps\n                    case TelephonyManager.NETWORK_TYPE_HSUPA: // ~ 1-23 Mbps\n                    case TelephonyManager.NETWORK_TYPE_HSPA: // ~ 700-1700 kbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_B: // ~ 5 Mbps\n                    case TelephonyManager.NETWORK_TYPE_EHRPD: // ~ 1-2 Mbps\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_3G\");\n                        }\n                        return CONNECTION_TYPE_3G;\n\n                    case TelephonyManager.NETWORK_TYPE_LTE: // ~ 10+ Mbps\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_4G\");\n                        }\n                        return CONNECTION_TYPE_4G;\n                    case TelephonyManager.NETWORK_TYPE_HSPAP: // ~ 10-20 Mbps\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_4G\");\n                        }\n                        return CONNECTION_TYPE_4G;\n\n                    default:\n                        if (DEBUG) {\n                            MyLog.w(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_UNKNOWN\");\n                        }\n                        return CONNECTION_TYPE_UNKNOWN;\n\n                }\n\n            default:\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getConnectionType: CONNECTION_TYPE_UNKNOWN\");\n                }\n                return CONNECTION_TYPE_UNKNOWN;\n        }\n    }\n\n    /**\n     * Is the device 4g capable\n     *\n     * @param ctx the application Context\n     * @return true if the device has the capability\n     */\n    @SuppressWarnings(\"deprecation\")\n    public static boolean isFourGCapable(final Context ctx) {\n\n        final ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);\n\n        final NetworkInfo[] info = cm.getAllNetworkInfo();\n\n        for (int i = 0; i < info.length - 1; i++) {\n\n            if (info[i].getType() == ConnectivityManager.TYPE_WIMAX) {\n                return true;\n            }\n\n        }\n\n        return false;\n    }\n\n    /**\n     * Get the network info\n     *\n     * @param ctx the application Context\n     * @return the {@link NetworkInfo}\n     */\n    public static NetworkInfo getActiveNetworkInfo(final Context ctx) {\n        final ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);\n        return cm.getActiveNetworkInfo();\n    }\n\n    /**\n     * Check if there is any connectivity\n     *\n     * @param ctx the application Context\n     * @return true if the device currently has a connection\n     */\n    public static boolean isConnected(final Context ctx) {\n        final NetworkInfo info = getActiveNetworkInfo(ctx);\n        return (info != null && info.isConnected());\n    }\n\n    /**\n     * Check if there is any connectivity to a mobile network\n     *\n     * @param ctx the application Context\n     * @return true if the device is connected to a mobile network\n     */\n    public static boolean isConnectedMobile(final Context ctx) {\n        final NetworkInfo info = getActiveNetworkInfo(ctx);\n        return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE);\n    }\n\n    /**\n     * Check if there is fast connectivity\n     *\n     * @param ctx the application Context\n     * @return true if the connection is 3g or above\n     */\n    public static boolean isConnectionFast(final Context ctx) {\n        final NetworkInfo info = getActiveNetworkInfo(ctx);\n        final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);\n        return (info != null && info.isConnected() && isConnectionFast(info.getType(), tm.getNetworkType()));\n    }\n\n    /**\n     * Check if the connection is fast\n     *\n     * @param type    of connection\n     * @param subType of connection\n     * @return true if the connection is 3g or above\n     */\n    public static boolean isConnectionFast(final int type, final int subType) {\n\n        switch (type) {\n\n            case ConnectivityManager.TYPE_WIFI:\n                return true;\n\n            case ConnectivityManager.TYPE_WIMAX:\n                return true;\n\n            case ConnectivityManager.TYPE_MOBILE:\n\n                switch (subType) {\n\n                    case TelephonyManager.NETWORK_TYPE_1xRTT:\n                        return false; // ~ 50-100 kbps\n                    case TelephonyManager.NETWORK_TYPE_CDMA:\n                        return false; // ~ 14-64 kbps\n                    case TelephonyManager.NETWORK_TYPE_EDGE:\n                        return false; // ~ 50-100 kbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_0:\n                        return true; // ~ 400-1000 kbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_A:\n                        return true; // ~ 600-1400 kbps\n                    case TelephonyManager.NETWORK_TYPE_GPRS:\n                        return false; // ~ 100 kbps\n                    case TelephonyManager.NETWORK_TYPE_HSDPA:\n                        return true; // ~ 2-14 Mbps\n                    case TelephonyManager.NETWORK_TYPE_HSPA:\n                        return true; // ~ 700-1700 kbps\n                    case TelephonyManager.NETWORK_TYPE_HSUPA:\n                        return true; // ~ 1-23 Mbps\n                    case TelephonyManager.NETWORK_TYPE_UMTS:\n                        return true; // ~ 400-7000 kbps\n                    case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11\n                        return true; // ~ 1-2 Mbps\n                    case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9\n                        return true; // ~ 5 Mbps\n                    case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13\n                        return true; // ~ 10-20 Mbps\n                    case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8\n                        return false; // ~25 kbps\n                    case TelephonyManager.NETWORK_TYPE_LTE: // API level 11\n                        return true; // ~ 10+ Mbps\n                    // Unknown\n                    case TelephonyManager.NETWORK_TYPE_UNKNOWN:\n\n                    default:\n                        return false;\n                }\n\n            default:\n                return false;\n\n        }\n    }\n\n    /**\n     * Check the conditions are suitable to request a Text to Speech network synthesised voice\n     *\n     * @param ctx the application context\n     * @return true if the network conditions are suitable, false otherwise\n     */\n    public static boolean shouldTTSNetwork(@NonNull final Context ctx) {\n        return SPH.getNetworkSynthesis(ctx) && Network.connectivityPass(ctx)\n                && (!SPH.getPingCheck(ctx) || pingSuccessSynchronous(ctx));\n    }\n\n    /**\n     * Perform a synchronous ping to check the network connection is actually connected. Although\n     * this will lock the main thread, it's not called from a UI 'environment' and is short-lived\n     * enough not to cause an issue.\n     *\n     * @param ctx the application context\n     * @return true if the connection responded with the established timeout, false otherwise\n     */\n    private static boolean pingSuccessSynchronous(@NonNull final Context ctx) {\n\n        synchronized (lock) {\n\n            final AtomicBoolean success = new AtomicBoolean(false);\n\n            new Thread() {\n                public void run() {\n\n                    HttpURLConnection urlConnection = null;\n\n                    final int timeout = SPH.getPingTimeout(ctx);\n\n                    try {\n                        urlConnection = (HttpURLConnection) (new URL(PING_URL).openConnection());\n                        urlConnection.setRequestProperty(USER_AGENT, USER_AGENT_ANDROID);\n                        urlConnection.addRequestProperty(CONNECTION, CLOSE);\n                        urlConnection.setConnectTimeout(timeout);\n                        urlConnection.setReadTimeout(timeout);\n                        urlConnection.connect();\n                        success.set(urlConnection.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT\n                                && urlConnection.getContentLength() == 0);\n                    } catch (final SocketTimeoutException e) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"pingSuccess SocketTimeoutException\");\n                        }\n                    } catch (final IOException e) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"pingSuccess IOException\");\n                        }\n                    } catch (final Exception e) {\n                        if (DEBUG) {\n                            MyLog.e(CLS_NAME, \"pingSuccess Exception\");\n                        }\n                    } finally {\n                        if (urlConnection != null) {\n                            try {\n                                urlConnection.disconnect();\n                            } catch (final Exception e) {\n                                if (DEBUG) {\n                                    MyLog.e(CLS_NAME, \"pingSuccess disconnect Exception\");\n                                }\n                            }\n                        }\n                        synchronized (lock) {\n                            lock.notifyAll();\n                        }\n                    }\n                }\n            }.start();\n\n            try {\n                lock.wait();\n            } catch (final InterruptedException e) {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"InterruptedException\");\n                    e.printStackTrace();\n                }\n            }\n\n            return success.get();\n        }\n    }\n\n\n    /**\n     * Perform a ping to check the network connection is actually connected.\n     *\n     * @param ctx the application context\n     * @return true if the connection responded with the established timeout, false otherwise\n     */\n    @WorkerThread\n    private static boolean pingSuccess(@NonNull final Context ctx) {\n\n        HttpURLConnection urlConnection = null;\n\n        final int timeout = SPH.getPingTimeout(ctx);\n\n        try {\n            urlConnection = (HttpURLConnection) (new URL(PING_URL).openConnection());\n            urlConnection.setRequestProperty(USER_AGENT, USER_AGENT_ANDROID);\n            urlConnection.addRequestProperty(CONNECTION, CLOSE);\n            urlConnection.setConnectTimeout(timeout);\n            urlConnection.setReadTimeout(timeout);\n            urlConnection.connect();\n            return (urlConnection.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT\n                    && urlConnection.getContentLength() == 0);\n        } catch (final SocketTimeoutException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"pingSuccess SocketTimeoutException\");\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"pingSuccess IOException\");\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"pingSuccess Exception\");\n            }\n        } finally {\n\n            if (urlConnection != null) {\n                try {\n                    urlConnection.disconnect();\n                } catch (final Exception e) {\n                    if (DEBUG) {\n                        MyLog.e(CLS_NAME, \"pingSuccess disconnect Exception\");\n                    }\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Check if the {@link CC} we are about to perform requires a network connection. If it does, either check a network\n     * connection is provisionally available or if the user preferences confirm, actually perform a 'ping' to confirm\n     * the network is reachable.\n     * <p>\n     * // TODO - I have a bad feeling about this. Preventing any network commands processing on the basis of this\n     * // TODO - network response is a major concern. But then again, if the Google Network goes down, won't the\n     * // TODO - world be post-apocalyptic anyway?\n     *\n     * @param ctx the application context\n     * @param cc  the {@link CC} that is about to be performed\n     * @return true if the network parameters pass, false otherwise.\n     */\n    public static boolean networkProceed(@NonNull final Context ctx, @NonNull final CC cc) {\n\n        if (CC.requiresNetwork(cc)) {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"command requires network: true\");\n            }\n\n            if (SPH.getPingCheck(ctx)) {\n                if (isNetworkAvailable(ctx)) {\n                    if (Network.pingSuccess(ctx)) {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"pingSuccess: true\");\n                        }\n                        return true;\n                    } else {\n                        if (DEBUG) {\n                            MyLog.i(CLS_NAME, \"pingSuccess: false\");\n                        }\n                    }\n                } else {\n                    if (DEBUG) {\n                        MyLog.w(CLS_NAME, \"pingSuccess: isConnected: false\");\n                    }\n                }\n            } else {\n\n                if (Network.isNetworkAvailable(ctx)) {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"isNetworkAvailable: true\");\n                    }\n                    return true;\n                } else {\n                    if (DEBUG) {\n                        MyLog.i(CLS_NAME, \"isNetworkAvailable: false\");\n                    }\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.i(CLS_NAME, \"command requires network: false\");\n            }\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/Constants.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.res.Resources;\n\n/**\n * A collection of constants that are not suitable to store in a resource file.\n * <p/>\n * Created by benrandall76@gmail.com on 10/02/2016.\n */\npublic final class Constants {\n\n    /**\n     * Prevent instantiation\n     */\n    public Constants() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static final String SAIY = \"Saiy\";\n    public static final String SAIY_WEB_URL = \"http://saiy.ai\";\n    public static final String SAIY_PRIVACY_URL = \"http://saiy.ai/privacy.html\";\n    public static final String SAIY_TOU_URL = \"http://saiy.ai/terms.html\";\n    public static final String SAIY_ENQUIRIES_EMAIL = \"commercial@saiy.ai\";\n    public static final String SAIY_FEEDBACK_EMAIL = \"feedback@saiy.ai\";\n    public static final String SAIY_GITHUB_URL = \"https://github.com/brandall76/saiy\";\n    public static final String SAIY_TWITTER_HANDLE = \"http://twitter.com/brandall76\";\n    public static final String SAIY_GOOGLE_PLUS_URL = \"https://plus.google.com/100131487913427971091\";\n    public static final String SAIY_XDA_URL = \"http://forum.xda-developers.com/showthread.php?t=1508195\";\n\n    public static final String DEFAULT_FILE_PREFIX = \"default_file\";\n    public static final String DEFAULT_AUDIO_FILE_SUFFIX = \"wav\";\n    public static final String DEFAULT_AUDIO_FILE_PREFIX = \"default_audio_file\";\n    public static final String DEFAULT_TEMP_FILE_SUFFIX = \"txt\";\n    public static final String DEFAULT_TEMP_FILE_PREFIX = \"default_temp_file\";\n    public static final String OGG_AUDIO_FILE_SUFFIX = \"ogg\";\n\n    public static final String ENCODING_UTF8 = \"UTF-8\";\n    public static final String HTTP_GET = \"GET\";\n    public static final String HTTP_POST = \"POST\";\n\n    public static final String STUDIO_AHREMARK_WEB_URL = \"http://www.studioahremark.com/\";\n    public static final String SCHLAPA_WEB_URL = \"https://schlapa.net\";\n\n    public static final String LICENSE_URL_GOOGLE_PLAY_SERVICES = \"https://developers.google.com/android/guides/overview\";\n    public static final String LICENSE_URL_GSON = \"https://github.com/google/gson\";\n    public static final String LICENSE_URL_VOLLEY = \"https://github.com/mcxiaoke/android-volley\";\n    public static final String LICENSE_URL_TWO_FORTY_FOUR = \"https://github.com/twofortyfouram/android-plugin-api-for-locale\";\n    public static final String LICENSE_URL_KAAREL_KALJURAND = \"https://github.com/Kaljurand/speechutils\";\n    public static final String LICENSE_URL_MICROSOFT_TRANSLATOR = \"https://github.com/boatmeme/microsoft-translator-java-api\";\n    public static final String LICENSE_URL_APACHE_COMMONS = \"https://commons.apache.org\";\n    public static final String LICENSE_URL_SIMMETRICS = \"https://github.com/Simmetrics/simmetrics\";\n    public static final String LICENSE_URL_NUANCE_SPEECHKIT = \"https://github.com/NuanceDev/speechkit-android\";\n    public static final String LICENSE_URL_GUAVA = \"https://github.com/google/guava\";\n    public static final String LICENSE_URL_MICROSOFT_COGNITIVE = \"https://www.microsoft.com/cognitive-services\";\n    public static final String LICENSE_URL_API_AI = \"https://github.com/api-ai/api-ai-android-sdk\";\n    public static final String LICENSE_URL_SIMPLE_XML = \"https://github.com/ngallagher/simplexml\";\n    public static final String LICENSE_MATERIAL_ICONS = \"https://github.com/Templarian/MaterialDesign\";\n    public static final String LICENSE_MATERIAL_DIALOGS = \"https://github.com/afollestad/material-dialogs\";\n    public static final String LICENSE_POCKETSPHINX = \"https://github.com/cmusphinx/pocketsphinx-android\";\n    public static final String LICENSE_SOUND_BIBLE = \"http://soundbible.com\";\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/Global.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.support.multidex.MultiDex;\nimport android.support.multidex.MultiDexApplication;\n\nimport ai.saiy.android.applications.Install;\n\nimport static ai.saiy.android.applications.Install.Location.PLAYSTORE;\n\n/**\n * Helper Class to deal with application wide variables. Use with caution as the expected persistent\n * behaviour is not always consistent.\n * <p/>\n * Created by benrandall76@gmail.com on 23/02/2016.\n */\npublic class Global extends MultiDexApplication {\n\n    public static final Install.Location installLocation = PLAYSTORE;\n\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        MultiDex.install(this);\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/MyLog.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\nimport android.util.Log;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Simple wrapper Class for debug output used across the application.\n * <p>\n * Created by benrandall76@gmail.com on 06/02/2016.\n */\npublic final class MyLog {\n\n    /**\n     * Prevent instantiation\n     */\n    public MyLog() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /* Set to false in production. All Classes check this */\n    public static boolean DEBUG = true;\n\n    private static final String TAG = \"SAIY\";\n\n    public static final String DO_DEBUG = \"DEBUG:\";\n\n    public static void d(@NonNull final String clsName, @NonNull final String message) {\n        Log.d(TAG, clsName + \": \" + message);\n    }\n\n    public static void v(@NonNull final String clsName, @NonNull final String message) {\n        Log.v(TAG, clsName + \": \" + message);\n    }\n\n    public static void i(@NonNull final String clsName, @NonNull final String message) {\n        Log.i(TAG, clsName + \": \" + message);\n    }\n\n    public static void w(@NonNull final String clsName, @NonNull final String message) {\n        Log.w(TAG, clsName + \": \" + message);\n    }\n\n    public static void e(@NonNull final String clsName, @NonNull final String message) {\n        Log.e(TAG, clsName + \": \" + message);\n    }\n\n    /**\n     * Method to time the completion of code sections.\n     *\n     * @param clsName the class name identifier\n     * @param then    the start time\n     * @return the elapsed time in milliseconds.\n     */\n    public static long getElapsed(@NonNull final String clsName, final long then) {\n\n        final long now = System.nanoTime();\n        final long elapsed = now - then;\n\n        if (DEBUG) {\n            Log.d(TAG, clsName + \": elapsed: \"\n                    + TimeUnit.MILLISECONDS.convert(elapsed, TimeUnit.NANOSECONDS));\n        }\n\n        return elapsed;\n    }\n\n    /**\n     * Method to time the completion of code sections.\n     *\n     * @param clsName    the class name identifier\n     * @param additional an additional identifier\n     * @param then       the start time\n     * @return the elapsed time in milliseconds.\n     */\n    public static long getElapsed(@NonNull final String clsName, @NonNull final String additional, final long then) {\n\n        final long now = System.nanoTime();\n        final long elapsed = now - then;\n\n        if (DEBUG) {\n            Log.d(TAG, clsName + \": \" + additional + \": elapsed: \"\n                    + TimeUnit.MILLISECONDS.convert(elapsed, TimeUnit.NANOSECONDS));\n        }\n\n        return elapsed;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/SPH.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.util.Locale;\n\nimport ai.saiy.android.algorithms.Algorithm;\nimport ai.saiy.android.algorithms.distance.jarowinkler.JaroWinklerHelper;\nimport ai.saiy.android.algorithms.distance.levenshtein.LevenshteinHelper;\nimport ai.saiy.android.algorithms.fuzzy.FuzzyHelper;\nimport ai.saiy.android.algorithms.mongeelkan.MongeElkanHelper;\nimport ai.saiy.android.algorithms.needlemanwunch.NeedlemanWunschHelper;\nimport ai.saiy.android.algorithms.soundex.SoundexHelper;\nimport ai.saiy.android.api.SaiyDefaults;\nimport ai.saiy.android.api.request.SaiyRequestParams;\nimport ai.saiy.android.applications.Installed;\nimport ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.BVCredentials;\nimport ai.saiy.android.cognitive.motion.provider.google.Motion;\nimport ai.saiy.android.command.battery.BatteryInformation;\nimport ai.saiy.android.command.translate.provider.TranslationProvider;\nimport ai.saiy.android.command.unknown.Unknown;\nimport ai.saiy.android.database.DBSpeech;\nimport ai.saiy.android.defaults.songrecognition.SongRecognitionProvider;\nimport ai.saiy.android.memory.Memory;\nimport ai.saiy.android.recognition.provider.android.RecognitionNative;\nimport ai.saiy.android.service.SelfAware;\nimport ai.saiy.android.service.helper.SelfAwareConditions;\nimport ai.saiy.android.tts.attributes.Gender;\nimport ai.saiy.android.utils.Conditions.Network;\n\n/**\n * Created by benrandall76@gmail.com on 07/02/2016.\n * <p/>\n * A helper class (Shared Preference Helper (SPH)) to access the user's preferences and retain\n * certain interaction information.\n * <p/>\n * The static nature of these global user variables does not appear to be a performance issue\n * and provides ease of access.\n */\npublic class SPH {\n\n    private static final String SAIY_PREF = \"saiyPref\";\n\n    private static final int ZERO = 0;\n    private static final int ONE = 1;\n\n    private static final String DEFAULT_RECOGNITION = \"default_recognition\";\n    private static final String DEFAULT_LANGUAGE_MODEL = \"default_language_model\";\n    private static final String DEFAULT_TTS = \"default_tts\";\n    private static final String DEFAULT_TTS_GENDER = \"default_tts_gender\";\n    private static final String DEFAULT_TEMPERATURE_UNITS = \"default_temperature_units\";\n    private static final String DEFAULT_RINGER = \"default_ringer\";\n    private static final String INACTIVITY_TIMEOUT = \"inactivity_timeout\";\n    private static final String VR_LOCALE = \"vr_locale\";\n    private static final String TTS_LOCALE = \"tts_locale\";\n    private static final String TTS_VOLUME = \"tts_volume\";\n    private static final String CUSTOM_INTRO = \"custom_intro\";\n    private static final String CUSTOM_INTRO_RANDOM = \"custom_intro_random\";\n    private static final String DEFAULT_TTS_VOICE = \"default_tts_voice\";\n    private static final String SPELL_COMMAND_VERBOSE = \"spell_command_verbose\";\n    private static final String EMOTION_COMMAND_VERBOSE = \"emotion_command_verbose\";\n    private static final String TRANSLATE_COMMAND_VERBOSE = \"translate_command_verbose\";\n    private static final String EMOTION_PERMISSION = \"emotion_permission\";\n    private static final String VIBRATE = \"vibrate\";\n    private static final String NETWORK_SYNTHESIS = \"network_synthesis\";\n    private static final String USE_OFFLINE = \"use_offline\";\n    private static final String PING_CHECK = \"ping_check\";\n    private static final String COMMAND_UNKNOWN_ACTION = \"command_unknown_action\";\n    private static final String INTERCEPT_GOOGLE_NOW = \"intercept_google_now\";\n    private static final String PING_TIMEOUT = \"ping_timeout\";\n    private static final String CONNECTION_MINIMUM = \"connection_minimum\";\n    private static final String USER_NAME = \"user_name\";\n    private static final String HOTWORD = \"hotword\";\n    private static final String HOTWORD_BOOT = \"hotword_boot\";\n    private static final String HOTWORD_DRIVING = \"hotword_driving\";\n    private static final String HOTWORD_WAKELOCK = \"hotword_wakelock\";\n    private static final String HOTWORD_SECURE = \"hotword_secure\";\n    private static final String BOOT_START = \"boot_start\";\n    private static final String SELF_AWARE_ENABLED = \"self_aware_enabled\";\n    private static final String ENROLLMENT_VERBOSE = \"enrollment_verbose\";\n    private static final String DISCLAIMER = \"disclaimer\";\n    private static final String WHATS_NEW = \"whats_new\";\n    private static final String DEVELOPER_NOTE = \"developer_note\";\n    private static final String PAUSE_TIMEOUT = \"pause_timeout\";\n    private static final String BING_TOKEN = \"bing_token\";\n    private static final String BEYOND_VERBAL_AUTH_RESPONSE = \"beyond_verbal_auth_response\";\n    private static final String BING_OAUTH_UPDATE = \"bing_oauth_update\";\n    private static final String TRANSLATION_PROVIDER = \"translation_provider\";\n    private static final String SAIY_ACCOUNTS = \"saiy_accounts\";\n    private static final String MEMORY = \"memory\";\n    private static final String EMOTION = \"emotion\";\n    private static final String MOTION = \"motion\";\n    private static final String MOTION_ENABLED = \"motion_enabled\";\n    private static final String TOAST_UNKNOWN = \"toast_unknown\";\n    private static final String BLACKLIST = \"blacklist\";\n    private static final String ALGORITHM = \"algorithm\";\n    private static final String ALGORITHMS = \"algorithms\";\n    private static final String JWD_LOWER_THRESHOLD = \"jwd_lower_threshold\";\n    private static final String JWD_UPPER_THRESHOLD = \"jwd_upper_threshold\";\n    private static final String LEV_UPPER_THRESHOLD = \"lev_upper_threshold\";\n    private static final String ME_UPPER_THRESHOLD = \"me_upper_threshold\";\n    private static final String FUZZY_MULTIPLIER = \"fuzzy_multiplier\";\n    private static final String NW_UPPER_THRESHOLD = \"nw_upper_threshold\";\n    private static final String SOUNDEX_UPPER_THRESHOLD = \"soundex_upper_threshold\";\n    private static final String REMOTE_COMMAND_VERBOSE = \"remote_command_verbose\";\n    private static final String LAST_USED = \"last_used\";\n    private static final String USED_INCREMENT = \"used_increment\";\n    private static final String MAX_SPEECH_CACHE_SIZE = \"max_speech_cache_size\";\n    private static final String DEFAULT_SONG_RECOGNITION = \"default_song_recognition\";\n    private static final String ANNOUNCE_TASKER = \"announce_tasker\";\n    private static final String ANNOUNCE_NOTIFICATIONS = \"announce_notifications\";\n    private static final String RECOGNISER_BUSY_FIX = \"recogniser_busy_fix\";\n    private static final String OKAY_GOOGLE_FIX = \"okay_google_fix\";\n\n    /**\n     * Prevent instantiation\n     */\n    public SPH() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * For convenience\n     *\n     * @param ctx the application context\n     * @return the {@link SharedPreferences} object\n     */\n    private static SharedPreferences getPref(@NonNull final Context ctx) {\n        return ctx.getSharedPreferences(SAIY_PREF, Context.MODE_PRIVATE);\n    }\n\n    /**\n     * For convenience\n     *\n     * @param pref {@link SharedPreferences} object\n     * @return the {@link android.content.SharedPreferences.Editor} object\n     */\n    private static SharedPreferences.Editor getEditor(final SharedPreferences pref) {\n        return pref.edit();\n    }\n\n    ////////////////////////////////////////////////////////////////////////////////\n    //                           START OF METHODS                                 //\n    ////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Get the default recognition provider\n     *\n     * @param ctx the application context\n     * @return the default recognition provider\n     */\n    public static SaiyDefaults.VR getDefaultRecognition(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return SaiyDefaults.getProviderVR(pref.getString(DEFAULT_RECOGNITION, SaiyDefaults.VR.NATIVE.name()));\n    }\n\n    /**\n     * Set the default recognition provider\n     *\n     * @param ctx      the application context\n     * @param provider of the recognition\n     */\n    public static void setDefaultRecognition(@NonNull final Context ctx, final SaiyDefaults.VR provider) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(DEFAULT_RECOGNITION, provider.name());\n        edit.commit();\n    }\n\n    /**\n     * Get the default Language Model\n     *\n     * @param ctx the application context\n     * @return the default language model\n     */\n    public static SaiyDefaults.LanguageModel getDefaultLanguageModel(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return SaiyDefaults.getLanguageModel(pref.getString(DEFAULT_LANGUAGE_MODEL,\n                SaiyDefaults.LanguageModel.LOCAL.name()));\n    }\n\n    /**\n     * Set the default Language Model\n     *\n     * @param ctx   the application context\n     * @param model to apply\n     */\n    public static void setDefaultLanguageModel(@NonNull final Context ctx, final SaiyDefaults.LanguageModel model) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(DEFAULT_LANGUAGE_MODEL, model.name());\n        edit.commit();\n    }\n\n    /**\n     * Get the amount of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     * @return the integer number of times\n     */\n    public static int getEmotionCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(EMOTION_COMMAND_VERBOSE, ZERO);\n    }\n\n    /**\n     * Increment the number of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     */\n    public static void incrementEmotionCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(EMOTION_COMMAND_VERBOSE, (getEmotionCommandVerbose(ctx) + 1));\n        edit.commit();\n    }\n\n    /**\n     * Set whether the Saiy should announce the name of the executed Tasker Task\n     *\n     * @param ctx       the application context\n     * @param condition to apply\n     */\n    public static void setAnnounceTasker(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(ANNOUNCE_TASKER, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether the Saiy should announce the name of the executed Tasker Task\n     *\n     * @param ctx the application context\n     * @return true as the default, or false if the user has disabled this\n     */\n    public static boolean getAnnounceTasker(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(ANNOUNCE_TASKER, true);\n    }\n\n    /**\n     * Get the user preferred Text to Speech volume level\n     *\n     * @param ctx the application context\n     * @return the user preferred volume level\n     */\n    public static int getTTSVolume(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(TTS_VOLUME, ZERO);\n    }\n\n    /**\n     * Set the user preferred Text to Speech volume level\n     *\n     * @param ctx   the application context\n     * @param level of the volume\n     */\n    public static void setTTSVolume(@NonNull final Context ctx, final int level) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(TTS_VOLUME, level);\n        edit.commit();\n    }\n\n    /**\n     * Get the default ringer configuration\n     *\n     * @param ctx the application context\n     * @return the default recognition provider\n     */\n    public static int getDefaultRinger(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(DEFAULT_RINGER, 99);\n    }\n\n    /**\n     * Set the default ringer configuration\n     *\n     * @param ctx           the application context\n     * @param ringerDefault of the recognition\n     */\n    public static void setDefaultRinger(@NonNull final Context ctx, final int ringerDefault) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(DEFAULT_RINGER, ringerDefault);\n        edit.commit();\n    }\n\n    /**\n     * Get the user preferred temperature units\n     *\n     * @param ctx the application context\n     * @return the default units one of {@link BatteryInformation#CELSIUS}\n     * or {@link BatteryInformation#FAHRENHEIT}\n     */\n    public static int getDefaultTemperatureUnits(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(DEFAULT_TEMPERATURE_UNITS, Locale.getDefault() == Locale.US\n                ? BatteryInformation.FAHRENHEIT : BatteryInformation.CELSIUS);\n    }\n\n    /**\n     * Set the user preferred temperature units\n     *\n     * @param ctx   the application context\n     * @param units one of {@link BatteryInformation#CELSIUS}\n     *              or {@link BatteryInformation#FAHRENHEIT}\n     */\n    public static void setDefaultTemperatureUnits(@NonNull final Context ctx, final int units) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(DEFAULT_TEMPERATURE_UNITS, units);\n        edit.commit();\n    }\n\n    /**\n     * Get the user preferred song recognition application\n     *\n     * @param ctx the application context\n     * @return one of {@link SongRecognitionProvider}\n     */\n    public static SongRecognitionProvider getDefaultSongRecognition(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return SongRecognitionProvider.getProvider(pref.getInt(DEFAULT_SONG_RECOGNITION,\n                SongRecognitionProvider.UNKNOWN.ordinal()));\n    }\n\n    /**\n     * Set the user preferred song recognition application\n     *\n     * @param ctx      the application context\n     * @param provider {@link SongRecognitionProvider}\n     */\n    public static void setDefaultSongRecognition(@NonNull final Context ctx,\n                                                 @NonNull final SongRecognitionProvider provider) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(DEFAULT_SONG_RECOGNITION, provider.ordinal());\n        edit.commit();\n    }\n\n    /**\n     * Get the user assigned maximum speech cache size, which they can adjust in the Saiy\n     * Application Settings\n     *\n     * @param ctx the application context\n     * @return the maximum speech cache size\n     */\n    public static long getMaxSpeechCacheSize(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(MAX_SPEECH_CACHE_SIZE, DBSpeech.MAX_CACHE_SIZE);\n    }\n\n    /**\n     * Set the maximum speech cache size\n     *\n     * @param maxSize the maximum cache size\n     * @param ctx     the application context\n     */\n    public static void setMaxSpeechCacheSize(@NonNull final Context ctx, final long maxSize) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putLong(MAX_SPEECH_CACHE_SIZE, maxSize);\n        edit.commit();\n    }\n\n    /**\n     * Get the last time the application was used\n     *\n     * @param ctx the application context\n     * @return the last time the application was used\n     */\n    public static long getLastUsed(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(LAST_USED, (long) ONE);\n    }\n\n    /**\n     * Set the last time the application was used to now.\n     *\n     * @param ctx the application context\n     */\n    public static void setLastUsed(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putLong(LAST_USED, System.currentTimeMillis());\n        edit.commit();\n\n        SPH.incrementUsed(ctx);\n    }\n\n    /**\n     * Get the number of speech requests that have been made, since the application data was last\n     * wiped. This will be used to run various housekeeping tasks.\n     *\n     * @param ctx the application context\n     * @return the count of application uses\n     */\n    public static long getUsedIncrement(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(USED_INCREMENT, ONE);\n    }\n\n    /**\n     * Increment the number of speech requests made\n     *\n     * @param ctx the application context\n     */\n    public static void incrementUsed(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putLong(USED_INCREMENT, (getUsedIncrement(ctx) + 1));\n        edit.commit();\n    }\n\n    /**\n     * Get the amount of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     * @return the integer number of times\n     */\n    public static int getRemoteCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(REMOTE_COMMAND_VERBOSE, ZERO);\n    }\n\n    /**\n     * Increment the number of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     */\n    public static void incrementRemoteCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(REMOTE_COMMAND_VERBOSE, (getRemoteCommandVerbose(ctx) + 1));\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised user defined {@link Algorithm}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static String getAlgorithms(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(ALGORITHMS, null);\n    }\n\n    /**\n     * Set the user defined algorithms\n     *\n     * @param ctx        the application context\n     * @param serialised list of {@link Algorithm}\n     */\n    public static void setAlgorithms(@NonNull final Context ctx, @NonNull final String serialised) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(ALGORITHMS, serialised);\n        edit.commit();\n    }\n\n    /**\n     * Get the upper distance limit to use in {@link SoundexHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getSoundexUpper(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(SOUNDEX_UPPER_THRESHOLD,\n                String.valueOf(Algorithm.SOUNDEX_UPPER_THRESHOLD)));\n    }\n\n    /**\n     * Set the upper distance limit to use in {@link SoundexHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setSoundexUpper(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(SOUNDEX_UPPER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Get the upper distance limit to use in {@link NeedlemanWunschHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getNeedlemanWunschUpper(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(NW_UPPER_THRESHOLD, String.valueOf(Algorithm.NW_UPPER_THRESHOLD)));\n    }\n\n    /**\n     * Set the upper distance limit to use in {@link NeedlemanWunschHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setNeedlemanWunschUpper(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(NW_UPPER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Get the fuzzy multiplier to use in {@link FuzzyHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getFuzzyMultiplier(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(FUZZY_MULTIPLIER,\n                String.valueOf(Algorithm.FUZZY_MULTIPLIER)));\n    }\n\n    /**\n     * Set the fuzzy multiplier to use in {@link FuzzyHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setFuzzyMultiplier(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(FUZZY_MULTIPLIER, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Get the upper distance limit to use in {@link MongeElkanHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getMongeElkanUpper(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(ME_UPPER_THRESHOLD,\n                String.valueOf(Algorithm.ME_UPPER_THRESHOLD)));\n    }\n\n    /**\n     * Set the upper distance limit to use in {@link MongeElkanHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setMongeElkanUpper(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(ME_UPPER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n\n    /**\n     * Get the upper distance limit to use in {@link LevenshteinHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getLevenshteinUpper(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(LEV_UPPER_THRESHOLD,\n                String.valueOf(Algorithm.LEV_UPPER_THRESHOLD)));\n    }\n\n    /**\n     * Set the upper distance limit to use in {@link LevenshteinHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setLevenshteinUpper(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(LEV_UPPER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Get the upper distance limit to use in {@link JaroWinklerHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getJaroWinklerUpper(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(JWD_UPPER_THRESHOLD,\n                String.valueOf(Algorithm.JWD_UPPER_THRESHOLD)));\n    }\n\n    /**\n     * Get the lower distance limit to use in {@link JaroWinklerHelper}\n     *\n     * @param ctx the application context\n     * @return the double or default value\n     */\n    public static double getJaroWinklerLower(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Double.valueOf(pref.getString(JWD_LOWER_THRESHOLD,\n                String.valueOf(Algorithm.JWD_LOWER_THRESHOLD)));\n    }\n\n\n    /**\n     * Set the upper distance limit to use in {@link JaroWinklerHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setJaroWinklerUpper(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(JWD_UPPER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Set the lower distance limit to use in {@link JaroWinklerHelper}\n     *\n     * @param ctx   the application context\n     * @param limit to set\n     */\n    public static void setJaroWinklerLower(@NonNull final Context ctx, final double limit) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(JWD_LOWER_THRESHOLD, String.valueOf(limit));\n        edit.commit();\n    }\n\n    /**\n     * Get the default algorithm provider\n     *\n     * @param ctx the application context\n     * @return the default algorithm provider\n     */\n    public static Algorithm getAlgorithm(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Algorithm.valueOf(pref.getString(ALGORITHM, Algorithm.JARO_WINKLER.name()));\n    }\n\n    /**\n     * Set the default algorithm provider\n     *\n     * @param ctx       the application context\n     * @param algorithm to set as the default\n     */\n    public static void setAlgorithm(@NonNull final Context ctx, final @NonNull Algorithm algorithm) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(ALGORITHM, algorithm.name());\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the array of blacklisted applications which will be coerced into\n     * a {@link ai.saiy.android.api.helper.BlackList} objects using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getBlacklistArray(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(BLACKLIST, null);\n    }\n\n    /**\n     * Set the serialised string of the blacklist array which has been coerced into\n     * a {@link ai.saiy.android.api.helper.BlackList} objects using {@link com.google.gson.Gson}\n     *\n     * @param ctx       the application context\n     * @param blacklist the serialised string\n     */\n    public static void setBlacklistArray(@NonNull final Context ctx, @Nullable final String blacklist) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(BLACKLIST, blacklist);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the last ActivityRecognition which will be coerced into\n     * a {@link Motion} object\n     * using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getMotion(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(MOTION, null);\n    }\n\n    /**\n     * Set the serialised string of the last ActivityRecognition which has been coerced into\n     * a {@link Motion} object\n     * using {@link com.google.gson.Gson}\n     *\n     * @param ctx    the application context\n     * @param motion the serialised string\n     */\n    public static void setMotion(@NonNull final Context ctx, @Nullable final String motion) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(MOTION, motion);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the last emotion analysis which will be coerced into\n     * a {@link ai.saiy.android.cognitive.emotion.provider.beyondverbal.AnalysisResult} object\n     * using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getEmotion(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(EMOTION, null);\n    }\n\n    /**\n     * Set the serialised string of the last emotion analysis which has been coerced into\n     * a {@link ai.saiy.android.cognitive.emotion.provider.beyondverbal.AnalysisResult} object\n     * using {@link com.google.gson.Gson}\n     *\n     * @param ctx     the application context\n     * @param emotion the serialised string\n     */\n    public static void setEmotion(@NonNull final Context ctx, @Nullable final String emotion) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(EMOTION, emotion);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the Saiy accounts which will be coerced into\n     * a {@link ai.saiy.android.user.SaiyAccountList} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getSaiyAccounts(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(SAIY_ACCOUNTS, null);\n    }\n\n    /**\n     * Set the serialised string of the Saiy accounts which has been coerced into\n     * a {@link ai.saiy.android.user.SaiyAccountList} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx         the application context\n     * @param accountList the serialised string\n     */\n    public static void setSaiyAccounts(@NonNull final Context ctx, @Nullable final String accountList) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(SAIY_ACCOUNTS, accountList);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the last action Saiy performed which will be coerced into\n     * a {@link Memory} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getMemory(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(MEMORY, null);\n    }\n\n    /**\n     * Set the serialised string of the last action Saiy performed which has been coerced into\n     * a {@link Memory} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx    the application context\n     * @param memory the serialised string\n     */\n    public static void setMemory(@NonNull final Context ctx, @NonNull final String memory) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(MEMORY, memory);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the user's default Text to Speech Voice which will be coerced into\n     * a {@link android.speech.tts.Voice} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getDefaultTTSVoice(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(DEFAULT_TTS_VOICE, null);\n    }\n\n    /**\n     * Set the serialised string of the user's default Text to Speech Voice which has been coerced into\n     * a {@link android.speech.tts.Voice} object using {@link com.google.gson.Gson}\n     *\n     * @param ctx   the application context\n     * @param voice the serialised string\n     */\n    public static void setDefaultTTSVoice(@NonNull final Context ctx, @Nullable final String voice) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(DEFAULT_TTS_VOICE, voice);\n        edit.commit();\n    }\n\n    /**\n     * Get the default translation provider\n     *\n     * @param ctx the application context\n     * @return the default translation provider\n     */\n    public static int getDefaultTranslationProvider(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(TRANSLATION_PROVIDER, TranslationProvider.TRANSLATION_PROVIDER_GOOGLE);\n    }\n\n    /**\n     * Set the default translation provider\n     *\n     * @param ctx      the application context\n     * @param provider of the translation\n     */\n    public static void setDefaultTranslationProvider(@NonNull final Context ctx, final int provider) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(TRANSLATION_PROVIDER, provider);\n        edit.commit();\n    }\n\n    /**\n     * Set the Bing refresh token timeout\n     *\n     * @param ctx  the application context\n     * @param time the {@link System#currentTimeMillis()}\n     */\n    public static void setBingTokenExpiryTime(@NonNull final Context ctx, final long time) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n        edit.putLong(BING_OAUTH_UPDATE, time);\n        edit.commit();\n    }\n\n    /**\n     * Get the Bing refresh token timeout\n     *\n     * @param ctx the application context\n     * @return the time the token was last refreshed\n     */\n    public static long getBingTokenExpiryTime(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(BING_OAUTH_UPDATE, 0);\n    }\n\n    /**\n     * Get the Bing access token\n     *\n     * @param ctx the application context\n     * @return the Bing access token or an empty String\n     */\n    public static String getBingToken(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(BING_TOKEN, null);\n    }\n\n    /**\n     * Set the Bing access token\n     *\n     * @param ctx   the application context\n     * @param token the Bing access token\n     */\n    public static void setBingToken(@NonNull final Context ctx, @NonNull final String token) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(BING_TOKEN, token);\n        edit.commit();\n    }\n\n    /**\n     * Get the serialised string of the most recent token credentials which will be coerced into\n     * an {@link BVCredentials}\n     * object using {@link com.google.gson.Gson}\n     *\n     * @param ctx the application context\n     * @return the serialised string or null\n     */\n    public static String getBeyondVerbalCredentials(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(BEYOND_VERBAL_AUTH_RESPONSE, null);\n    }\n\n    /**\n     * Set the serialised string of the last token credentials which has been coerced into\n     * a {@link BVCredentials}\n     * object using {@link com.google.gson.Gson}\n     *\n     * @param ctx         the application context\n     * @param credentials the serialised string\n     */\n    public static void setBeyondVerbalCredentials(@NonNull final Context ctx, @NonNull final String credentials) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(BEYOND_VERBAL_AUTH_RESPONSE, credentials);\n        edit.commit();\n    }\n\n    /**\n     * Get the user assigned pause timeout\n     *\n     * @param ctx the application context\n     * @return the pause timeout\n     */\n    public static long getPauseTimeout(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(PAUSE_TIMEOUT, RecognitionNative.PAUSE_TIMEOUT);\n    }\n\n    /**\n     * Set the user assigned pause timeout\n     *\n     * @param ctx     the application context\n     * @param timeout to assign\n     */\n    public static void setPauseTimeout(@NonNull final Context ctx, final Long timeout) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putLong(PAUSE_TIMEOUT, timeout);\n        edit.commit();\n    }\n\n    /**\n     * Set that the user has seen the what's new note.\n     *\n     * @param ctx the application context\n     */\n    public static void setWhatsNew(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(WHATS_NEW, true);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not the user has seen the what's new note.\n     *\n     * @param ctx the application context\n     * @return true if the user has seen the what's new note, false otherwise\n     */\n    public static boolean getWhatsNew(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(WHATS_NEW, false);\n    }\n\n    /**\n     * Set that the user has already heard the verbose enrollment explanation.\n     *\n     * @param ctx the application context\n     */\n    public static void setEnrollmentVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(ENROLLMENT_VERBOSE, true);\n        edit.commit();\n    }\n\n    /**\n     * Get if the user has already heard the verbose enrollment explanation.\n     *\n     * @param ctx the application context\n     * @return true if the user has heard the verbose explanation, false otherwise\n     */\n    public static boolean getEnrollmentVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(ENROLLMENT_VERBOSE, false);\n    }\n\n    /**\n     * Set that the user has seen the developer note.\n     *\n     * @param ctx the application context\n     */\n    public static void setDeveloperNote(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(DEVELOPER_NOTE, true);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not the user has seen the developer note.\n     *\n     * @param ctx the application context\n     * @return true if the user has seen the developer note, false otherwise\n     */\n    public static boolean getDeveloperNote(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(DEVELOPER_NOTE, false);\n    }\n\n    /**\n     * Set that the user has accepted the disclaimer.\n     *\n     * @param ctx the application context\n     */\n    public static void setAcceptedDisclaimer(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(DISCLAIMER, true);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not the user has accepted the disclaimer.\n     *\n     * @param ctx the application context\n     * @return true if the disclaimer has been accepted, false otherwise\n     */\n    public static boolean getAcceptedDisclaimer(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(DISCLAIMER, false);\n    }\n\n    /**\n     * Set whether or not to toast unknown commands.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied.\n     */\n    public static void setToastUnknown(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(TOAST_UNKNOWN, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not to toast unknown commands.\n     *\n     * @param ctx the application context\n     * @return true if commands should be toasted, false otherwise\n     */\n    public static boolean getToastUnknown(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(TOAST_UNKNOWN, true);\n    }\n\n    /**\n     * Set whether or not to track the user's motion.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied.\n     */\n    public static void setMotionEnabled(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(MOTION_ENABLED, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not to track the user's motion\n     *\n     * @param ctx the application context\n     * @return true if motion should be tracked, false otherwise\n     */\n    public static boolean getMotionEnabled(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(MOTION_ENABLED, true);\n    }\n\n    /**\n     * Set whether or not to start the {@link SelfAware} service is enabled by the user.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied.\n     */\n    public static void setSelfAwareEnabled(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(SELF_AWARE_ENABLED, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether or not to start the {@link SelfAware} service is enabled by the user.\n     *\n     * @param ctx the application context\n     * @return true if the {@link SelfAware} should be enabled, false otherwise\n     */\n    public static boolean getSelfAwareEnabled(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(SELF_AWARE_ENABLED, true);\n    }\n\n    /**\n     * Set whether or not to start the {@link SelfAware} service at boot.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied.\n     */\n    public static void setStartAtBoot(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(BOOT_START, condition);\n        edit.commit();\n    }\n\n    /**\n     * Check if the {@link SelfAware} should start at boot\n     *\n     * @param ctx the application context\n     * @return true if the {@link SelfAware} should start at boot, false otherwise\n     */\n    public static boolean getStartAtBoot(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(BOOT_START, true);\n    }\n\n    /**\n     * Get the default Text to Speech provider\n     *\n     * @param ctx the application context\n     * @return the default Text to Speech provider\n     */\n    public static SaiyDefaults.TTS getDefaultTTS(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return SaiyDefaults.getProviderTTS(pref.getString(DEFAULT_TTS, SaiyDefaults.TTS.LOCAL.name()));\n    }\n\n    /**\n     * Set the default Text to Speech provider\n     *\n     * @param ctx      the application context\n     * @param provider of the Text to Speech\n     */\n    public static void setDefaultTTS(@NonNull final Context ctx, @NonNull final SaiyDefaults.TTS provider) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(DEFAULT_TTS, provider.name());\n        edit.commit();\n    }\n\n    /**\n     * Get the user preferred Text to Speech voice gender\n     *\n     * @param ctx the application context\n     * @return one of {@link Gender#FEMALE} or {@link Gender#MALE}\n     */\n    public static Gender getDefaultTTSGender(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return Gender.getGender(pref.getString(DEFAULT_TTS_GENDER, Gender.FEMALE.name()));\n    }\n\n    /**\n     * Set the user preferred Text to Speech voice gender\n     *\n     * @param ctx    the application context\n     * @param gender one of {@link Gender#FEMALE} or {@link Gender#MALE}\n     */\n    public static void setDefaultTTSGender(@NonNull final Context ctx, @NonNull final Gender gender) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(DEFAULT_TTS_GENDER, gender.name());\n        edit.commit();\n    }\n\n    /**\n     * Set whether the user wishes their voice data to be subject to emotion analysis\n     *\n     * @param ctx       the application context\n     * @param condition to apply\n     */\n    public static void setEmotionPermission(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(EMOTION_PERMISSION, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether the user wishes their voice data to be subject to emotion analysis\n     *\n     * @param ctx the application context\n     * @return true if they require emotion analysis\n     */\n    public static boolean getEmotionPermission(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(EMOTION_PERMISSION, false);\n    }\n\n    /**\n     * Set the default {@link Locale} of the voice recognition\n     *\n     * @param ctx          the application context\n     * @param nativeLocale to apply as the default.\n     */\n    public static void setVRLocale(@NonNull final Context ctx, @NonNull final Locale nativeLocale) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(VR_LOCALE, nativeLocale.toString());\n        edit.commit();\n    }\n\n    /**\n     * Get the default {@link Locale} of the voice recognition\n     *\n     * @param ctx the application context\n     * @return the default {@link Locale} of the recognition\n     */\n    public static Locale getVRLocale(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return UtilsLocale.stringToLocale(pref.getString(VR_LOCALE, UtilsLocale.DEFAULT_LOCALE_STRING));\n    }\n\n    /**\n     * Set the default {@link Locale} of the Text to Speech engine\n     *\n     * @param ctx          the application context\n     * @param nativeLocale to apply as the default.\n     */\n    public static void setTTSLocale(@NonNull final Context ctx, @NonNull final Locale nativeLocale) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(TTS_LOCALE, nativeLocale.toString());\n        edit.commit();\n    }\n\n    /**\n     * Get the default {@link Locale} of the Text to Speech engine\n     *\n     * @param ctx the application context\n     * @return the default {@link Locale} of the engine\n     */\n    public static Locale getTTSLocale(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return UtilsLocale.stringToLocale(pref.getString(TTS_LOCALE, UtilsLocale.DEFAULT_LOCALE_STRING));\n    }\n\n    /**\n     * Get the user preferred inactivity timeout\n     *\n     * @param ctx the application context\n     * @return the value in milliseconds\n     */\n    public static long getInactivityTimeout(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getLong(INACTIVITY_TIMEOUT, SelfAwareConditions.DEFAULT_INACTIVITY_TIMEOUT);\n    }\n\n    /**\n     * Set the user preferred inactivity timeout, to release memory resources\n     *\n     * @param ctx     the application context\n     * @param timeout in milliseconds\n     */\n    public static void setInactivityTimeout(@NonNull final Context ctx, final long timeout) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putLong(INACTIVITY_TIMEOUT, timeout);\n        edit.commit();\n    }\n\n    /**\n     * Get the amount of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     * @return the integer number of times\n     */\n    public static int getSpellCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(SPELL_COMMAND_VERBOSE, ZERO);\n    }\n\n    /**\n     * Increment the number of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     */\n    public static void incrementSpellCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(SPELL_COMMAND_VERBOSE, (getSpellCommandVerbose(ctx) + 1));\n        edit.commit();\n    }\n\n    /**\n     * Get the amount of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     * @return the integer number of times\n     */\n    public static int getTranslateCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(TRANSLATE_COMMAND_VERBOSE, ZERO);\n    }\n\n    /**\n     * Increment the number of times the user has been informed of this verbose information\n     *\n     * @param ctx the application context\n     */\n    public static void incrementTranslateCommandVerbose(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(TRANSLATE_COMMAND_VERBOSE, (getTranslateCommandVerbose(ctx) + 1));\n        edit.commit();\n    }\n\n    /**\n     * Set the user's preferred vibration condition\n     *\n     * @param ctx       the application context\n     * @param condition to set\n     */\n    public static void setVibrateCondition(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(VIBRATE, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preferred vibration condition\n     *\n     * @param ctx the application context\n     * @return true if the user requires haptic feedback\n     */\n    public static boolean getVibrateCondition(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(VIBRATE, true);\n    }\n\n    /**\n     * Set the user's preference for how long a 'ping' request should be given to timeout.\n     *\n     * @param ctx     the application context\n     * @param timeout in milliseconds to be applied\n     */\n    public static void setPingTimeout(@NonNull final Context ctx, final int timeout) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(PING_TIMEOUT, timeout);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for how long a 'ping' request should be given to timeout.\n     *\n     * @param ctx the application context\n     * @return the timeout in milliseconds\n     */\n    public static int getPingTimeout(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(PING_TIMEOUT, Network.PING_TIMEOUT);\n    }\n\n    /**\n     * Set the user's preference for intercepting the Google Now commands.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setInterceptGoogle(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(INTERCEPT_GOOGLE_NOW, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for intercepting the Google Now commands.\n     *\n     * @param ctx the application context\n     * @return true if Google Now commands should be intercepted, false otherwise\n     */\n    public static boolean getInterceptGoogle(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(INTERCEPT_GOOGLE_NOW, false);\n    }\n\n    /**\n     * Set the user's preference for announcing notifications.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setAnnounceNotifications(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(ANNOUNCE_NOTIFICATIONS, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for announcing notifications.\n     *\n     * @param ctx the application context\n     * @return true if notification content should be announced, false otherwise\n     */\n    public static boolean getAnnounceNotifications(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(ANNOUNCE_NOTIFICATIONS, false);\n    }\n\n    /**\n     * Set the user's preference for pinging the network prior to making a request.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setPingCheck(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(PING_CHECK, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for pinging the network prior to making a request.\n     *\n     * @param ctx the application context\n     * @return true if the network should be pinged, false otherwise\n     */\n    public static boolean getPingCheck(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(PING_CHECK, true);\n    }\n\n    /**\n     * Set the user's preference for hotword wakelock.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setHotwordWakelock(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(HOTWORD_WAKELOCK, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for hotword wakelock.\n     *\n     * @param ctx the application context\n     * @return true if hotword should hold a wakelock\n     */\n    public static boolean getHotwordWakelock(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(HOTWORD_WAKELOCK, false);\n    }\n\n    /**\n     * Set the user's preference for hotword when driving.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setHotwordDriving(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(HOTWORD_DRIVING, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for hotword when driving.\n     *\n     * @param ctx the application context\n     * @return true if hotword should begin when driving\n     */\n    public static boolean getHotwordDriving(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(HOTWORD_DRIVING, false);\n    }\n\n    /**\n     * Set the user's preference for hotword security.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setHotwordSecure(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(HOTWORD_SECURE, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for hotword security.\n     *\n     * @param ctx the application context\n     * @return true if hotword commands should be secure\n     */\n    public static boolean getHotwordSecure(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(HOTWORD_SECURE, true);\n    }\n\n    /**\n     * Set the user's preference for hotword detection starting at boot.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setHotwordBoot(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(HOTWORD_BOOT, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for hotword detection starting at boot.\n     *\n     * @param ctx the application context\n     * @return true if hotword detection should start at boot\n     */\n    public static boolean getHotwordBoot(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(HOTWORD_BOOT, false);\n    }\n\n    /**\n     * Set the user's preference for offline voice recognition.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setUseOffline(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(USE_OFFLINE, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for offline voice recognition.\n     *\n     * @param ctx the application context\n     * @return true if offline recognition is preferred\n     */\n    public static boolean getUseOffline(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(USE_OFFLINE, false);\n    }\n\n    /**\n     * Set the user's preference for a network synthesised voice.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setNetworkSynthesis(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(NETWORK_SYNTHESIS, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for a network synthesised voice.\n     *\n     * @param ctx the application context\n     * @return true if network synthesis is preferred\n     */\n    public static boolean getNetworkSynthesis(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(NETWORK_SYNTHESIS, true);\n    }\n\n    /**\n     * Get the user default action for an unknown command\n     *\n     * @param ctx the application context\n     * @return the action constant\n     */\n    public static int getCommandUnknownAction(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(COMMAND_UNKNOWN_ACTION, Unknown.UNKNOWN_STATE);\n    }\n\n    /**\n     * Set the user default action for an unknown command\n     *\n     * @param ctx    the application context\n     * @param action to set\n     */\n    public static void setCommandUnknownAction(@NonNull final Context ctx, final int action) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(COMMAND_UNKNOWN_ACTION, action);\n        edit.commit();\n    }\n\n    /**\n     * Get the minimum require connection level to process network events\n     *\n     * @param ctx the application context\n     * @return the integer constant from {@link Network}\n     */\n    public static int getConnectionMinimum(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getInt(CONNECTION_MINIMUM, Network.CONNECTION_TYPE_3G);\n    }\n\n    /**\n     * Set the minimum require connection level to process network events\n     *\n     * @param ctx        the application context\n     * @param connection to set as a minimum\n     */\n    public static void setConnectionMinimum(@NonNull final Context ctx, final int connection) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putInt(CONNECTION_MINIMUM, connection);\n        edit.commit();\n    }\n\n    /**\n     * Get the hotword the user has enrolled.\n     *\n     * @param ctx the application context\n     * @return the enrolled hotword or null if one has yet to be enrolled\n     */\n    public static String getHotword(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(HOTWORD, SaiyRequestParams.SILENCE);\n    }\n\n    /**\n     * Set the user's enrolled hotword\n     *\n     * @param ctx     the application context\n     * @param hotword that has been enrolled\n     */\n    public static void setHotword(@NonNull final Context ctx, final String hotword) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(HOTWORD, hotword);\n        edit.commit();\n    }\n\n    /**\n     * Get the user defined custom intro\n     *\n     * @param ctx the application context\n     * @return the user's name or 'master' if none is applied\n     */\n    public static String getCustomIntro(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(CUSTOM_INTRO, null);\n    }\n\n    /**\n     * Set the user defined custom intro\n     *\n     * @param ctx   the application context\n     * @param intro by which they wish to be called\n     */\n    public static void setCustomIntro(@NonNull final Context ctx, final String intro) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(CUSTOM_INTRO, intro);\n        edit.commit();\n    }\n\n    /**\n     * Set the user's preference for randomising their custom intro.\n     *\n     * @param ctx       the application context\n     * @param condition to be applied\n     */\n    public static void setCustomIntroRandom(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(CUSTOM_INTRO_RANDOM, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get the user's preference for randomising their custom intro.\n     *\n     * @param ctx the application context\n     * @return true if offline recognition is preferred\n     */\n    public static boolean getCustomIntroRandom(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(CUSTOM_INTRO_RANDOM, true);\n    }\n\n    /**\n     * Get the user defined name by which they wish to be addressed.\n     *\n     * @param ctx the application context\n     * @return the user's name or 'master' if none is applied\n     */\n    public static String getUserName(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getString(USER_NAME, ctx.getString(ai.saiy.android.R.string.master));\n    }\n\n    /**\n     * Set the user defined name by which they wish to be addressed.\n     *\n     * @param ctx      the application context\n     * @param userName by which they wish to be called\n     */\n    public static void setUserName(@NonNull final Context ctx, final String userName) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putString(USER_NAME, userName);\n        edit.commit();\n    }\n\n    /**\n     * Set whether the recogniser should use a workaround\n     *\n     * @param ctx       the application context\n     * @param condition to apply\n     */\n    public static void setRecogniserBusyFix(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(RECOGNISER_BUSY_FIX, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether the recogniser should use a workaround\n     *\n     * @param ctx the application context\n     * @return true as the default, or false if the user has disabled this\n     */\n    public static boolean getRecogniserBusyFix(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(RECOGNISER_BUSY_FIX, Installed.isGoogleNowLauncherDefault(ctx));\n    }\n\n    /**\n     * Set whether the hotword should use a workaround\n     *\n     * @param ctx       the application context\n     * @param condition to apply\n     */\n    public static void setOkayGoogleFix(@NonNull final Context ctx, final boolean condition) {\n        final SharedPreferences pref = getPref(ctx);\n        final SharedPreferences.Editor edit = getEditor(pref);\n\n        edit.putBoolean(OKAY_GOOGLE_FIX, condition);\n        edit.commit();\n    }\n\n    /**\n     * Get whether the hotword should use a workaround\n     *\n     * @param ctx the application context\n     * @return true as the default, or false if the user has disabled this\n     */\n    public static boolean getOkayGoogleFix(@NonNull final Context ctx) {\n        final SharedPreferences pref = getPref(ctx);\n        return pref.getBoolean(OKAY_GOOGLE_FIX, Installed.isGoogleNowLauncherDefault(ctx));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsBundle.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * This file incorporates work covered by the following copyright and\n * permission notice:\n *\n *      Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>\n *\n *      Licensed to the Apache Software Foundation (ASF) under one or more\n *      contributor license agreements.  See the NOTICE file distributed with\n *      this work for additional information regarding copyright ownership.\n *      The ASF licenses this file to You under the Apache License, Version 2.0\n *      (the \"License\"); you may not use this file except in compliance with\n *      the License.  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 */\npackage ai.saiy.android.utils;\n\nimport android.content.Intent;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\n/**\n * Created by benrandall76@gmail.com on 23/04/2016.\n */\npublic class UtilsBundle {\n\n    /**\n     * Prevent instantiation\n     */\n    public UtilsBundle() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    public static boolean notNaked(@Nullable final Bundle bundle) {\n        return bundle != null && !bundle.isEmpty();\n    }\n\n    /**\n     * Scrubs Intents for private serializable subclasses in the Intent extras. If the Intent's extras contain\n     * a private serializable subclass, the Bundle is cleared. The Bundle will not be set to null. If the\n     * Bundle is null, has no extras, or the extras do not contain a private serializable subclass, the Bundle\n     * is not mutated.\n     *\n     * @param intent {@code Intent} to scrub. This parameter may be mutated if scrubbing is necessary. This\n     *               parameter may be null.\n     * @return true if the Intent was scrubbed, false if the Intent was not modified.\n     */\n    public static boolean isSuspicious(@Nullable final Intent intent) {\n        return intent != null && isSuspicious(intent.getExtras());\n    }\n\n    /**\n     * Scrubs Bundles for private serializable subclasses in the extras. If the Bundle's extras contain a\n     * private serializable subclass, the Bundle is cleared. If the Bundle is null, has no extras, or the\n     * extras do not contain a private serializable subclass, the Bundle is not mutated.\n     *\n     * @param bundle {@code Bundle} to scrub. This parameter may be mutated if scrubbing is necessary. This\n     *               parameter may be null.\n     * @return true if the Bundle was scrubbed, false if the Bundle was not modified.\n     */\n    public static boolean isSuspicious(@Nullable final Bundle bundle) {\n\n        if (bundle != null) {\n\n        /*\n         * Note: This is a hack to work around a private serializable classloader attack\n         */\n            try {\n                // if a private serializable exists, this will throw an exception\n                bundle.containsKey(null);\n            } catch (final Exception e) {\n                bundle.clear();\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsFile.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.Resources;\nimport android.net.Uri;\nimport android.support.annotation.NonNull;\nimport android.support.v4.content.ContextCompat;\nimport android.support.v4.content.FileProvider;\nimport android.util.Pair;\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * Utility class of handy file method. Static for ease of access\n * <p>\n * Created by benrandall76@gmail.com on 10/09/2016.\n */\n\npublic class UtilsFile {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = UtilsFile.class.getSimpleName();\n\n    private static final String FILE_PROVIDER = \"ai.saiy.android.fileprovider\";\n    private static final String SOUND_EFFECT_DIR = \"/se/\";\n\n    /**\n     * Prevent instantiation\n     */\n    public UtilsFile() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * Convert a raw resource to a file on the private storage\n     *\n     * @param ctx        the application context\n     * @param resourceId the resource identifier\n     * @return the file or null if the process failed\n     */\n    public static File oggToCacheAndGrant(@NonNull final Context ctx, final int resourceId, final String packageName) {\n\n        final String cachePath = getPrivateDirPath(ctx);\n\n        if (UtilsString.notNaked(cachePath)) {\n\n            final String name = ctx.getResources().getResourceEntryName(resourceId);\n\n            final File file = resourceToFile(ctx, resourceId, new File(cachePath + SOUND_EFFECT_DIR + name\n                    + \".\" + Constants.OGG_AUDIO_FILE_SUFFIX));\n\n            if (file != null) {\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"wavToCacheAndGrant file exists: \" + file.exists());\n                    MyLog.i(CLS_NAME, \"wavToCacheAndGrant file path: \" + file.getAbsolutePath());\n                }\n\n                final Uri contentUri = FileProvider.getUriForFile(ctx, FILE_PROVIDER, file);\n                ctx.grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);\n\n                return file;\n            } else {\n                if (DEBUG) {\n                    MyLog.e(CLS_NAME, \"wavToCacheAndGrant file null\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.e(CLS_NAME, \"wavToCacheAndGrant failed to get any path\");\n            }\n        }\n\n        return null;\n\n    }\n\n    /**\n     * Utility to copy the contents of a raw resource to a file\n     *\n     * @param ctx        the application context\n     * @param resourceId the resource identifier\n     * @param file       the destination file\n     * @return the completed file or null if the process failed\n     */\n    private static File resourceToFile(@NonNull final Context ctx, final int resourceId,\n                                       @NonNull final File file) {\n\n        try {\n            FileUtils.copyInputStreamToFile(ctx.getResources().openRawResource(resourceId), file);\n            return file;\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"resourceToFile IOException\");\n                e.printStackTrace();\n            }\n        } catch (final Resources.NotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"resourceToFile NotFoundException\");\n                e.printStackTrace();\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Attempt to get a directory that does not require permission to read/write. This should be\n     * simple but @see https://code.google.com/p/android/issues/detail?id=81357\n     *\n     * @param ctx the application context\n     * @return the directory or null if all efforts fail.\n     */\n    public static File getPrivateDir(@NonNull final Context ctx) {\n\n        Pair<Boolean, File> dirPair = getExternalFilesDir(ctx);\n\n        if (dirPair.first) {\n            return dirPair.second;\n        }\n\n        dirPair = getExternalCacheDir(ctx);\n\n        if (dirPair.first) {\n            return dirPair.second;\n        }\n\n        dirPair = getCacheDir(ctx);\n\n        if (dirPair.first) {\n            return dirPair.second;\n        }\n\n        return null;\n    }\n\n    /**\n     * Attempt to get a directory location that does not require permission to read/write. This should be\n     * simple but @see https://code.google.com/p/android/issues/detail?id=81357\n     *\n     * @param ctx the application context\n     * @return the absolute path of the location or null if all efforts fail.\n     */\n    public static String getPrivateDirPath(@NonNull final Context ctx) {\n\n        final File file = getPrivateDir(ctx);\n\n        if (file != null) {\n            return file.getAbsolutePath();\n        }\n\n        return null;\n    }\n\n    /**\n     * Check we can create a file in the desired location by attempting to create a temporary file\n     * there.\n     *\n     * @param ctx the application context\n     * @return a {@link Pair} with the first parameter denoting success and the second the directory\n     * or null if the process failed.\n     */\n    private static Pair<Boolean, File> getExternalFilesDir(@NonNull final Context ctx) {\n\n        File tempFile = null;\n\n        try {\n\n            tempFile = File.createTempFile(Constants.DEFAULT_TEMP_FILE_PREFIX,\n                    \".\" + Constants.DEFAULT_TEMP_FILE_SUFFIX, ContextCompat.getExternalFilesDirs(ctx, null)[0]);\n\n            if (tempFile.exists()) {\n                return new Pair<>(true, ctx.getExternalFilesDir(null));\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getExternalFilesDir: file does not exist\");\n                }\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getExternalFilesDir: IOException\");\n                e.printStackTrace();\n            }\n        } catch (final IndexOutOfBoundsException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getExternalFilesDir: IndexOutOfBoundsException\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            if (tempFile != null && tempFile.exists()) {\n                final boolean deleted = tempFile.delete();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getExternalFilesDir: finally file deleted: \" + deleted);\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"getExternalFilesDir: failed\");\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    /**\n     * Check we can create a file in the desired location by attempting to create a temporary file\n     * there.\n     *\n     * @param ctx the application context\n     * @return a {@link Pair} with the first parameter denoting success and the second the directory\n     * or null if the process failed.\n     */\n    private static Pair<Boolean, File> getExternalCacheDir(@NonNull final Context ctx) {\n\n        File tempFile = null;\n\n        try {\n\n            tempFile = File.createTempFile(Constants.DEFAULT_TEMP_FILE_PREFIX,\n                    \".\" + Constants.DEFAULT_TEMP_FILE_SUFFIX, ContextCompat.getExternalCacheDirs(ctx)[0]);\n\n            if (tempFile.exists()) {\n                return new Pair<>(true, ctx.getExternalCacheDir());\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getExternalCacheDir: file does not exist\");\n                }\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getExternalCacheDir: IOException\");\n                e.printStackTrace();\n            }\n        } catch (final IndexOutOfBoundsException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getExternalCacheDir: IndexOutOfBoundsException\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            if (tempFile != null && tempFile.exists()) {\n                final boolean deleted = tempFile.delete();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getExternalCacheDir: finally file deleted: \" + deleted);\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"getExternalCacheDir: failed\");\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    /**\n     * Check we can create a file in the desired location by attempting to create a temporary file\n     * there.\n     *\n     * @param ctx the application context\n     * @return a {@link Pair} with the first parameter denoting success and the second the directory\n     * or null if the process failed.\n     */\n    private static Pair<Boolean, File> getCacheDir(@NonNull final Context ctx) {\n\n        File tempFile = null;\n\n        try {\n\n            tempFile = File.createTempFile(Constants.DEFAULT_TEMP_FILE_PREFIX,\n                    \".\" + Constants.DEFAULT_TEMP_FILE_SUFFIX, ctx.getCacheDir());\n\n            if (tempFile.exists()) {\n                return new Pair<>(true, ctx.getCacheDir());\n            } else {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getCacheDir: file does not exist\");\n                }\n            }\n        } catch (final IOException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getCacheDir: IOException\");\n                e.printStackTrace();\n            }\n        } finally {\n\n            if (tempFile != null && tempFile.exists()) {\n                final boolean deleted = tempFile.delete();\n\n                if (DEBUG) {\n                    MyLog.i(CLS_NAME, \"getCacheDir: finally file deleted: \" + deleted);\n                }\n            }\n        }\n\n        if (DEBUG) {\n            MyLog.w(CLS_NAME, \"getCacheDir: failed\");\n        }\n\n        return new Pair<>(false, null);\n    }\n\n    /**\n     * Get a default temporary file to write audio to\n     *\n     * @param ctx the application context\n     * @return the created file\n     */\n    public static File getTempAudioFile(@NonNull final Context ctx) {\n\n        final File tempFile = getPrivateDir(ctx);\n\n        if (tempFile != null) {\n\n            try {\n                return File.createTempFile(Constants.DEFAULT_AUDIO_FILE_PREFIX,\n                        \".\" + Constants.DEFAULT_AUDIO_FILE_SUFFIX, tempFile);\n            } catch (final IOException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"getTempAudioFile: IOException\");\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsList.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.support.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by benrandall76@gmail.com on 20/04/2016.\n */\npublic class UtilsList {\n\n    public static boolean notNaked(@Nullable final List<?> array) {\n        return array != null && !array.isEmpty();\n    }\n\n    public static boolean notNaked(@Nullable final ArrayList<?> array) {\n        return array != null && !array.isEmpty();\n    }\n\n    public static boolean notNaked(@Nullable final float[] array) {\n        return array != null && array.length > 0;\n    }\n\n    public static boolean notNaked(@Nullable final byte[] array) {\n        return array != null && array.length > 0;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsLocale.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.res.Resources;\nimport android.support.annotation.Nullable;\n\nimport java.util.Comparator;\nimport java.util.Locale;\nimport java.util.MissingResourceException;\nimport java.util.NoSuchElementException;\nimport java.util.StringTokenizer;\n\n/**\n * Created by benrandall76@gmail.com on 24/03/2016.\n */\npublic class UtilsLocale {\n\n    /**\n     * Prevent instantiation\n     */\n    public UtilsLocale() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = UtilsLocale.class.getSimpleName();\n\n    public static final String LOCALE_DELIMITER_1 = \"-\";\n    public static final String LOCALE_DELIMITER_2 = \"_\";\n\n    public static final Locale DEFAULT_LOCALE = Locale.getDefault();\n    public static final String DEFAULT_LOCALE_STRING = Locale.getDefault().toString();\n\n    /**\n     * Utility method to convert a string to a {@link Locale}. If this process fails, the best\n     * possible format will be returned.\n     *\n     * @param stringLocale the string {@link Locale} to convert\n     * @return the converted {@link Locale}\n     */\n    public static Locale stringToLocale(@Nullable final String stringLocale) {\n\n        if (UtilsString.notNaked(stringLocale)) {\n\n            try {\n                StringTokenizer tokens;\n\n                if (stringLocale.contains(LOCALE_DELIMITER_1)) {\n                    tokens = new StringTokenizer(stringLocale, LOCALE_DELIMITER_1);\n\n                    switch (tokens.countTokens()) {\n\n                        case 0:\n                            return new Locale(stringLocale);\n                        case 1:\n                            return new Locale(tokens.nextToken());\n                        case 2:\n                            return new Locale(tokens.nextToken(), tokens.nextToken());\n                        case 3:\n                            return new Locale(tokens.nextToken(), tokens.nextToken(), tokens.nextToken());\n                        default:\n                            return new Locale(stringLocale);\n                    }\n\n                } else if (stringLocale.contains(LOCALE_DELIMITER_2)) {\n                    tokens = new StringTokenizer(stringLocale, LOCALE_DELIMITER_2);\n\n                    switch (tokens.countTokens()) {\n\n                        case 0:\n                            return new Locale(stringLocale);\n                        case 1:\n                            return new Locale(tokens.nextToken());\n                        case 2:\n                            return new Locale(tokens.nextToken(), tokens.nextToken());\n                        case 3:\n                            return new Locale(tokens.nextToken(), tokens.nextToken(), tokens.nextToken());\n                        default:\n                            return new Locale(stringLocale);\n                    }\n                } else {\n                    return new Locale(stringLocale);\n                }\n            } catch (final NoSuchElementException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stringToLocale: NoSuchElementException\");\n                }\n            } catch (final MissingResourceException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stringToLocale: MissingResourceException\");\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stringToLocale: NullPointerException\");\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"stringToLocale: Exception\");\n                }\n            }\n        } else {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"stringToLocale: naked\");\n            }\n        }\n\n        return DEFAULT_LOCALE;\n    }\n\n    public static boolean localesLanguageMatch(@Nullable final Locale localeOne, @Nullable final Locale localeTwo) {\n\n        if (localeOne != null && localeTwo != null) {\n\n            try {\n\n                return localeOne.equals(localeTwo)\n                        || localeOne.getLanguage().matches(localeTwo.getLanguage())\n                        || localeOne.getISO3Language().matches(localeTwo.getISO3Language());\n\n            } catch (final MissingResourceException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesLanguageMatch: MissingResourceException\");\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesLanguageMatch: NullPointerException\");\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesLanguageMatch: Exception\");\n                }\n            }\n        }\n\n        return false;\n    }\n\n    public static boolean localesMatch(@Nullable final Locale localeOne, @Nullable final Locale localeTwo) {\n\n        if (localeOne != null && localeTwo != null) {\n\n            try {\n\n                if (localeOne.equals(localeTwo)) {\n                    return true;\n                } else {\n                    if (localeOne.getLanguage().matches(localeTwo.getLanguage())) {\n                        return localeOne.getCountry().matches(localeTwo.getCountry());\n                    } else {\n                        return localeOne.getISO3Language().matches(localeTwo.getISO3Language())\n                                && localeOne.getISO3Country().matches(localeTwo.getISO3Country());\n                    }\n                }\n            } catch (final MissingResourceException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesMatch: MissingResourceException\");\n                }\n            } catch (final NullPointerException e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesMatch: NullPointerException\");\n                }\n            } catch (final Exception e) {\n                if (DEBUG) {\n                    MyLog.w(CLS_NAME, \"localesMatch: Exception\");\n                }\n            }\n        }\n\n        return false;\n    }\n\n    public static class LocaleComparator implements Comparator<Locale> {\n        @Override\n        public int compare(final Locale l1, final Locale l2) {\n            return l1.toString().compareTo(l2.toString());\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsMap.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.support.annotation.Nullable;\n\nimport java.util.Map;\n\n/**\n * Created by benrandall76@gmail.com on 31/05/2016.\n */\npublic class UtilsMap {\n\n    public static boolean notNaked(@Nullable final Map<?, ?> map) {\n        return map != null && !map.isEmpty();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsString.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\n\n/**\n * A collection of handy methods. Static for easy access\n * <p/>\n * Created by benrandall76@gmail.com on 07/02/2016.\n */\npublic class UtilsString {\n\n    /**\n     * Prevent instantiation\n     */\n    public UtilsString() {\n        throw new IllegalArgumentException(Resources.getSystem().getString(android.R.string.no));\n    }\n\n    /**\n     * Get a readable string from the {@link InputStream}\n     *\n     * @param is the {@link InputStream}\n     * @return a readable String\n     * @throws IOException\n     */\n    public static String streamToString(@Nullable final InputStream is) throws IOException {\n\n        String output = \"\";\n\n        if (is != null) {\n\n            final StringBuilder stringBuilder = new StringBuilder();\n\n            String line;\n\n            final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));\n            while ((line = bufferedReader.readLine()) != null) {\n                stringBuilder.append(line);\n            }\n\n            bufferedReader.close();\n            is.close();\n\n            output = stringBuilder.toString();\n        }\n\n        return output;\n    }\n\n    /**\n     * Utility method to check if a string is null or empty. Purely to prevent clutter wherever such\n     * as check is needed.\n     *\n     * @param toCheck the input String\n     * @return true if the String is neither null or empty\n     */\n    public static boolean notNaked(@Nullable final String toCheck) {\n        return toCheck != null && !toCheck.trim().isEmpty();\n    }\n\n    /**\n     * Remove any spaces before punctuation, left by a response failing to add in the user's name.\n     *\n     * @param input the input String\n     * @return the stripped string\n     */\n    public static String stripNameSpace(@NonNull final String input) {\n        return input.replaceAll(\" ,\", \",\").replaceAll(\" \\\\.\", \".\").replaceAll(\" \\\\?\", \"?\");\n    }\n\n    /**\n     * Remove any punctuation from the beginning of utterances that might otherwise be pronounced\n     * by the voice engine.\n     *\n     * @param input the input String\n     * @return the stripped string\n     */\n    public static String stripLeadingPunctuation(@NonNull final String input) {\n        return input.trim().matches(\"\\\\p{P}.*\") ? input.trim().replaceFirst(\"\\\\p{P}\", \"\").trim() : input.trim();\n    }\n\n    /**\n     * Utility method to remove the last instance of a character from a String\n     *\n     * @param inputString the String to be manipulated\n     * @param from        character\n     * @param to          character\n     * @return the manipulated String\n     */\n    public static String replaceLast(@NonNull final String inputString, @NonNull final String from,\n                                     @NonNull final String to) {\n\n        final int lastIndex = inputString.lastIndexOf(from);\n\n        if (lastIndex >= 0) {\n            return inputString.substring(0, lastIndex) + inputString.substring(lastIndex).replaceFirst(from, to);\n        } else {\n            return inputString;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/UtilsVolley.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils;\n\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\nimport com.android.volley.Cache;\nimport com.android.volley.toolbox.DiskBasedCache;\n\n/**\n * Created by benrandall76@gmail.com on 08/09/2016.\n */\n\npublic class UtilsVolley {\n\n    private static Cache cache;\n\n    public static Cache getCache(@NonNull final Context ctx) {\n\n        if (cache == null) {\n            cache = new DiskBasedCache(ctx.getCacheDir(), 1024 * 1024);\n        }\n\n        return cache;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/ai/saiy/android/utils/debug/DebugAction.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android.utils.debug;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.Signature;\nimport android.support.annotation.NonNull;\n\nimport java.io.ByteArrayInputStream;\nimport java.security.PublicKey;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.security.interfaces.RSAPublicKey;\nimport java.util.ArrayList;\n\nimport ai.saiy.android.R;\nimport ai.saiy.android.utils.MyLog;\nimport ai.saiy.android.utils.UtilsList;\n\n/**\n * Created by benrandall76@gmail.com on 01/09/2016.\n */\n\npublic class DebugAction {\n\n    private static final boolean DEBUG = MyLog.DEBUG;\n    private static final String CLS_NAME = DebugAction.class.getSimpleName();\n\n    public static final int DEBUG_TOGGLE_LOGGING = 0;\n    public static final int DEBUG_VALIDATE_SIGNATURE = 1;\n    public static final int DEBUG_CLEAR_SYNTHESIS = 2;\n    public static final int DEBUG_CLEAR_CUSTOM_COMMANDS = 3;\n\n    /**\n     * Remotely validate the signatures\n     *\n     * @param ctx the application context\n     * @return true if the signatures were validated, false otherwise\n     */\n    public static boolean validateSignatures(@NonNull final Context ctx) {\n        final ArrayList<String> signatureArray = DebugAction.getSignatures(ctx);\n        // TODO\n        return UtilsList.notNaked(signatureArray);\n    }\n\n    /**\n     * Get the signatures for the application to be validated remotely\n     *\n     * @param ctx the application context\n     * @return an Array List of {@link Signature}\n     */\n    @SuppressLint(\"PackageManagerGetSignatures\")\n    private static ArrayList<String> getSignatures(@NonNull final Context ctx) {\n        if (DEBUG) {\n            MyLog.i(CLS_NAME, \"getSignatures\");\n        }\n\n        final ArrayList<String> signatureArray = new ArrayList<>();\n\n        try {\n\n            final PackageManager pm = ctx.getPackageManager();\n            final PackageInfo packageInfo = pm.getPackageInfo(ctx.getPackageName(),\n                    PackageManager.GET_SIGNATURES);\n            final Signature[] signatures = packageInfo.signatures;\n\n            CertificateFactory cf;\n            X509Certificate cert;\n            PublicKey key;\n            String mhString;\n            int modulusHash;\n            if (signatures != null && signatures.length > 0) {\n\n                for (final Signature signature : signatures) {\n\n                    cf = CertificateFactory.getInstance(\"X.509\");\n                    cert = (X509Certificate) cf.generateCertificate(\n                            new ByteArrayInputStream(signature.toByteArray()));\n                    key = cert.getPublicKey();\n                    modulusHash = ((RSAPublicKey) key).getModulus().hashCode();\n                    mhString = String.valueOf(modulusHash)\n                            + String.valueOf(ctx.getResources().getInteger(R.integer.hash_version));\n\n                    if (DEBUG) {\n                        MyLog.v(CLS_NAME, \"hash: \" + mhString);\n                    }\n\n                    signatureArray.add(mhString);\n                }\n            }\n        } catch (final PackageManager.NameNotFoundException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSignature  NameNotFoundException\");\n                e.printStackTrace();\n            }\n        } catch (final CertificateException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSignature  CertificateException\");\n                e.printStackTrace();\n            }\n        } catch (final SecurityException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSignature  SecurityException\");\n                e.printStackTrace();\n            }\n        } catch (final NullPointerException e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSignature  NullPointerException\");\n                e.printStackTrace();\n            }\n        } catch (final Exception e) {\n            if (DEBUG) {\n                MyLog.w(CLS_NAME, \"getSignature  Exception\");\n                e.printStackTrace();\n            }\n        }\n\n        return signatureArray;\n    }\n}\n"
  },
  {
    "path": "app/src/main/proto/.gitignore",
    "content": "OWNERS\nREADME.google\ngoogle/internal\ngoogle/protobuf\n"
  },
  {
    "path": "app/src/main/proto/google/api/annotations.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.api;\n\nimport \"google/api/http.proto\";\nimport \"google/protobuf/descriptor.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"AnnotationsProto\";\noption java_package = \"com.google.api\";\n\nextend google.protobuf.MethodOptions {\n  // See `HttpRule`.\n  HttpRule http = 72295728;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/api/http.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.api;\n\noption java_multiple_files = true;\noption java_outer_classname = \"HttpProto\";\noption java_package = \"com.google.api\";\n\n\n// `HttpRule` defines the mapping of an RPC method to one or more HTTP\n// REST APIs.  The mapping determines what portions of the request\n// message are populated from the path, query parameters, or body of\n// the HTTP request.  The mapping is typically specified as an\n// `google.api.http` annotation, see \"google/api/annotations.proto\"\n// for details.\n//\n// The mapping consists of a field specifying the path template and\n// method kind.  The path template can refer to fields in the request\n// message, as in the example below which describes a REST GET\n// operation on a resource collection of messages:\n//\n// ```proto\n// service Messaging {\n//   rpc GetMessage(GetMessageRequest) returns (Message) {\n//     option (google.api.http).get = \"/v1/messages/{message_id}\";\n//   }\n// }\n// message GetMessageRequest {\n//   string message_id = 1; // mapped to the URL\n// }\n// message Message {\n//   string text = 1; // content of the resource\n// }\n// ```\n//\n// This definition enables an automatic, bidrectional mapping of HTTP\n// JSON to RPC. Example:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456`  | `GetMessage(message_id: \"123456\")`\n//\n// In general, not only fields but also field paths can be referenced\n// from a path pattern. Fields mapped to the path pattern cannot be\n// repeated and must have a primitive (non-message) type.\n//\n// Any fields in the request message which are not bound by the path\n// pattern automatically become (optional) HTTP query\n// parameters. Assume the following definition of the request message:\n//\n// ```proto\n// message GetMessageRequest {\n//   string message_id = 1; // mapped to the URL\n//   int64 revision = 2;    // becomes a parameter\n// }\n// ```\n//\n// This enables a HTTP JSON to RPC mapping as below:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456?revision=2` | `GetMessage(message_id: \"123456\" revision: 2)`\n//\n// Note that fields which are mapped to HTTP parameters must have a\n// primitive type or a repeated primitive type. Message types are not\n// allowed. In the case of a repeated type, the parameter can be\n// repeated in the URL, as in `...?param=A&param=B`.\n//\n// For HTTP method kinds which allow a request body, the `body` field\n// specifies the mapping. Consider a REST update method on the\n// message resource collection:\n//\n// ```proto\n// service Messaging {\n//   rpc UpdateMessage(UpdateMessageRequest) returns (Message) {\n//     option (google.api.http) = {\n//       put: \"/v1/messages/{message_id}\"\n//       body: \"message\"\n//   }\n// }\n// message UpdateMessageRequest {\n//   string message_id = 1; // mapped to the URL\n//   Message message = 2;   // mapped to the body\n// }\n// ```\n//\n// The following HTTP JSON to RPC mapping is enabled, where the\n// representation of the JSON in the request body is determined by\n// protos JSON encoding:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" message { text: \"Hi!\" })`\n//\n// The special name `*` can be used in the body mapping to define that\n// every field not bound by the path template should be mapped to the\n// request body.  This enables the following alternative definition of\n// the update method:\n//\n// ```proto\n// service Messaging {\n//   rpc UpdateMessage(Message) returns (Message) {\n//     option (google.api.http) = {\n//       put: \"/v1/messages/{message_id}\"\n//       body: \"*\"\n//   }\n// }\n// message Message {\n//   string message_id = 2;\n//   string text = 2;\n// }\n// ```\n//\n// The following HTTP JSON to RPC mapping is enabled:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" text: \"Hi!\")`\n//\n// Note that when using `*` in the body mapping, it is not possible to\n// have HTTP parameters, as all fields not bound by the path end in\n// the body. This makes this option more rarely used in practice of\n// defining REST APIs. The common usage of `*` is in custom methods\n// which don't use the URL at all for transferring data.\n//\n// It is possible to define multiple HTTP methods for one RPC by using\n// the `additional_bindings` option. Example:\n//\n// ```proto\n// service Messaging {\n//   rpc GetMessage(GetMessageRequest) returns (Message) {\n//     option (google.api.http) = {\n//       get: \"/v1/messages/{message_id}\"\n//       additional_bindings {\n//         get: \"/v1/users/{user_id}/messages/{message_id}\"\n//       }\n//   }\n// }\n// message GetMessageRequest {\n//   string message_id = 1;\n//   string user_id = 2;\n// }\n// ```\n//\n// This enables the following two alternative HTTP JSON to RPC\n// mappings:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456` | `GetMessage(message_id: \"123456\")`\n// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: \"me\" message_id: \"123456\")`\n//\n// # Rules for HTTP mapping\n// The rules for mapping HTTP path, query parameters, and body fields\n// to the request message are as follows:\n//\n// 1. The `body` field specifies either `*` or a field path, or is\n//    omitted. If omitted, it assumes there is no HTTP body.\n// 2. Leaf fields (recursive expansion of nested messages in the\n//    request) can be classified into three types:\n//     (a) Matched in the URL template.\n//     (b) Covered by body (if body is `*`, everything except (a) fields;\n//         else everything under the body field)\n//     (c) All other fields.\n// 3. URL query parameters found in the HTTP request are mapped to (c) fields.\n// 4. Any body sent with an HTTP request can contain only (b) fields.\n//\n// The syntax of the path template is as follows:\n//\n//     Template = \"/\" Segments [ Verb ] ;\n//     Segments = Segment { \"/\" Segment } ;\n//     Segment  = \"*\" | \"**\" | LITERAL | Variable ;\n//     Variable = \"{\" FieldPath [ \"=\" Segments ] \"}\" ;\n//     FieldPath = IDENT { \".\" IDENT } ;\n//     Verb     = \":\" LITERAL ;\n//\n// `*` matches a single path component, `**` zero or more path components, and\n// `LITERAL` a constant.  A `Variable` can match an entire path as specified\n// again by a template; this nested template must not contain further variables.\n// If no template is given with a variable, it matches a single path component.\n// The notation `{var}` is henceforth equivalent to `{var=*}`. NOTE: the field\n// paths in variables and in the `body` must not refer to repeated fields.\n//\n// Use CustomHttpPattern to specify any HTTP method that is not included in the\n// pattern field, such as HEAD, or \"*\" to leave the HTTP method unspecified for\n// a given URL path rule. The wild-card rule is useful for services that provide\n// content to Web (HTML) clients.\nmessage HttpRule {\n\n  // Determines the URL pattern is matched by this rules. This pattern can be\n  // used with any of the {get|put|post|delete|patch} methods. A custom method\n  // can be defined using the 'custom' field.\n  oneof pattern {\n    // Used for listing and getting information about resources.\n    string get = 2;\n\n    // Used for updating a resource.\n    string put = 3;\n\n    // Used for creating a resource.\n    string post = 4;\n\n    // Used for deleting a resource.\n    string delete = 5;\n\n    // Used for updating a resource.\n    string patch = 6;\n\n    // Custom pattern is used for defining custom verbs.\n    CustomHttpPattern custom = 8;\n  }\n\n  // The name of the request field whose value is mapped to the HTTP body, or\n  // `*` for mapping all fields not captured by the path pattern to the HTTP\n  // body. NOTE: the referred field must not be a repeated field.\n  string body = 7;\n\n  // Additional HTTP bindings for the selector. Nested bindings must\n  // not contain an `additional_bindings` field themselves (that is,\n  // the nesting may only be one level deep).\n  repeated HttpRule additional_bindings = 11;\n}\n\n// A custom pattern is used for defining custom HTTP verb.\nmessage CustomHttpPattern {\n  // The name of this custom HTTP verb.\n  string kind = 1;\n\n  // The path matched by this custom verb.\n  string path = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/api/label.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.api;\n\noption java_multiple_files = true;\noption java_outer_classname = \"LabelProto\";\noption java_package = \"com.google.api\";\n\n\n// A description of a label.\nmessage LabelDescriptor {\n  // Value types that can be used as label values.\n  enum ValueType {\n    // A variable-length string. This is the default.\n    STRING = 0;\n\n    // Boolean; true or false.\n    BOOL = 1;\n\n    // A 64-bit signed integer.\n    INT64 = 2;\n  }\n\n  // The label key.\n  string key = 1;\n\n  // The type of data that can be assigned to the label.\n  ValueType value_type = 2;\n\n  // A human-readable description for the label.\n  string description = 3;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/api/monitored_resource.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.api;\n\nimport \"google/api/label.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"MonitoredResourceProto\";\noption java_package = \"com.google.api\";\n\n\n// A descriptor that describes the schema of [MonitoredResource][google.api.MonitoredResource].\nmessage MonitoredResourceDescriptor {\n  // The monitored resource type. For example, the type `\"cloudsql_database\"`\n  // represents databases in Google Cloud SQL.\n  string type = 1;\n\n  // A concise name for the monitored resource type that can be displayed in\n  // user interfaces. For example, `\"Google Cloud SQL Database\"`.\n  string display_name = 2;\n\n  // A detailed description of the monitored resource type that can be used in\n  // documentation.\n  string description = 3;\n\n  // A set of labels that can be used to describe instances of this monitored\n  // resource type. For example, Google Cloud SQL databases can be labeled with\n  // their `\"database_id\"` and their `\"zone\"`.\n  repeated LabelDescriptor labels = 4;\n}\n\n// A monitored resource describes a resource that can be used for monitoring\n// purpose. It can also be used for logging, billing, and other purposes. Each\n// resource has a `type` and a set of `labels`. The labels contain information\n// that identifies the resource and describes attributes of it. For example,\n// you can use monitored resource to describe a normal file, where the resource\n// has `type` as `\"file\"`, the label `path` identifies the file, and the label\n// `size` describes the file size. The monitoring system can use a set of\n// monitored resources of files to generate file size distribution.\nmessage MonitoredResource {\n  // The monitored resource type. This field must match the corresponding\n  // [MonitoredResourceDescriptor.type][google.api.MonitoredResourceDescriptor.type] to this resource..  For example,\n  // `\"cloudsql_database\"` represents Cloud SQL databases.\n  string type = 1;\n\n  // Values for some or all of the labels listed in the associated monitored\n  // resource descriptor. For example, you specify a specific Cloud SQL database\n  // by supplying values for both the `\"database_id\"` and `\"zone\"` labels.\n  map<string, string> labels = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/README.md",
    "content": "# Introduction\nThe Google logging service.\n"
  },
  {
    "path": "app/src/main/proto/google/logging/type/http_request.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.type;\n\nimport \"google/api/annotations.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"HttpRequestProto\";\noption java_package = \"com.google.logging.type\";\n\n\n// A common proto for logging HTTP requests.\n//\nmessage HttpRequest {\n  // The request method. Examples: `\"GET\"`, `\"HEAD\"`, `\"PUT\"`, `\"POST\"`.\n  string request_method = 1;\n\n  // The scheme (http, https), the host name, the path and the query\n  // portion of the URL that was requested.\n  // Example: `\"http://example.com/some/info?color=red\"`.\n  string request_url = 2;\n\n  // The size of the HTTP request message in bytes, including the request\n  // headers and the request body.\n  int64 request_size = 3;\n\n  // The response code indicating the status of response.\n  // Examples: 200, 404.\n  int32 status = 4;\n\n  // The size of the HTTP response message sent back to the client, in bytes,\n  // including the response headers and the response body.\n  int64 response_size = 5;\n\n  // The user agent sent by the client. Example:\n  // `\"Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Q312461; .NET CLR 1.0.3705)\"`.\n  string user_agent = 6;\n\n  // The IP address (IPv4 or IPv6) of the client that issued the HTTP\n  // request. Examples: `\"192.168.1.1\"`, `\"FE80::0202:B3FF:FE1E:8329\"`.\n  string remote_ip = 7;\n\n  // The referer URL of the request, as defined in\n  // [HTTP/1.1 Header Field Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html).\n  string referer = 8;\n\n  // Whether or not an entity was served from cache\n  // (with or without validation).\n  bool cache_hit = 9;\n\n  // Whether or not the response was validated with the origin server before\n  // being served from cache. This field is only meaningful if `cache_hit` is\n  // True.\n  bool validated_with_origin_server = 10;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/type/log_severity.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.type;\n\nimport \"google/api/annotations.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"LogSeverityProto\";\noption java_package = \"com.google.logging.type\";\n\n\n// The severity of the event described in a log entry.  These guideline severity\n// levels are ordered, with numerically smaller levels treated as less severe\n// than numerically larger levels. If the source of the log entries uses a\n// different set of severity levels, the client should select the closest\n// corresponding `LogSeverity` value. For example, Java's FINE, FINER, and\n// FINEST levels might all map to `LogSeverity.DEBUG`. If the original severity\n// code must be preserved, it can be stored in the payload.\n//\nenum LogSeverity {\n  // The log entry has no assigned severity level.\n  DEFAULT = 0;\n\n  // Debug or trace information.\n  DEBUG = 100;\n\n  // Routine information, such as ongoing status or performance.\n  INFO = 200;\n\n  // Normal but significant events, such as start up, shut down, or\n  // configuration.\n  NOTICE = 300;\n\n  // Warning events might cause problems.\n  WARNING = 400;\n\n  // Error events are likely to cause problems.\n  ERROR = 500;\n\n  // Critical events cause more severe problems or brief outages.\n  CRITICAL = 600;\n\n  // A person must take an action immediately.\n  ALERT = 700;\n\n  // One or more systems are unusable.\n  EMERGENCY = 800;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/v2/log_entry.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.v2;\n\nimport \"google/api/annotations.proto\";\nimport \"google/api/monitored_resource.proto\";\nimport \"google/logging/type/http_request.proto\";\nimport \"google/logging/type/log_severity.proto\";\nimport \"google/protobuf/any.proto\";\nimport \"google/protobuf/struct.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"LogEntryProto\";\noption java_package = \"com.google.logging.v2\";\n\n\n// An individual entry in a log.\nmessage LogEntry {\n  // Required. The resource name of the log to which this log entry\n  // belongs. The format of the name is\n  // `projects/&lt;project-id&gt;/logs/&lt;log-id%gt;`.  Examples:\n  // `\"projects/my-projectid/logs/syslog\"`,\n  // `\"projects/1234567890/logs/library.googleapis.com%2Fbook_log\"`.\n  //\n  // The log ID part of resource name must be less than 512 characters\n  // long and can only include the following characters: upper and\n  // lower case alphanumeric characters: [A-Za-z0-9]; and punctuation\n  // characters: forward-slash, underscore, hyphen, and period.\n  // Forward-slash (`/`) characters in the log ID must be URL-encoded.\n  string log_name = 12;\n\n  // Required. The monitored resource associated with this log entry.\n  // Example: a log entry that reports a database error would be\n  // associated with the monitored resource designating the particular\n  // database that reported the error.\n  google.api.MonitoredResource resource = 8;\n\n  // Required. The log entry payload, which can be one of multiple types.\n  oneof payload {\n    // The log entry payload, represented as a protocol buffer.\n    // You can only use `protoPayload` values that belong to a set of approved\n    // types.\n    google.protobuf.Any proto_payload = 2;\n\n    // The log entry payload, represented as a Unicode string (UTF-8).\n    string text_payload = 3;\n\n    // The log entry payload, represented as a structure that\n    // is expressed as a JSON object.\n    google.protobuf.Struct json_payload = 6;\n  }\n\n  // Optional. The time the event described by the log entry occurred.  If\n  // omitted, Cloud Logging will use the time the log entry is written.\n  google.protobuf.Timestamp timestamp = 9;\n\n  // Optional. The severity of the log entry. The default value is\n  // `LogSeverity.DEFAULT`.\n  google.logging.type.LogSeverity severity = 10;\n\n  // Optional. A unique ID for the log entry. If you provide this field, the\n  // logging service considers other log entries in the same log with the same\n  // ID as duplicates which can be removed.\n  // If omitted, Cloud Logging will generate a unique ID for this log entry.\n  string insert_id = 4;\n\n  // Optional. Information about the HTTP request associated with this log entry,\n  // if applicable.\n  google.logging.type.HttpRequest http_request = 7;\n\n  // Optional. A set of user-defined (key, value) data that provides additional\n  // information about the log entry.\n  map<string, string> labels = 11;\n\n  // Optional. Information about an operation associated with the log entry, if\n  // applicable.\n  LogEntryOperation operation = 15;\n}\n\n// Additional information about a potentially long-running operation with which\n// a log entry is associated.\nmessage LogEntryOperation {\n  // Required. An arbitrary operation identifier. Log entries with the\n  // same identifier are assumed to be part of the same operation.\n  //\n  string id = 1;\n\n  // Required. An arbitrary producer identifier. The combination of\n  // `id` and `producer` must be globally unique.  Examples for `producer`:\n  // `\"MyDivision.MyBigCompany.com\"`, \"github.com/MyProject/MyApplication\"`.\n  //\n  string producer = 2;\n\n  // Optional. Set this to True if this is the first log entry in the operation.\n  bool first = 3;\n\n  // Optional. Set this to True if this is the last log entry in the operation.\n  bool last = 4;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/v2/logging.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.v2;\n\nimport \"google/api/annotations.proto\";\nimport \"google/api/monitored_resource.proto\";\nimport \"google/logging/v2/log_entry.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/rpc/status.proto\";\n\noption cc_enable_arenas = true;\noption java_multiple_files = true;\noption java_outer_classname = \"LoggingProto\";\noption java_package = \"com.google.logging.v2\";\n\n\n// Service for ingesting and querying logs.\nservice LoggingServiceV2 {\n  // Deletes a log and all its log entries.\n  // The log will reappear if it receives new entries.\n  //\n  rpc DeleteLog(DeleteLogRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = { delete: \"/v2beta1/{log_name=projects/*/logs/*}\" };\n  }\n\n  // Writes log entries to Cloud Logging.\n  // All log entries in Cloud Logging are written by this method.\n  //\n  rpc WriteLogEntries(WriteLogEntriesRequest) returns (WriteLogEntriesResponse) {\n    option (google.api.http) = { post: \"/v2beta1/entries:write\" body: \"*\" };\n  }\n\n  // Lists log entries.  Use this method to retrieve log entries from Cloud\n  // Logging.  For ways to export log entries, see\n  // [Exporting Logs](/logging/docs/export).\n  //\n  rpc ListLogEntries(ListLogEntriesRequest) returns (ListLogEntriesResponse) {\n    option (google.api.http) = { post: \"/v2beta1/entries:list\" body: \"*\" };\n  }\n\n  // Lists monitored resource descriptors that are used by Cloud Logging.\n  rpc ListMonitoredResourceDescriptors(ListMonitoredResourceDescriptorsRequest) returns (ListMonitoredResourceDescriptorsResponse) {\n    option (google.api.http) = { get: \"/v2beta1/monitoredResourceDescriptors\" };\n  }\n}\n\n// The parameters to DeleteLog.\nmessage DeleteLogRequest {\n  // Required. The resource name of the log to delete.  Example:\n  // `\"projects/my-project/logs/syslog\"`.\n  string log_name = 1;\n}\n\n// The parameters to WriteLogEntries.\nmessage WriteLogEntriesRequest {\n  // Optional. A default log resource name for those log entries in `entries`\n  // that do not specify their own `logName`.  Example:\n  // `\"projects/my-project/logs/syslog\"`.  See\n  // [LogEntry][google.logging.v2.LogEntry].\n  string log_name = 1;\n\n  // Optional. A default monitored resource for those log entries in `entries`\n  // that do not specify their own `resource`.\n  google.api.MonitoredResource resource = 2;\n\n  // Optional. User-defined `key:value` items that are added to\n  // the `labels` field of each log entry in `entries`, except when a log\n  // entry specifies its own `key:value` item with the same key.\n  // Example: `{ \"size\": \"large\", \"color\":\"red\" }`\n  map<string, string> labels = 3;\n\n  // Required. The log entries to write. The log entries must have values for\n  // all required fields.\n  repeated LogEntry entries = 4;\n}\n\n// Result returned from WriteLogEntries.\nmessage WriteLogEntriesResponse {\n\n}\n\n// The parameters to `ListLogEntries`.\nmessage ListLogEntriesRequest {\n  // Required. One or more project IDs or project numbers from which to retrieve\n  // log entries.  Examples of a project ID: `\"my-project-1A\"`, `\"1234567890\"`.\n  repeated string project_ids = 1;\n\n  // Optional. An [advanced logs filter](/logging/docs/view/advanced_filters).\n  // The filter is compared against all log entries in the projects specified by\n  // `projectIds`.  Only entries that match the filter are retrieved.  An empty\n  // filter matches all log entries.\n  string filter = 2;\n\n  // Optional. How the results should be sorted.  Presently, the only permitted\n  // values are `\"timestamp\"` (default) and `\"timestamp desc\"`.  The first\n  // option returns entries in order of increasing values of\n  // `LogEntry.timestamp` (oldest first), and the second option returns entries\n  // in order of decreasing timestamps (newest first).  Entries with equal\n  // timestamps are returned in order of `LogEntry.insertId`.\n  string order_by = 3;\n\n  // Optional. The maximum number of results to return from this request.  Fewer\n  // results might be returned. You must check for the `nextPageToken` result to\n  // determine if additional results are available, which you can retrieve by\n  // passing the `nextPageToken` value in the `pageToken` parameter to the next\n  // request.\n  int32 page_size = 4;\n\n  // Optional. If the `pageToken` request parameter is supplied, then the next\n  // page of results in the set are retrieved.  The `pageToken` parameter must\n  // be set with the value of the `nextPageToken` result parameter from the\n  // previous request.  The values of `projectIds`, `filter`, and `orderBy` must\n  // be the same as in the previous request.\n  string page_token = 5;\n}\n\n// Result returned from `ListLogEntries`.\nmessage ListLogEntriesResponse {\n  // A list of log entries.\n  repeated LogEntry entries = 1;\n\n  // If there are more results than were returned, then `nextPageToken` is\n  // given a value in the response.  To get the next batch of results, call\n  // this method again using the value of `nextPageToken` as `pageToken`.\n  string next_page_token = 2;\n}\n\n// The parameters to ListMonitoredResourceDescriptors\nmessage ListMonitoredResourceDescriptorsRequest {\n  // Optional. The maximum number of results to return from this request.  Fewer\n  // results might be returned. You must check for the `nextPageToken` result to\n  // determine if additional results are available, which you can retrieve by\n  // passing the `nextPageToken` value in the `pageToken` parameter to the next\n  // request.\n  int32 page_size = 1;\n\n  // Optional. If the `pageToken` request parameter is supplied, then the next\n  // page of results in the set are retrieved.  The `pageToken` parameter must\n  // be set with the value of the `nextPageToken` result parameter from the\n  // previous request.\n  string page_token = 2;\n}\n\n// Result returned from ListMonitoredResourceDescriptors.\nmessage ListMonitoredResourceDescriptorsResponse {\n  // A list of resource descriptors.\n  repeated google.api.MonitoredResourceDescriptor resource_descriptors = 1;\n\n  // If there are more results than were returned, then `nextPageToken` is\n  // returned in the response.  To get the next batch of results, call this\n  // method again using the value of `nextPageToken` as `pageToken`.\n  string next_page_token = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/v2/logging_config.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.v2;\n\nimport \"google/api/annotations.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"LoggingConfig\";\noption java_package = \"com.google.logging.v2\";\n\n\nservice ConfigServiceV2 {\n  // Lists sinks.\n  rpc ListSinks(ListSinksRequest) returns (ListSinksResponse) {\n    option (google.api.http) = { get: \"/v2beta1/{project_name=projects/*}/sinks\" };\n  }\n\n  // Gets a sink.\n  rpc GetSink(GetSinkRequest) returns (LogSink) {\n    option (google.api.http) = { get: \"/v2beta1/{sink_name=projects/*/sinks/*}\" };\n  }\n\n  // Creates a sink.\n  rpc CreateSink(CreateSinkRequest) returns (LogSink) {\n    option (google.api.http) = { post: \"/v2beta1/{project_name=projects/*}/sinks\" body: \"sink\" };\n  }\n\n  // Creates or updates a sink.\n  rpc UpdateSink(UpdateSinkRequest) returns (LogSink) {\n    option (google.api.http) = { put: \"/v2beta1/{sink_name=projects/*/sinks/*}\" body: \"sink\" };\n  }\n\n  // Deletes a sink.\n  rpc DeleteSink(DeleteSinkRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = { delete: \"/v2beta1/{sink_name=projects/*/sinks/*}\" };\n  }\n}\n\n// Describes a sink used to export log entries outside Cloud Logging.\nmessage LogSink {\n  // Available log entry formats. Log entries can be written to Cloud\n  // Logging in either format and can be exported in either format.\n  // Version 2 is the preferred format.\n  enum VersionFormat {\n    // An unspecified version format will default to V2.\n    VERSION_FORMAT_UNSPECIFIED = 0;\n\n    // `LogEntry` version 2 format.\n    V2 = 1;\n\n    // `LogEntry` version 1 format.\n    V1 = 2;\n  }\n\n  // Required. The client-assigned sink identifier. Example:\n  // `\"my-severe-errors-to-pubsub\"`.\n  // Sink identifiers are limited to 1000 characters\n  // and can include only the following characters: `A-Z`, `a-z`,\n  // `0-9`, and the special characters `_-.`.\n  string name = 1;\n\n  // The export destination. See\n  // [Exporting Logs With Sinks](/logging/docs/api/tasks/exporting-logs).\n  // Examples: `\"storage.googleapis.com/a-bucket\"`,\n  // `\"bigquery.googleapis.com/projects/a-project-id/datasets/a-dataset\"`.\n  string destination = 3;\n\n  // An [advanced logs filter](/logging/docs/view/advanced_filters)\n  // that defines the log entries to be exported.  The filter must be\n  // consistent with the log entry format designed by the\n  // `outputVersionFormat` parameter, regardless of the format of the\n  // log entry that was originally written to Cloud Logging.\n  // Example: `\"logName:syslog AND severity>=ERROR\"`.\n  string filter = 5;\n\n  // The log entry version used when exporting log entries from this\n  // sink.  This version does not have to correspond to the version of\n  // the log entry when it was written to Cloud Logging.\n  VersionFormat output_version_format = 6;\n}\n\n// The parameters to `ListSinks`.\nmessage ListSinksRequest {\n  // Required. The resource name of the project containing the sinks.\n  // Example: `\"projects/my-logging-project\"`, `\"projects/01234567890\"`.\n  string project_name = 1;\n\n  // Optional. If the `pageToken` request parameter is supplied, then the next\n  // page of results in the set are retrieved.  The `pageToken` parameter must\n  // be set with the value of the `nextPageToken` result parameter from the\n  // previous request. The value of `projectName` must be the same as in the\n  // previous request.\n  string page_token = 2;\n\n  // Optional. The maximum number of results to return from this request.  Fewer\n  // results might be returned. You must check for the `nextPageToken` result to\n  // determine if additional results are available, which you can retrieve by\n  // passing the `nextPageToken` value in the `pageToken` parameter to the next\n  // request.\n  int32 page_size = 3;\n}\n\n// Result returned from `ListSinks`.\nmessage ListSinksResponse {\n  // A list of sinks.\n  repeated LogSink sinks = 1;\n\n  // If there are more results than were returned, then `nextPageToken` is\n  // given a value in the response.  To get the next batch of results, call this\n  // method again using the value of `nextPageToken` as `pageToken`.\n  string next_page_token = 2;\n}\n\n// The parameters to `GetSink`.\nmessage GetSinkRequest {\n  // The resource name of the sink to return.\n  // Example: `\"projects/my-project-id/sinks/my-sink-id\"`.\n  string sink_name = 1;\n}\n\n// The parameters to `CreateSink`.\nmessage CreateSinkRequest {\n  // The resource name of the project in which to create the sink.\n  // Example: `\"projects/my-project-id\"`.\n  //\n  // The new sink must be provided in the request.\n  string project_name = 1;\n\n  // The new sink, which must not have an identifier that already\n  // exists.\n  LogSink sink = 2;\n}\n\n// The parameters to `UpdateSink`.\nmessage UpdateSinkRequest {\n  // The resource name of the sink to update.\n  // Example: `\"projects/my-project-id/sinks/my-sink-id\"`.\n  //\n  // The updated sink must be provided in the request and have the\n  // same name that is specified in `sinkName`.  If the sink does not\n  // exist, it is created.\n  string sink_name = 1;\n\n  // The updated sink, whose name must be the same as the sink\n  // identifier in `sinkName`.  If `sinkName` does not exist, then\n  // this method creates a new sink.\n  LogSink sink = 2;\n}\n\n// The parameters to `DeleteSink`.\nmessage DeleteSinkRequest {\n  // The resource name of the sink to delete.\n  // Example: `\"projects/my-project-id/sinks/my-sink-id\"`.\n  string sink_name = 1;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/logging/v2/logging_metrics.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.logging.v2;\n\nimport \"google/api/annotations.proto\";\nimport \"google/protobuf/empty.proto\";\n\noption java_multiple_files = true;\noption java_package = \"com.google.logging.v2\";\n\n\nservice MetricsServiceV2 {\n  // Lists logs-based metrics.\n  rpc ListLogMetrics(ListLogMetricsRequest) returns (ListLogMetricsResponse) {\n    option (google.api.http) = { get: \"/v2beta1/{project_name=projects/*}/metrics\" };\n  }\n\n  // Gets a logs-based metric.\n  rpc GetLogMetric(GetLogMetricRequest) returns (LogMetric) {\n    option (google.api.http) = { get: \"/v2beta1/{metric_name=projects/*/metrics/*}\" };\n  }\n\n  // Creates a logs-based metric.\n  rpc CreateLogMetric(CreateLogMetricRequest) returns (LogMetric) {\n    option (google.api.http) = { post: \"/v2beta1/{project_name=projects/*}/metrics\" body: \"metric\" };\n  }\n\n  // Creates or updates a logs-based metric.\n  rpc UpdateLogMetric(UpdateLogMetricRequest) returns (LogMetric) {\n    option (google.api.http) = { put: \"/v2beta1/{metric_name=projects/*/metrics/*}\" body: \"metric\" };\n  }\n\n  // Deletes a logs-based metric.\n  rpc DeleteLogMetric(DeleteLogMetricRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = { delete: \"/v2beta1/{metric_name=projects/*/metrics/*}\" };\n  }\n}\n\n// Describes a logs-based metric.  The value of the metric is the\n// number of log entries that match a logs filter.\nmessage LogMetric {\n  // Required. The client-assigned metric identifier. Example:\n  // `\"severe_errors\"`.  Metric identifiers are limited to 1000\n  // characters and can include only the following characters: `A-Z`,\n  // `a-z`, `0-9`, and the special characters `_-.,+!*',()%/\\`.  The\n  // forward-slash character (`/`) denotes a hierarchy of name pieces,\n  // and it cannot be the first character of the name.\n  string name = 1;\n\n  // A description of this metric, which is used in documentation.\n  string description = 2;\n\n  // An [advanced logs filter](/logging/docs/view/advanced_filters).\n  // Example: `\"logName:syslog AND severity>=ERROR\"`.\n  string filter = 3;\n}\n\n// The parameters to ListLogMetrics.\nmessage ListLogMetricsRequest {\n  // Required. The resource name of the project containing the metrics.\n  // Example: `\"projects/my-project-id\"`.\n  string project_name = 1;\n\n  // Optional. If the `pageToken` request parameter is supplied, then the next\n  // page of results in the set are retrieved.  The `pageToken` parameter must\n  // be set with the value of the `nextPageToken` result parameter from the\n  // previous request.  The value of `projectName` must\n  // be the same as in the previous request.\n  string page_token = 2;\n\n  // Optional. The maximum number of results to return from this request.  Fewer\n  // results might be returned. You must check for the `nextPageToken` result to\n  // determine if additional results are available, which you can retrieve by\n  // passing the `nextPageToken` value in the `pageToken` parameter to the next\n  // request.\n  int32 page_size = 3;\n}\n\n// Result returned from ListLogMetrics.\nmessage ListLogMetricsResponse {\n  // A list of logs-based metrics.\n  repeated LogMetric metrics = 1;\n\n  // If there are more results than were returned, then `nextPageToken` is given\n  // a value in the response.  To get the next batch of results, call this\n  // method again using the value of `nextPageToken` as `pageToken`.\n  string next_page_token = 2;\n}\n\n// The parameters to GetLogMetric.\nmessage GetLogMetricRequest {\n  // The resource name of the desired metric.\n  // Example: `\"projects/my-project-id/metrics/my-metric-id\"`.\n  string metric_name = 1;\n}\n\n// The parameters to CreateLogMetric.\nmessage CreateLogMetricRequest {\n  // The resource name of the project in which to create the metric.\n  // Example: `\"projects/my-project-id\"`.\n  //\n  // The new metric must be provided in the request.\n  string project_name = 1;\n\n  // The new logs-based metric, which must not have an identifier that\n  // already exists.\n  LogMetric metric = 2;\n}\n\n// The parameters to UpdateLogMetric.\n//\nmessage UpdateLogMetricRequest {\n  // The resource name of the metric to update.\n  // Example: `\"projects/my-project-id/metrics/my-metric-id\"`.\n  //\n  // The updated metric must be provided in the request and have the\n  // same identifier that is specified in `metricName`.\n  // If the metric does not exist, it is created.\n  string metric_name = 1;\n\n  // The updated metric, whose name must be the same as the\n  // metric identifier in `metricName`. If `metricName` does not\n  // exist, then a new metric is created.\n  LogMetric metric = 2;\n}\n\n// The parameters to DeleteLogMetric.\nmessage DeleteLogMetricRequest {\n  // The resource name of the metric to delete.\n  // Example: `\"projects/my-project-id/metrics/my-metric-id\"`.\n  string metric_name = 1;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/longrunning/operations.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.longrunning;\n\nimport \"google/api/annotations.proto\";\nimport \"google/protobuf/any.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/rpc/status.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"OperationsProto\";\noption java_package = \"com.google.longrunning\";\n\n\n// Manages long-running operations with an API service.\n//\n// When an API method normally takes long time to complete, it can be designed\n// to return [Operation][google.longrunning.Operation] to the client, and the client can use this\n// interface to receive the real response asynchronously by polling the\n// operation resource, or using `google.watcher.v1.Watcher` interface to watch\n// the response, or pass the operation resource to another API (such as Google\n// Cloud Pub/Sub API) to receive the response.  Any API service that returns\n// long-running operations should implement the `Operations` interface so\n// developers can have a consistent client experience.\nservice Operations {\n  // Gets the latest state of a long-running operation.  Clients may use this\n  // method to poll the operation result at intervals as recommended by the API\n  // service.\n  rpc GetOperation(GetOperationRequest) returns (Operation) {\n    option (google.api.http) = { get: \"/v1/{name=operations/**}\" };\n  }\n\n  // Lists operations that match the specified filter in the request. If the\n  // server doesn't support this method, it returns\n  // `google.rpc.Code.UNIMPLEMENTED`.\n  rpc ListOperations(ListOperationsRequest) returns (ListOperationsResponse) {\n    option (google.api.http) = { get: \"/v1/{name=operations}\" };\n  }\n\n  // Starts asynchronous cancellation on a long-running operation.  The server\n  // makes a best effort to cancel the operation, but success is not\n  // guaranteed.  If the server doesn't support this method, it returns\n  // `google.rpc.Code.UNIMPLEMENTED`.  Clients may use\n  // [Operations.GetOperation] or other methods to check whether the\n  // cancellation succeeded or the operation completed despite cancellation.\n  rpc CancelOperation(CancelOperationRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = { post: \"/v1/{name=operations/**}:cancel\" body: \"*\" };\n  }\n\n  // Deletes a long-running operation.  It indicates the client is no longer\n  // interested in the operation result. It does not cancel the operation.\n  rpc DeleteOperation(DeleteOperationRequest) returns (google.protobuf.Empty) {\n    option (google.api.http) = { delete: \"/v1/{name=operations/**}\" };\n  }\n}\n\n// This resource represents a long-running operation that is the result of a\n// network API call.\nmessage Operation {\n  // The name of the operation resource, which is only unique within the same\n  // service that originally returns it.\n  string name = 1;\n\n  // Some service-specific metadata associated with the operation.  It typically\n  // contains progress information and common metadata such as create time.\n  // Some services may not provide such metadata.  Any method that returns a\n  // long-running operation should document the metadata type, if any.\n  google.protobuf.Any metadata = 2;\n\n  // If the value is false, it means the operation is still in progress.\n  // If true, the operation is completed and the `result` is available.\n  bool done = 3;\n\n  oneof result {\n    // The error result of the operation in case of failure.\n    google.rpc.Status error = 4;\n\n    // The normal response of the operation in case of success.  If the original\n    // method returns no data on success, such as `Delete`, the response will be\n    // `google.protobuf.Empty`.  If the original method is standard\n    // `Get`/`Create`/`Update`, the response should be the resource.  For other\n    // methods, the response should have the type `XxxResponse`, where `Xxx`\n    // is the original method name.  For example, if the original method name\n    // is `TakeSnapshot()`, the inferred response type will be\n    // `TakeSnapshotResponse`.\n    google.protobuf.Any response = 5;\n  }\n}\n\n// The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation].\nmessage GetOperationRequest {\n  // The name of the operation resource.\n  string name = 1;\n}\n\n// The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations].\nmessage ListOperationsRequest {\n  // The name of the operation collection.\n  string name = 4;\n\n  // The standard List filter.\n  string filter = 1;\n\n  // The standard List page size.\n  int32 page_size = 2;\n\n  // The standard List page token.\n  string page_token = 3;\n}\n\n// The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations].\nmessage ListOperationsResponse {\n  // A list of operations that match the specified filter in the request.\n  repeated Operation operations = 1;\n\n  // The standard List next-page token.\n  string next_page_token = 2;\n}\n\n// The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation].\nmessage CancelOperationRequest {\n  // The name of the operation resource to be cancelled.\n  string name = 1;\n}\n\n// The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation].\nmessage DeleteOperationRequest {\n  // The name of the operation resource to be deleted.\n  string name = 1;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/rpc/README.md",
    "content": "# Google RPC\n\nThis package contains type definitions for general RPC systems. While\n[gRPC](https://github.com/grpc) is using these defintions, but they\nare not designed specifically to support gRPC.\n"
  },
  {
    "path": "app/src/main/proto/google/rpc/code.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.rpc;\n\noption java_multiple_files = true;\noption java_outer_classname = \"CodeProto\";\noption java_package = \"com.google.rpc\";\n\n\n// The canonical error codes for Google APIs.\n// Warnings:\n//\n// -   Do not change any numeric assignments.\n// -   Changes to this list should be made only if there is a compelling\n//     need that can't be satisfied in another way.\n//\n// Sometimes multiple error codes may apply.  Services should return\n// the most specific error code that applies.  For example, prefer\n// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.\n// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.\nenum Code {\n  // Not an error; returned on success\n  //\n  // HTTP Mapping: 200 OK\n  OK = 0;\n\n  // The operation was cancelled, typically by the caller.\n  //\n  // HTTP Mapping: 499 Client Closed Request\n  CANCELLED = 1;\n\n  // Unknown error.  For example, this error may be returned when\n  // a `Status` value received from another address space belongs to\n  // an error space that is not known in this address space.  Also\n  // errors raised by APIs that do not return enough error information\n  // may be converted to this error.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  UNKNOWN = 2;\n\n  // The client specified an invalid argument.  Note that this differs\n  // from `FAILED_PRECONDITION`.  `INVALID_ARGUMENT` indicates arguments\n  // that are problematic regardless of the state of the system\n  // (e.g., a malformed file name).\n  //\n  // HTTP Mapping: 400 Bad Request\n  INVALID_ARGUMENT = 3;\n\n  // The deadline expired before the operation could complete. For operations\n  // that change the state of the system, this error may be returned\n  // even if the operation has completed successfully.  For example, a\n  // successful response from a server could have been delayed long\n  // enough for the deadline to expire.\n  //\n  // HTTP Mapping: 504 Gateway Timeout\n  DEADLINE_EXCEEDED = 4;\n\n  // Some requested entity (e.g., file or directory) was not found.\n  // For privacy reasons, this code *might* be returned when the client\n  // does not have the access rights to the entity.\n  //\n  // HTTP Mapping: 404 Not Found\n  NOT_FOUND = 5;\n\n  // The entity that a client attempted to create (e.g., file or directory)\n  // already exists.\n  //\n  // HTTP Mapping: 409 Conflict\n  ALREADY_EXISTS = 6;\n\n  // The caller does not have permission to execute the specified\n  // operation. `PERMISSION_DENIED` must not be used for rejections\n  // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n  // instead for those errors). `PERMISSION_DENIED` must not be\n  // used if the caller can not be identified (use `UNAUTHENTICATED`\n  // instead for those errors).\n  //\n  // HTTP Mapping: 403 Forbidden\n  PERMISSION_DENIED = 7;\n\n  // The request does not have valid authentication credentials for the\n  // operation.\n  //\n  // HTTP Mapping: 401 Unauthorized\n  UNAUTHENTICATED = 16;\n\n  // Some resource has been exhausted, perhaps a per-user quota, or\n  // perhaps the entire file system is out of space.\n  //\n  // HTTP Mapping: 429 Too Many Requests\n  RESOURCE_EXHAUSTED = 8;\n\n  // The operation was rejected because the system is not in a state\n  // required for the operation's execution.  For example, the directory\n  // to be deleted is non-empty, an rmdir operation is applied to\n  // a non-directory, etc.\n  //\n  // Service implementors can use the following guidelines to decide\n  // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n  //  (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n  //  (b) Use `ABORTED` if the client should retry at a higher level\n  //      (e.g., restarting a read-modify-write sequence).\n  //  (c) Use `FAILED_PRECONDITION` if the client should not retry until\n  //      the system state has been explicitly fixed.  E.g., if an \"rmdir\"\n  //      fails because the directory is non-empty, `FAILED_PRECONDITION`\n  //      should be returned since the client should not retry unless\n  //      the files are deleted from the directory.\n  //  (d) Use `FAILED_PRECONDITION` if the client performs conditional\n  //      REST Get/Update/Delete on a resource and the resource on the\n  //      server does not match the condition. E.g., conflicting\n  //      read-modify-write on the same resource.\n  //\n  // HTTP Mapping: 400 Bad Request\n  //\n  // NOTE: HTTP spec says `412 Precondition Failed` should be used only if\n  // the request contains Etag-related headers. So if the server does see\n  // Etag-related headers in the request, it may choose to return 412\n  // instead of 400 for this error code.\n  FAILED_PRECONDITION = 9;\n\n  // The operation was aborted, typically due to a concurrency issue such as\n  // a sequencer check failure or transaction abort.\n  //\n  // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n  // `ABORTED`, and `UNAVAILABLE`.\n  //\n  // HTTP Mapping: 409 Conflict\n  ABORTED = 10;\n\n  // The operation was attempted past the valid range.  E.g., seeking or\n  // reading past end-of-file.\n  //\n  // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n  // be fixed if the system state changes. For example, a 32-bit file\n  // system will generate `INVALID_ARGUMENT` if asked to read at an\n  // offset that is not in the range [0,2^32-1], but it will generate\n  // `OUT_OF_RANGE` if asked to read from an offset past the current\n  // file size.\n  //\n  // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n  // `OUT_OF_RANGE`.  We recommend using `OUT_OF_RANGE` (the more specific\n  // error) when it applies so that callers who are iterating through\n  // a space can easily look for an `OUT_OF_RANGE` error to detect when\n  // they are done.\n  //\n  // HTTP Mapping: 400 Bad Request\n  OUT_OF_RANGE = 11;\n\n  // The operation is not implemented or is not supported/enabled in this\n  // service.\n  //\n  // HTTP Mapping: 501 Not Implemented\n  UNIMPLEMENTED = 12;\n\n  // Internal errors.  This means that some invariants expected by the\n  // underlying system have been broken.  This error code is reserved\n  // for serious errors.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  INTERNAL = 13;\n\n  // The service is currently unavailable.  This is most likely a\n  // transient condition, which can be corrected by retrying with\n  // a backoff.\n  //\n  // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n  // `ABORTED`, and `UNAVAILABLE`.\n  //\n  // HTTP Mapping: 503 Service Unavailable\n  UNAVAILABLE = 14;\n\n  // Unrecoverable data loss or corruption.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  DATA_LOSS = 15;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/rpc/error_details.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.rpc;\n\nimport \"google/protobuf/duration.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"ErrorDetailsProto\";\noption java_package = \"com.google.rpc\";\n\n\n// Describes when the clients can retry a failed request. Clients could ignore\n// the recommendation here or retry when this information is missing from error\n// responses.\n//\n// It's always recommended that clients should use exponential backoff when\n// retrying.\n//\n// Clients should wait until `retry_delay` amount of time has passed since\n// receiving the error response before retrying.  If retrying requests also\n// fail, clients should use an exponential backoff scheme to gradually increase\n// the delay between retries based on `retry_delay`, until either a maximum\n// number of retires have been reached or a maximum retry delay cap has been\n// reached.\nmessage RetryInfo {\n  // Clients should wait at least this long between retrying the same request.\n  google.protobuf.Duration retry_delay = 1;\n}\n\n// Describes additional debugging info.\nmessage DebugInfo {\n  // The stack trace entries indicating where the error occurred.\n  repeated string stack_entries = 1;\n\n  // Additional debugging information provided by the server.\n  string detail = 2;\n}\n\n// Describes how a quota check failed.\n//\n// For example if a daily limit was exceeded for the calling project,\n// a service could respond with a QuotaFailure detail containing the project\n// id and the description of the quota limit that was exceeded.  If the\n// calling project hasn't enabled the service in the developer console, then\n// a service could respond with the project id and set `service_disabled`\n// to true.\n//\n// Also see RetryDetail and Help types for other details about handling a\n// quota failure.\nmessage QuotaFailure {\n  // A message type used to describe a single quota violation.  For example, a\n  // daily quota or a custom quota that was exceeded.\n  message Violation {\n    // The subject on which the quota check failed.\n    // For example, \"clientip:<ip address of client>\" or \"project:<Google\n    // developer project id>\".\n    string subject = 1;\n\n    // A description of how the quota check failed. Clients can use this\n    // description to find more about the quota configuration in the service's\n    // public documentation, or find the relevant quota limit to adjust through\n    // developer console.\n    //\n    // For example: \"Service disabled\" or \"Daily Limit for read operations\n    // exceeded\".\n    string description = 2;\n  }\n\n  // Describes all quota violations.\n  repeated Violation violations = 1;\n}\n\n// Describes violations in a client request. This error type focuses on the\n// syntactic aspects of the request.\nmessage BadRequest {\n  // A message type used to describe a single bad request field.\n  message FieldViolation {\n    // A path leading to a field in the request body. The value will be a\n    // sequence of dot-separated identifiers that identify a protocol buffer\n    // field. E.g., \"violations.field\" would identify this field.\n    string field = 1;\n\n    // A description of why the request element is bad.\n    string description = 2;\n  }\n\n  // Describes all violations in a client request.\n  repeated FieldViolation field_violations = 1;\n}\n\n// Contains metadata about the request that clients can attach when filing a bug\n// or providing other forms of feedback.\nmessage RequestInfo {\n  // An opaque string that should only be interpreted by the service generating\n  // it. For example, it can be used to identify requests in the service's logs.\n  string request_id = 1;\n\n  // Any data that was used to serve this request. For example, an encrypted\n  // stack trace that can be sent back to the service provider for debugging.\n  string serving_data = 2;\n}\n\n// Describes the resource that is being accessed.\nmessage ResourceInfo {\n  // A name for the type of resource being accessed, e.g. \"sql table\",\n  // \"cloud storage bucket\", \"file\", \"Google calendar\"; or the type URL\n  // of the resource: e.g. \"type.googleapis.com/google.pubsub.v1.Topic\".\n  string resource_type = 1;\n\n  // The name of the resource being accessed.  For example, a shared calendar\n  // name: \"example.com_4fghdhgsrgh@group.calendar.google.com\", if the current\n  // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].\n  string resource_name = 2;\n\n  // The owner of the resource (optional).\n  // For example, \"user:<owner email>\" or \"project:<Google developer project\n  // id>\".\n  string owner = 3;\n\n  // Describes what error is encountered when accessing this resource.\n  // For example, updating a cloud project may require the `writer` permission\n  // on the developer console project.\n  string description = 4;\n}\n\n// Provides links to documentation or for performing an out of band action.\n//\n// For example, if a quota check failed with an error indicating the calling\n// project hasn't enabled the accessed service, this can contain a URL pointing\n// directly to the right place in the developer console to flip the bit.\nmessage Help {\n  // Describes a URL link.\n  message Link {\n    // Describes what the link offers.\n    string description = 1;\n\n    // The URL of the link.\n    string url = 2;\n  }\n\n  // URL(s) pointing to additional information on handling the current error.\n  repeated Link links = 1;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/rpc/status.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.rpc;\n\nimport \"google/protobuf/any.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"StatusProto\";\noption java_package = \"com.google.rpc\";\n\n\n// The `Status` type defines a logical error model that is suitable for different\n// programming environments, including REST APIs and RPC APIs. It is used by\n// [gRPC](https://github.com/grpc). The error model is designed to be:\n//\n// - Simple to use and understand for most users\n// - Flexible enough to meet unexpected needs\n//\n// # Overview\n//\n// The `Status` message contains three pieces of data: error code, error message,\n// and error details. The error code should be an enum value of\n// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed.  The\n// error message should be a developer-facing English message that helps\n// developers *understand* and *resolve* the error. If a localized user-facing\n// error message is needed, put the localized message in the error details or\n// localize it in the client. The optional error details may contain arbitrary\n// information about the error. There is a predefined set of error detail types\n// in the package `google.rpc` which can be used for common error conditions.\n//\n// # Language mapping\n//\n// The `Status` message is the logical representation of the error model, but it\n// is not necessarily the actual wire format. When the `Status` message is\n// exposed in different client libraries and different wire protocols, it can be\n// mapped differently. For example, it will likely be mapped to some exceptions\n// in Java, but more likely mapped to some error codes in C.\n//\n// # Other uses\n//\n// The error model and the `Status` message can be used in a variety of\n// environments, either with or without APIs, to provide a\n// consistent developer experience across different environments.\n//\n// Example uses of this error model include:\n//\n// - Partial errors. If a service needs to return partial errors to the client,\n//     it may embed the `Status` in the normal response to indicate the partial\n//     errors.\n//\n// - Workflow errors. A typical workflow has multiple steps. Each step may\n//     have a `Status` message for error reporting purpose.\n//\n// - Batch operations. If a client uses batch request and batch response, the\n//     `Status` message should be used directly inside batch response, one for\n//     each error sub-response.\n//\n// - Asynchronous operations. If an API call embeds asynchronous operation\n//     results in its response, the status of those operations should be\n//     represented directly using the `Status` message.\n//\n// - Logging. If some API errors are stored in logs, the message `Status` could\n//     be used directly after any stripping needed for security/privacy reasons.\nmessage Status {\n  // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].\n  int32 code = 1;\n\n  // A developer-facing error message, which should be in English. Any\n  // user-facing error message should be localized and sent in the\n  // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.\n  string message = 2;\n\n  // A list of messages that carry the error details.  There will be a\n  // common set of message types for APIs to use.\n  repeated google.protobuf.Any details = 3;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/speech/v1/cloud-speech.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.cloud.speech.v1;\n\nimport \"google/api/annotations.proto\";\nimport \"google/rpc/status.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"SpeechProto\";\noption java_package = \"com.google.cloud.speech.v1\";\n\n\n// Service that implements Google Cloud Speech API.\nservice Speech {\n  // Perform bidirectional streaming speech recognition on audio using gRPC.\n  rpc Recognize(stream RecognizeRequest) returns (stream RecognizeResponse);\n\n  // Perform non-streaming speech recognition on audio using HTTPS.\n  rpc NonStreamingRecognize(RecognizeRequest) returns (NonStreamingRecognizeResponse) {\n    option (google.api.http) = { post: \"/v1/speech:recognize\" body: \"*\" };\n  }\n}\n\n// `RecognizeRequest` is the only message type sent by the client.\n//\n// `NonStreamingRecognize` sends only one `RecognizeRequest` message and it\n// must contain both an `initial_request` and an 'audio_request`.\n//\n// Streaming `Recognize` sends one or more `RecognizeRequest` messages. The\n// first message must contain an `initial_request` and may contain an\n// 'audio_request`. Any subsequent messages must not contain an\n// `initial_request` and must contain an 'audio_request`.\nmessage RecognizeRequest {\n  // The `initial_request` message provides information to the recognizer\n  // that specifies how to process the request.\n  //\n  // The first `RecognizeRequest` message must contain an `initial_request`.\n  // Any subsequent `RecognizeRequest` messages must not contain an\n  // `initial_request`.\n  InitialRecognizeRequest initial_request = 1;\n\n  // The audio data to be recognized. For `NonStreamingRecognize`, all the\n  // audio data must be contained in the first (and only) `RecognizeRequest`\n  // message. For streaming `Recognize`, sequential chunks of audio data are\n  // sent in sequential `RecognizeRequest` messages.\n  AudioRequest audio_request = 2;\n}\n\n// The `InitialRecognizeRequest` message provides information to the recognizer\n// that specifies how to process the request.\nmessage InitialRecognizeRequest {\n  // Audio encoding of the data sent in the audio message.\n  enum AudioEncoding {\n    // Not specified. Will return result [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].\n    ENCODING_UNSPECIFIED = 0;\n\n    // Uncompressed 16-bit signed little-endian samples.\n    // This is the simplest encoding format, useful for getting started.\n    // However, because it is uncompressed, it is not recommended for deployed\n    // clients.\n    LINEAR16 = 1;\n\n    // This is the recommended encoding format because it uses lossless\n    // compression; therefore recognition accuracy is not compromised by a lossy\n    // codec.\n    //\n    // The stream FLAC format is specified at:\n    // http://flac.sourceforge.net/documentation.html.\n    // Only 16-bit samples are supported.\n    // Not all fields in STREAMINFO are supported.\n    FLAC = 2;\n\n    // 8-bit samples that compand 14-bit audio samples using PCMU/mu-law.\n    MULAW = 3;\n\n    // Adaptive Multi-Rate Narrowband codec. `sample_rate` must be 8000 Hz.\n    AMR = 4;\n\n    // Adaptive Multi-Rate Wideband codec. `sample_rate` must be 16000 Hz.\n    AMR_WB = 5;\n  }\n\n  // [Required] Encoding of audio data sent in all `AudioRequest` messages.\n  AudioEncoding encoding = 1;\n\n  // [Required] Sample rate in Hertz of the audio data sent in all\n  // AudioRequest messages.\n  // 16000 is optimal. Valid values are: 8000-48000.\n  int32 sample_rate = 2;\n\n  // [Optional] The language of the supplied audio as a BCP-47 language tag.\n  // Example: \"en-GB\"  https://www.rfc-editor.org/rfc/bcp/bcp47.txt\n  // If omitted, defaults to \"en-US\".\n  string language_code = 3;\n\n  // [Optional] Maximum number of recognition hypotheses to be returned.\n  // Specifically, the maximum number of `SpeechRecognitionAlternative` messages\n  // within each `SpeechRecognitionResult`.\n  // The server may return fewer than `max_alternatives`.\n  // Valid values are `0`-`30`. A value of `0` or `1` will return a maximum of\n  // `1`. If omitted, defaults to `1`.\n  int32 max_alternatives = 4;\n\n  // [Optional] If set to `true`, the server will attempt to filter out\n  // profanities, replacing all but the initial character in each filtered word\n  // with asterisks, e.g. \"f***\". If set to `false` or omitted, profanities\n  // won't be filtered out.\n  bool profanity_filter = 5;\n\n  // [Optional] If `false` or omitted, the recognizer will detect a single\n  // spoken utterance, and it will cease recognition when the user stops\n  // speaking. If `enable_endpointer_events` is `true`, it will return\n  // `END_OF_UTTERANCE` when it detects that the user has stopped speaking.\n  // In all cases, it will return no more than one `SpeechRecognitionResult`,\n  // and set the `is_final` flag to `true`.\n  //\n  // If `true`, the recognizer will continue recognition (even if the user\n  // pauses speaking) until the client sends an `end_of_data` message or when\n  // the maximum time limit has been reached. Multiple\n  // `SpeechRecognitionResult`s with the `is_final` flag set to `true` may be\n  // returned to indicate that the recognizer will not return any further\n  // hypotheses for this portion of the transcript.\n  bool continuous = 6;\n\n  // [Optional] If this parameter is `true`, interim results may be returned as\n  // they become available.\n  // If `false` or omitted, only `is_final=true` result(s) are returned.\n  bool interim_results = 7;\n\n  // [Optional] If this parameter is `true`, `EndpointerEvents` may be returned\n  // as they become available.\n  // If `false` or omitted, no `EndpointerEvents` are returned.\n  bool enable_endpointer_events = 8;\n\n  // [Optional] URI that points to a file where the recognition result should\n  // be stored in JSON format. If omitted or empty string, the recognition\n  // result is returned in the response. Should be specified only for\n  // `NonStreamingRecognize`. If specified in a `Recognize` request,\n  // `Recognize` returns [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].\n  // If specified in a `NonStreamingRecognize` request,\n  // `NonStreamingRecognize` returns immediately, and the output file\n  // is created asynchronously once the audio processing completes.\n  // Currently, only Google Cloud Storage URIs are supported, which must be\n  // specified in the following format: `gs://bucket_name/object_name`\n  // (other URI formats return [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]). For\n  // more information, see [Request URIs](/storage/docs/reference-uris).\n  string output_uri = 9;\n}\n\n// Contains audio data in the format specified in the `InitialRecognizeRequest`.\n// Either `content` or `uri` must be supplied. Supplying both or neither\n// returns [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT].\nmessage AudioRequest {\n  // The audio data bytes encoded as specified in\n  // `InitialRecognizeRequest`. Note: as with all bytes fields, protobuffers\n  // use a pure binary representation, whereas JSON representations use base64.\n  bytes content = 1;\n\n  // URI that points to a file that contains audio data bytes as specified in\n  // `InitialRecognizeRequest`. Currently, only Google Cloud Storage URIs are\n  // supported, which must be specified in the following format:\n  // `gs://bucket_name/object_name` (other URI formats return\n  // [google.rpc.Code.INVALID_ARGUMENT][google.rpc.Code.INVALID_ARGUMENT]). For more information, see\n  // [Request URIs](/storage/docs/reference-uris).\n  string uri = 2;\n}\n\n// `NonStreamingRecognizeResponse` is the only message returned to the client by\n// `NonStreamingRecognize`. It contains the result as zero or more sequential\n// `RecognizeResponse` messages.\n//\n// Note that streaming `Recognize` will also return multiple `RecognizeResponse`\n// messages, but each message is individually streamed.\nmessage NonStreamingRecognizeResponse {\n  // [Output-only] Sequential list of messages returned by the recognizer.\n  repeated RecognizeResponse responses = 1;\n}\n\n// `RecognizeResponse` is the only message type returned to the client.\nmessage RecognizeResponse {\n  // Indicates the type of endpointer event.\n  enum EndpointerEvent {\n    // No endpointer event specified.\n    ENDPOINTER_EVENT_UNSPECIFIED = 0;\n\n    // Speech has been detected in the audio stream.\n    START_OF_SPEECH = 1;\n\n    // Speech has ceased to be detected in the audio stream.\n    END_OF_SPEECH = 2;\n\n    // The end of the audio stream has been reached. and it is being processed.\n    END_OF_AUDIO = 3;\n\n    // This event is only sent when continuous is `false`. It indicates that the\n    // server has detected the end of the user's speech utterance and expects no\n    // additional speech. Therefore, the server will not process additional\n    // audio. The client should stop sending additional audio data.\n    END_OF_UTTERANCE = 4;\n  }\n\n  // [Output-only] If set, returns a [google.rpc.Status][] message that\n  // specifies the error for the operation.\n  google.rpc.Status error = 1;\n\n  // [Output-only] For `continuous=false`, this repeated list contains zero or\n  // one result that corresponds to all of the audio processed so far. For\n  // `continuous=true`, this repeated list contains zero or more results that\n  // correspond to consecutive portions of the audio being processed.\n  // In both cases, contains zero or one `is_final=true` result (the newly\n  // settled portion), followed by zero or more `is_final=false` results.\n  repeated SpeechRecognitionResult results = 2;\n\n  // [Output-only] Indicates the lowest index in the `results` array that has\n  // changed. The repeated `SpeechRecognitionResult` results overwrite past\n  // results at this index and higher.\n  int32 result_index = 3;\n\n  // [Output-only] Indicates the type of endpointer event.\n  EndpointerEvent endpoint = 4;\n}\n\n// A speech recognition result corresponding to a portion of the audio.\nmessage SpeechRecognitionResult {\n  // [Output-only] May contain one or more recognition hypotheses (up to the\n  // maximum specified in `max_alternatives`).\n  repeated SpeechRecognitionAlternative alternatives = 1;\n\n  // [Output-only] Set `true` if this is the final time the speech service will\n  // return this particular `SpeechRecognitionResult`. If `false`, this\n  // represents an interim result that may change.\n  bool is_final = 2;\n\n  // [Output-only] An estimate of the probability that the recognizer will not\n  // change its guess about this interim result. Values range from 0.0\n  // (completely unstable) to 1.0 (completely stable). Note that this is not the\n  // same as `confidence`, which estimates the probability that a recognition\n  // result is correct.\n  // This field is only provided for interim results (`is_final=false`).\n  // The default of 0.0 is a sentinel value indicating stability was not set.\n  float stability = 3;\n}\n\n// Alternative hypotheses (a.k.a. n-best list).\nmessage SpeechRecognitionAlternative {\n  // [Output-only] Transcript text representing the words that the user spoke.\n  string transcript = 1;\n\n  // [Output-only] The confidence estimate between 0.0 and 1.0. A higher number\n  // means the system is more confident that the recognition is correct.\n  // This field is typically provided only for the top hypothesis. and only for\n  // `is_final=true` results.\n  // The default of 0.0 is a sentinel value indicating confidence was not set.\n  float confidence = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/speech/v1beta1/cloud_speech.proto",
    "content": "// Copyright 2016 Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.cloud.speech.v1beta1;\n\noption java_multiple_files = true;\noption java_outer_classname = \"SpeechProto\";\noption java_package = \"com.google.cloud.speech.v1beta1\";\n\nimport \"google/api/annotations.proto\";\nimport \"google/longrunning/operations.proto\";\nimport \"google/rpc/status.proto\";\n\n\n// Service that implements Google Cloud Speech API.\nservice Speech {\n  // Perform synchronous speech-recognition: receive results after all audio\n  // has been sent and processed.\n  rpc SyncRecognize(SyncRecognizeRequest) returns (SyncRecognizeResponse) {\n    option (google.api.http) =\n        { post: \"/v1beta1/speech:syncrecognize\" body: \"*\" };\n  }\n\n  // Perform asynchronous speech-recognition: receive results via the\n  // google.longrunning.Operations interface. `Operation.response` returns\n  // `AsyncRecognizeResponse`.\n  rpc AsyncRecognize(AsyncRecognizeRequest)\n      returns (google.longrunning.Operation) {\n    option (google.api.http) =\n        { post: \"/v1beta1/speech:asyncrecognize\" body: \"*\" };\n  }\n\n  // Perform bidirectional streaming speech-recognition: receive results while\n  // sending audio. This method is only available via the gRPC API (not REST).\n  rpc StreamingRecognize(stream StreamingRecognizeRequest)\n      returns (stream StreamingRecognizeResponse);\n}\n\n// `SyncRecognizeRequest` is the top-level message sent by the client for\n// the `SyncRecognize` method.\nmessage SyncRecognizeRequest {\n  // [Required] The `config` message provides information to the recognizer\n  // that specifies how to process the request.\n  RecognitionConfig config = 1;\n\n  // [Required] The audio data to be recognized.\n  RecognitionAudio audio = 2;\n}\n\n// `AsyncRecognizeRequest` is the top-level message sent by the client for\n// the `AsyncRecognize` method.\nmessage AsyncRecognizeRequest {\n  // [Required] The `config` message provides information to the recognizer\n  // that specifies how to process the request.\n  RecognitionConfig config = 1;\n\n  // [Required] The audio data to be recognized.\n  RecognitionAudio audio = 2;\n}\n\n// `StreamingRecognizeRequest` is the top-level message sent by the client for\n// the `StreamingRecognize`. Multiple `StreamingRecognizeRequest` messages are\n// sent. The first message must contain a `streaming_config` message and must\n// not contain `audio` data. All subsequent messages must contain `audio` data\n// and must not contain a `streaming_config` message.\nmessage StreamingRecognizeRequest {\n  oneof streaming_request {\n    // The `streaming_config` message provides information to the recognizer\n    // that specifies how to process the request.\n    //\n    // The first `StreamingRecognizeRequest` message must contain a\n    // `streaming_config`  message.\n    StreamingRecognitionConfig streaming_config = 1;\n\n    // The audio data to be recognized. Sequential chunks of audio data are sent\n    // in sequential `StreamingRecognizeRequest` messages. The first\n    // `StreamingRecognizeRequest` message must not contain `audio_content` data\n    // and all subsequent `StreamingRecognizeRequest` messages must contain\n    // `audio_content` data. The audio bytes must be encoded as specified in\n    // `RecognitionConfig`. Note: as with all bytes fields, protobuffers use a\n    // pure binary representation (not base64).\n    bytes audio_content = 2 [ctype = CORD];\n  }\n}\n\n// The `StreamingRecognitionConfig` message provides information to the\n// recognizer that specifies how to process the request.\nmessage StreamingRecognitionConfig {\n  // [Required] The `config` message provides information to the recognizer\n  // that specifies how to process the request.\n  RecognitionConfig config = 1;\n\n  // [Optional] If `false` or omitted, the recognizer will perform continuous\n  // recognition (continuing to process audio even if the user pauses speaking)\n  // until the client closes the output stream (gRPC API) or when the maximum\n  // time limit has been reached. Multiple `SpeechRecognitionResult`s with the\n  // `is_final` flag set to `true` may be returned.\n  //\n  // If `true`, the recognizer will detect a single spoken utterance. When it\n  // detects that the user has paused or stopped speaking, it will return an\n  // `END_OF_UTTERANCE` event and cease recognition. It will return no more than\n  // one `SpeechRecognitionResult` with the `is_final` flag set to `true`.\n  bool single_utterance = 2;\n\n  // [Optional] If `true`, interim results (tentative hypotheses) may be\n  // returned as they become available (these interim results are indicated with\n  // the `is_final=false` flag).\n  // If `false` or omitted, only `is_final=true` result(s) are returned.\n  bool interim_results = 3;\n}\n\n// The `RecognitionConfig` message provides information to the recognizer\n// that specifies how to process the request.\nmessage RecognitionConfig {\n  // Audio encoding of the data sent in the audio message. All encodings support\n  // only 1 channel (mono) audio. Only `FLAC` includes a header that describes\n  // the bytes of audio that follow the header. The other encodings are raw\n  // audio bytes with no header.\n  //\n  // For best results, the audio source should be captured and transmitted using\n  // a lossless encoding (`FLAC` or `LINEAR16`). Recognition accuracy may be\n  // reduced if lossy codecs (such as AMR, AMR_WB and MULAW) are used to capture\n  // or transmit the audio, particularly if background noise is present.\n  enum AudioEncoding {\n    // Not specified. Will return result [google.rpc.Code.INVALID_ARGUMENT][].\n    ENCODING_UNSPECIFIED = 0;\n\n    // Uncompressed 16-bit signed little-endian samples.\n    // This is the only encoding that may be used by `AsyncRecognize`.\n    LINEAR16 = 1;\n\n    // This is the recommended encoding for `SyncRecognize` and\n    // `StreamingRecognize` because it uses lossless compression; therefore\n    // recognition accuracy is not compromised by a lossy codec.\n    //\n    // The stream FLAC (Free Lossless Audio Codec) encoding is specified at:\n    // http://flac.sourceforge.net/documentation.html.\n    // Only 16-bit samples are supported.\n    // Not all fields in STREAMINFO are supported.\n    FLAC = 2;\n\n    // 8-bit samples that compand 14-bit audio samples using G.711 PCMU/mu-law.\n    MULAW = 3;\n\n    // Adaptive Multi-Rate Narrowband codec. `sample_rate` must be 8000 Hz.\n    AMR = 4;\n\n    // Adaptive Multi-Rate Wideband codec. `sample_rate` must be 16000 Hz.\n    AMR_WB = 5;\n  }\n\n  // [Required] Encoding of audio data sent in all `RecognitionAudio` messages.\n  AudioEncoding encoding = 1;\n\n  // [Required] Sample rate in Hertz of the audio data sent in all\n  // `RecognitionAudio` messages. Valid values are: 8000-48000.\n  // 16000 is optimal. For best results, set the sampling rate of the audio\n  // source to 16000 Hz. If that's not possible, use the native sample rate of\n  // the audio source (instead of re-sampling).\n  int32 sample_rate = 2;\n\n  // [Optional] The language of the supplied audio as a BCP-47 language tag.\n  // Example: \"en-GB\"  https://www.rfc-editor.org/rfc/bcp/bcp47.txt\n  // If omitted, defaults to \"en-US\". See\n  // [Language Support](/speech/docs/best-practices#language_support) for\n  // a list of the currently supported language codes.\n  string language_code = 3;\n\n  // [Optional] Maximum number of recognition hypotheses to be returned.\n  // Specifically, the maximum number of `SpeechRecognitionAlternative` messages\n  // within each `SpeechRecognitionResult`.\n  // The server may return fewer than `max_alternatives`.\n  // Valid values are `0`-`30`. A value of `0` or `1` will return a maximum of\n  // `1`. If omitted, defaults to `1`.\n  int32 max_alternatives = 4;\n\n  // [Optional] If set to `true`, the server will attempt to filter out\n  // profanities, replacing all but the initial character in each filtered word\n  // with asterisks, e.g. \"f***\". If set to `false` or omitted, profanities\n  // won't be filtered out.\n  bool profanity_filter = 5;\n\n  // [Optional] A means to provide context to assist the speech recognition.\n  SpeechContext speech_context = 6;\n}\n\n// Provides \"hints\" to the speech recognizer to favor specific words and phrases\n// in the results.\nmessage SpeechContext {\n  // [Optional] A list of up to 50 phrases of up to 100 characters each to\n  // provide words and phrases \"hints\" to the speech recognition so that it is\n  // more likely to recognize them.\n  repeated string phrases = 1;\n}\n\n// Contains audio data in the encoding specified in the `RecognitionConfig`.\n// Either `content` or `uri` must be supplied. Supplying both or neither\n// returns [google.rpc.Code.INVALID_ARGUMENT][].\nmessage RecognitionAudio {\n  oneof audio_source {\n    // The audio data bytes encoded as specified in\n    // `RecognitionConfig`. Note: as with all bytes fields, protobuffers use a\n    // pure binary representation, whereas JSON representations use base64.\n    bytes content = 1 [ctype = CORD];\n\n    // URI that points to a file that contains audio data bytes as specified in\n    // `RecognitionConfig`. Currently, only Google Cloud Storage URIs are\n    // supported, which must be specified in the following format:\n    // `gs://bucket_name/object_name` (other URI formats return\n    // [google.rpc.Code.INVALID_ARGUMENT][]). For more information, see\n    // [Request URIs](/storage/docs/reference-uris).\n    string uri = 2;\n  }\n}\n\n// `SyncRecognizeResponse` is the only message returned to the client by\n// `SyncRecognize`. It contains the result as zero or more\n// sequential `RecognizeResponse` messages.\nmessage SyncRecognizeResponse {\n  // [Output-only] Sequential list of transcription results corresponding to\n  // sequential portions of audio.\n  repeated SpeechRecognitionResult results = 2;\n}\n\n// `AsyncRecognizeResponse` is the only message returned to the client by\n// `AsyncRecognize`. It contains the result as zero or more\n// sequential `RecognizeResponse` messages.\nmessage AsyncRecognizeResponse {\n  // [Output-only] Sequential list of transcription results corresponding to\n  // sequential portions of audio.\n  repeated SpeechRecognitionResult results = 2;\n}\n\n// `StreamingRecognizeResponse` is the only message returned to the client by\n// `StreamingRecognize`. It contains the result as zero or more\n// sequential `RecognizeResponse` messages.\nmessage StreamingRecognizeResponse {\n  // Indicates the type of endpointer event.\n  enum EndpointerType {\n    // No endpointer event specified.\n    ENDPOINTER_EVENT_UNSPECIFIED = 0;\n\n    // Speech has been detected in the audio stream.\n    START_OF_SPEECH = 1;\n\n    // Speech has ceased to be detected in the audio stream.\n    END_OF_SPEECH = 2;\n\n    // The end of the audio stream has been reached. and it is being processed.\n    END_OF_AUDIO = 3;\n\n    // This event is only sent when `single_utterance` is `true`. It indicates\n    // that the server has detected the end of the user's speech utterance and\n    // expects no additional speech. Therefore, the server will not process\n    // additional audio. The client should stop sending additional audio data.\n    END_OF_UTTERANCE = 4;\n  }\n\n  // [Output-only] If set, returns a [google.rpc.Status][] message that\n  // specifies the error for the operation.\n  google.rpc.Status error = 1;\n\n  // [Output-only] This repeated list contains zero or more results that\n  // correspond to consecutive portions of the audio currently being processed.\n  // It contains zero or one `is_final=true` result (the newly settled portion),\n  // followed by zero or more `is_final=false` results.\n  repeated StreamingRecognitionResult results = 2;\n\n  // [Output-only] Indicates the lowest index in the `results` array that has\n  // changed. The repeated `SpeechRecognitionResult` results overwrite past\n  // results at this index and higher.\n  int32 result_index = 3;\n\n  // [Output-only] Indicates the type of endpointer event.\n  EndpointerType endpointer_type = 4;\n}\n\n// A speech recognition result corresponding to a portion of the audio that is\n// currently being processed.\n// TODO(gshires): add a comment describing the various repeated interim and\n// alternative results fields.\nmessage StreamingRecognitionResult {\n  // [Output-only] May contain one or more recognition hypotheses (up to the\n  // maximum specified in `max_alternatives`).\n  repeated SpeechRecognitionAlternative alternatives = 1;\n\n  // [Output-only] If `false`, this `SpeechRecognitionResult` represents an\n  // interim result that may change. If `true`, this is the final time the\n  // speech service will return this particular `SpeechRecognitionResult`,\n  // the recognizer will not return any further hypotheses for this portion of\n  // the transcript and corresponding audio.\n  bool is_final = 2;\n\n  // [Output-only] An estimate of the probability that the recognizer will not\n  // change its guess about this interim result. Values range from 0.0\n  // (completely unstable) to 1.0 (completely stable). Note that this is not the\n  // same as `confidence`, which estimates the probability that a recognition\n  // result is correct.\n  // This field is only provided for interim results (`is_final=false`).\n  // The default of 0.0 is a sentinel value indicating stability was not set.\n  float stability = 3;\n}\n\n// A speech recognition result corresponding to a portion of the audio.\nmessage SpeechRecognitionResult {\n  // [Output-only] May contain one or more recognition hypotheses (up to the\n  // maximum specified in `max_alternatives`).\n  repeated SpeechRecognitionAlternative alternatives = 1;\n}\n\n// Alternative hypotheses (a.k.a. n-best list).\nmessage SpeechRecognitionAlternative {\n  // [Output-only] Transcript text representing the words that the user spoke.\n  string transcript = 1;\n\n  // [Output-only] The confidence estimate between 0.0 and 1.0. A higher number\n  // means the system is more confident that the recognition is correct.\n  // This field is typically provided only for the top hypothesis, and only for\n  // `is_final=true` results.\n  // The default of 0.0 is a sentinel value indicating confidence was not set.\n  float confidence = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/README.md",
    "content": "# Google Common Types\nThis package contains definitions of common types for Google APIs.\nAll types defined in this package are suitable for different APIs to\nexchange data, and will never break binary compatibility. They should\nhave design quality comparable to major programming languages like\nJava and C#.\n\nNOTE: Some common types are defined in the package `google.protobuf`\nas they are directly supported by Protocol Buffers compiler and\nruntime. Those types are called Well-Known Types.\n"
  },
  {
    "path": "app/src/main/proto/google/type/color.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\nimport \"google/protobuf/wrappers.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"ColorProto\";\noption java_package = \"com.google.type\";\n\n\n// Represents a color in the RGBA color space. This representation is designed\n// for simplicity of conversion to/from color representations in various\n// languages over compactness; for example, the fields of this representation\n// can be trivially provided to the constructor of \"java.awt.Color\" in Java; it\n// can also be trivially provided to UIColor's \"+colorWithRed:green:blue:alpha\"\n// method in iOS; and, with just a little work, it can be easily formatted into\n// a CSS \"rgba()\" string in JavaScript, as well. Here are some examples:\n//\n// Example (Java):\n//\n//      import com.google.type.Color;\n//\n//      // ...\n//      public static java.awt.Color fromProto(Color protocolor) {\n//        float alpha = protocolor.hasAlpha()\n//            ? protocolor.getAlpha().getValue()\n//            : 1.0;\n//\n//        return new java.awt.Color(\n//            protocolor.getRed(),\n//            protocolor.getGreen(),\n//            protocolor.getBlue(),\n//            alpha);\n//      }\n//\n//      public static Color toProto(java.awt.Color color) {\n//        float red = (float) color.getRed();\n//        float green = (float) color.getGreen();\n//        float blue = (float) color.getBlue();\n//        float denominator = 255.0;\n//        Color.Builder resultBuilder =\n//            Color\n//                .newBuilder()\n//                .setRed(red / denominator)\n//                .setGreen(green / denominator)\n//                .setBlue(blue / denominator);\n//        int alpha = color.getAlpha();\n//        if (alpha != 255) {\n//          result.setAlpha(\n//              FloatValue\n//                  .newBuilder()\n//                  .setValue(((float) alpha) / denominator)\n//                  .build());\n//        }\n//        return resultBuilder.build();\n//      }\n//      // ...\n//\n// Example (iOS / Obj-C):\n//\n//      // ...\n//      static UIColor* fromProto(Color* protocolor) {\n//         float red = [protocolor red];\n//         float green = [protocolor green];\n//         float blue = [protocolor blue];\n//         FloatValue* alpha_wrapper = [protocolor alpha];\n//         float alpha = 1.0;\n//         if (alpha_wrapper != nil) {\n//           alpha = [alpha_wrapper value];\n//         }\n//         return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];\n//      }\n//\n//      static Color* toProto(UIColor* color) {\n//          CGFloat red, green, blue, alpha;\n//          if (![color getRed:&red green:&green blue:&blue alpha:&alpha]) {\n//            return nil;\n//          }\n//          Color* result = [Color alloc] init];\n//          [result setRed:red];\n//          [result setGreen:green];\n//          [result setBlue:blue];\n//          if (alpha <= 0.9999) {\n//            [result setAlpha:floatWrapperWithValue(alpha)];\n//          }\n//          [result autorelease];\n//          return result;\n//     }\n//     // ...\n//\n//  Example (JavaScript):\n//\n//     // ...\n//\n//     var protoToCssColor = function(rgb_color) {\n//        var redFrac = rgb_color.red || 0.0;\n//        var greenFrac = rgb_color.green || 0.0;\n//        var blueFrac = rgb_color.blue || 0.0;\n//        var red = Math.floor(redFrac * 255);\n//        var green = Math.floor(greenFrac * 255);\n//        var blue = Math.floor(blueFrac * 255);\n//\n//        if (!('alpha' in rgb_color)) {\n//           return rgbToCssColor_(red, green, blue);\n//        }\n//\n//        var alphaFrac = rgb_color.alpha.value || 0.0;\n//        var rgbParams = [red, green, blue].join(',');\n//        return ['rgba(', rgbParams, ',', alphaFrac, ')'].join('');\n//     };\n//\n//     var rgbToCssColor_ = function(red, green, blue) {\n//       var rgbNumber = new Number((red << 16) | (green << 8) | blue);\n//       var hexString = rgbNumber.toString(16);\n//       var missingZeros = 6 - hexString.length;\n//       var resultBuilder = ['#'];\n//       for (var i = 0; i < missingZeros; i++) {\n//          resultBuilder.push('0');\n//       }\n//       resultBuilder.push(hexString);\n//       return resultBuilder.join('');\n//     };\n//\n//     // ...\n//\nmessage Color {\n  // The amount of red in the color as a value in the interval [0, 1].\n  float red = 1;\n\n  // The amount of green in the color as a value in the interval [0, 1].\n  float green = 2;\n\n  // The amount of blue in the color as a value in the interval [0, 1].\n  float blue = 3;\n\n  // The fraction of this color that should be applied to the pixel. That is,\n  // the final pixel color is defined by the equation:\n  //\n  //   pixel color = alpha * (this color) + (1.0 - alpha) * (background color)\n  //\n  // This means that a value of 1.0 corresponds to a solid color, whereas\n  // a value of 0.0 corresponds to a completely transparent color. This\n  // uses a wrapper message rather than a simple float scalar so that it is\n  // possible to distinguish between a default value and the value being unset.\n  // If omitted, this color object is to be rendered as a solid color\n  // (as if the alpha value had been explicitly given with a value of 1.0).\n  google.protobuf.FloatValue alpha = 4;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/date.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\noption java_generate_equals_and_hash = true;\noption java_multiple_files = true;\noption java_outer_classname = \"DateProto\";\noption java_package = \"com.google.type\";\n\n\n// Represents a whole calendar date, e.g. date of birth. The time of day and\n// time zone are either specified elsewhere or are not significant. The date\n// is relative to the Proleptic Gregorian Calendar. The day may be 0 to\n// represent a year and month where the day is not significant, e.g. credit card\n// expiration date. The year may be 0 to represent a month and day independent\n// of year, e.g. anniversary date. Related types are [google.type.TimeOfDay][google.type.TimeOfDay]\n// and [google.protobuf.Timestamp][google.protobuf.Timestamp].\nmessage Date {\n  // Year of date. Must be from 1 to 9,999, or 0 if specifying a date without\n  // a year.\n  int32 year = 1;\n\n  // Month of year of date. Must be from 1 to 12.\n  int32 month = 2;\n\n  // Day of month. Must be from 1 to 31 and valid for the year and month, or 0\n  // if specifying a year/month where the day is not sigificant.\n  int32 day = 3;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/dayofweek.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\noption java_generate_equals_and_hash = true;\noption java_multiple_files = true;\noption java_outer_classname = \"DayOfWeekProto\";\noption java_package = \"com.google.type\";\n\n\n// Represents a day of week.\nenum DayOfWeek {\n  // The unspecified day-of-week.\n  DAY_OF_WEEK_UNSPECIFIED = 0;\n\n  // The day-of-week of Monday.\n  MONDAY = 1;\n\n  // The day-of-week of Tuesday.\n  TUESDAY = 2;\n\n  // The day-of-week of Wednesday.\n  WEDNESDAY = 3;\n\n  // The day-of-week of Thursday.\n  THURSDAY = 4;\n\n  // The day-of-week of Friday.\n  FRIDAY = 5;\n\n  // The day-of-week of Saturday.\n  SATURDAY = 6;\n\n  // The day-of-week of Sunday.\n  SUNDAY = 7;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/latlng.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\noption java_generate_equals_and_hash = true;\noption java_multiple_files = true;\noption java_outer_classname = \"LatLngProto\";\noption java_package = \"com.google.type\";\n\n\n// An object representing a latitude/longitude pair. This is expressed as a pair\n// of doubles representing degrees latitude and degrees longitude. Unless\n// specified otherwise, this must conform to the\n// <a href=\"http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf\">WGS84\n// standard</a>. Values must be within normalized ranges.\nmessage LatLng {\n  // The latitude in degrees. It must be in the range [-90.0, +90.0].\n  double latitude = 1;\n\n  // The longitude in degrees. It must be in the range [-180.0, +180.0].\n  double longitude = 2;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/money.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\noption java_multiple_files = true;\noption java_outer_classname = \"MoneyProto\";\noption java_package = \"com.google.type\";\n\n\n// Represents an amount of money with its currency type.\nmessage Money {\n  // The 3-letter currency code defined in ISO 4217.\n  string currency_code = 1;\n\n  // The whole units of the amount.\n  // For example if `currencyCode` is `\"USD\"`, then 1 unit is one US dollar.\n  int64 units = 2;\n\n  // Number of nano (10^-9) units of the amount.\n  // The value must be between -999,999,999 and +999,999,999 inclusive.\n  // If `units` is positive, `nanos` must be positive or zero.\n  // If `units` is zero, `nanos` can be positive, zero, or negative.\n  // If `units` is negative, `nanos` must be negative or zero.\n  // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.\n  int32 nanos = 3;\n}\n"
  },
  {
    "path": "app/src/main/proto/google/type/timeofday.proto",
    "content": "// Copyright (c) 2015, Google Inc.\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\nsyntax = \"proto3\";\n\npackage google.type;\n\noption java_generate_equals_and_hash = true;\noption java_multiple_files = true;\noption java_outer_classname = \"TimeOfDayProto\";\noption java_package = \"com.google.type\";\n\n\n// Represents a time of day. The date and time zone are either not significant\n// or are specified elsewhere. An API may chose to allow leap seconds. Related\n// types are [google.type.Date][google.type.Date] and [google.protobuf.Timestamp][google.protobuf.Timestamp].\nmessage TimeOfDay {\n  // Hours of day in 24 hour format. Should be from 0 to 23. An API may choose\n  // to allow the value \"24:00:00\" for scenarios like business closing time.\n  int32 hours = 1;\n\n  // Minutes of hour of day. Must be from 0 to 59.\n  int32 minutes = 2;\n\n  // Seconds of minutes of the time. Must normally be from 0 to 59. An API may\n  // allow the value 60 if it allows leap-seconds.\n  int32 seconds = 3;\n\n  // Fractions of seconds in nanoseconds. Must be from 0 to 999,999,999.\n  int32 nanos = 4;\n}\n"
  },
  {
    "path": "app/src/main/res/anim/fade_in_slow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:duration=\"@integer/animate_slow\"\n           android:interpolator=\"@android:interpolator/accelerate_quad\"\n           android:propertyName=\"alpha\"\n           android:fromAlpha=\"0\"\n           android:toAlpha=\"1\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/fade_in_slow_delayed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:duration=\"@integer/animate_slow\"\n           android:fromAlpha=\"0\"\n           android:interpolator=\"@android:interpolator/accelerate_quad\"\n           android:propertyName=\"alpha\"\n           android:startOffset=\"@integer/animate_slow_offset\"\n           android:toAlpha=\"1\"/>\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/none.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n           android:duration=\"0\"\n           android:interpolator=\"@android:interpolator/accelerate_quad\"\n           android:propertyName=\"alpha\"\n           android:valueFrom=\"1.0\"\n           android:valueTo=\"0.0\"/>\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_in_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromXDelta=\"50%p\"\n        android:toXDelta=\"0\" />\n\n    <alpha\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromAlpha=\"0.0\"\n        android:toAlpha=\"1.0\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_out_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"-50%p\" />\n\n    <alpha\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromAlpha=\"1.0\"\n        android:toAlpha=\"0.0\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/animator/fade_in_slow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <objectAnimator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                    android:duration=\"@integer/animate_slow\"\n                    android:interpolator=\"@android:interpolator/accelerate_quad\"\n                    android:propertyName=\"alpha\"\n                    android:valueFrom=\"0\"\n                    android:valueTo=\"1\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/animator/fade_in_slow_delayed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <objectAnimator\n        android:duration=\"0\"\n        android:propertyName=\"alpha\"\n        android:valueFrom=\"1.0\"\n        android:valueTo=\"0.0\"/>\n\n    <objectAnimator\n        android:duration=\"@integer/animate_slow\"\n        android:interpolator=\"@android:interpolator/accelerate_quad\"\n        android:propertyName=\"alpha\"\n        android:startOffset=\"@integer/animate_slow_offset\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"1\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/animator/none.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <objectAnimator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                    android:duration=\"0\"\n                    android:interpolator=\"@android:interpolator/accelerate_quad\"\n                    android:propertyName=\"alpha\"\n                    android:valueFrom=\"1.0\"\n                    android:valueTo=\"0.0\"/>\n\n</set>"
  },
  {
    "path": "app/src/main/res/drawable/chevron.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/ic_chevron_right\"/>\n    <item android:drawable=\"@drawable/ic_chevron_right_light\"/>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_logo_saiy_vector.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector android:height=\"24dp\" android:viewportHeight=\"419.92\"\n    android:viewportWidth=\"765.66\" android:width=\"60dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M167.7,157.5a232.2,232.2 0,0 0,-25.5 -24.3q-14.1,-11.5 -27.7,-23c-9,-7.6 -17.7,-15.5 -25.6,-23.5A107,107 0,0 1,69.8 61.4C65.1,52.9 62.7,45 62.6,38s1.3,-13.1 4.2,-18.2A30.2,30.2 0,0 1,79.5 7.9a41.2,41.2 0,0 1,19 -4.3,1.8 1.8,0 0,0 0,-3.6A134.5,134.5 0,0 0,63.3 4.8a95,95 0,0 0,-31.5 15A80.8,80.8 0,0 0,9 46.1C3,56.8 0,69.7 0,84.4a71.6,71.6 0,0 0,7.4 32.3,124.3 124.3,0 0,0 18.8,27.4 230.9,230.9 0,0 0,25.5 24.3q14,11.5 27.9,23.2c9.1,7.8 17.8,15.7 25.7,23.5a116.8,116.8 0,0 1,19.3 25.2c4.4,8.5 6.7,16.4 6.8,23.4s-1.3,13.1 -4.2,18.2a30.1,30.1 0,0 1,-12.9 11.9,41.6 41.6,0 0,1 -18.8,4.3 1.8,1.8 0,1 0,0 3.6,135 135,0 0,0 35,-4.8 94.6,94.6 0,0 0,31.7 -15A81.6,81.6 0,0 0,185 255.9c5.9,-10.6 9,-23.6 9,-38.6A71.7,71.7 0,0 0,186.5 185,124.8 124.8,0 0,0 167.7,157.5Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M763.9,86.9H691.6a1.8,1.8 0,0 0,0 3.6,14.3 14.3,0 0,1 11.7,6.9c9.6,15.6 24.1,56.2 24.1,56.2a1.8,1.8 0,0 0,3.3 0l28,-57.3c0.1,-0.2 0.3,-0.6 0.4,-0.9 1,-2.5 2.3,-4.8 4.8,-4.8a1.8,1.8 0,1 0,0 -3.6Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M629,95.3a4.2,4.2 0,0 1,0.8 -3.4c1,-1.2 2.9,-1.4 4.3,-1.4a1.8,1.8 0,0 0,0 -3.6H551.8a1.8,1.8 0,1 0,0 3.6c4.6,0 7.7,2.1 9.4,6.5l91.2,210.3a1.8,1.8 0,0 0,1.6 1.1h0a1.8,1.8 0,0 0,1.6 -1l36.2,-75.9a1.8,1.8 0,0 0,0 -1.6C674.7,195.4 630,103.8 629,95.3Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M536.8,298.6a1.8,1.8 0,0 0,-1.8 -1.8,8.4 8.4,0 0,1 -8.9,-8.9L526.1,145.2a8.4,8.4 0,0 1,8.9 -8.9,1.8 1.8,0 0,0 0,-3.6L446.4,132.8a1.8,1.8 0,0 0,0 3.6,8.4 8.4,0 0,1 8.8,8.9L455.3,288a8.4,8.4 0,0 1,-8.8 8.9,1.8 1.8,0 0,0 0,3.6L535,300.4A1.8,1.8 0,0 0,536.8 298.6ZM446.4,134.6h0.3l0,0.1C446.6,134.6 446.5,134.6 446.4,134.6ZM490.7,134.6h0Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M411.5,296.4a8.4,8.4 0,0 1,-8.9 -8.9v-141a44.2,44.2 0,0 0,-6.8 -23.5,66.5 66.5,0 0,0 -18.1,-19 89.9,89.9 0,0 0,-26.4 -12.6,112.2 112.2,0 0,0 -32.4,-4.6 1.8,1.8 0,0 0,-0.2 3.6c4.9,0.5 8.2,5 10.1,13.7a164.4,164.4 0,0 1,3 33.5V260.7c0,14.2 2.3,24 6.9,30.1S352,300 364.1,300h47.5a1.8,1.8 0,0 0,0 -3.6Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M330,278.9h0a1.8,1.8 0,0 0,-0.7 0.2h0a1.8,1.8 0,0 0,-0.3 0.2,20.2 20.2,0 0,1 -11.5,4.3c-0.4,0 -0.8,0 -1.1,0a18.2,18.2 0,0 1,-10.8 -3.7c-3.8,-2.8 -7.1,-7.3 -9.8,-13.3s-4.1,-14.6 -4.1,-25c0,-12.8 1.3,-23.3 3.9,-31.2a68.3,68.3 0,0 1,9.7 -19.5,74.7 74.7,0 0,1 12.8,-13.3 1.8,1.8 0,0 0,-1.1 -3.2,1.8 1.8,0 0,0 -0.5,0.1l-29.5,9.2c-7.4,2.3 -14.7,4.9 -21.9,7.9a86.6,86.6 0,0 0,-19.5 11.3,67.6 67.6,0 0,0 -14.9,16c-4.1,6.2 -6.7,13.8 -7.5,22.5 -1.2,9.9 -0.1,18.8 3.1,26.4A50.8,50.8 0,0 0,240 287a53.9,53.9 0,0 0,21.4 11,70.7 70.7,0 0,0 18.3,2.3c2.6,0 5.2,-0.1 7.8,-0.4 17,-1.7 31.6,-7.7 43.5,-17.8a1.8,1.8 0,0 0,-1 -3.3ZM313.9,178.6l0.4,-0.2c-0.4,0.3 -0.7,0.7 -1.1,1ZM329.8,280.8 L329.8,280.8c0.1,0 0.1,-0.1 0.2,-0.1Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M159,65.8m-34.8,0a34.8,34.8 0,1 1,69.7 0a34.8,34.8 0,1 1,-69.7 0\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M117.6,4.8c2.1,0.5 47.9,9.5 44.4,50L193.9,65S196.5,14 118,1.3c-0.1,0 -1.9,-0.3 -2,1.5A1.9,1.9 0,0 0,117.6 4.8Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M35.9,235.9m-34.8,0a34.8,34.8 0,1 1,69.7 0a34.8,34.8 0,1 1,-69.7 0\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M77.3,296.9c-2.1,-0.5 -47.9,-9.5 -44.4,-50L1,236.7s-2.7,51 75.9,63.7c0.1,0 1.9,0.3 2,-1.5A1.9,1.9 0,0 0,77.3 296.9Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M261.4,145.8m-4.4,34.6a34.8,34.8 62.6,1 1,8.8 -69.1a34.8,34.8 127.6,1 1,-8.8 69.1\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M306.7,90.8c-1,0.2 -2.8,0.5 -4.5,0.9 -13.9,2.8 -41.1,12 -42.4,42.9l-32.8,6s3.5,-47.1 74.6,-53.1a41.8,41.8 0,0 1,5 -0.2,1.7 1.7,0 0,1 1.9,1.7A1.9,1.9 0,0 1,306.7 90.8Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M563.1,384.6m-9.4,33.5a34.8,34.8 73.7,1 1,18.8 -67.1a34.8,34.8 133.7,1 1,-18.8 67.1\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M633,361.2c-1,1.9 -22,43.5 -60.2,29.3l-18.4,27.9s48.4,16.4 81.8,-55.8a1.6,1.6 0,0 0,-0.9 -2.4A1.9,1.9 0,0 0,633 361.2Z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M490.7,65.8m-34.8,0a34.8,34.8 0,1 1,69.7 0a34.8,34.8 0,1 1,-69.7 0\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/side_nav_bar.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <gradient\n        android:angle=\"135\"\n        android:centerColor=\"@color/colorPrimary\"\n        android:endColor=\"@color/colorPrimaryDark\"\n        android:startColor=\"@color/colorFade\"\n        android:type=\"linear\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_camera.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_gallery.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_manage.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_send.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_share.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v21/ic_menu_slideshow.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_home_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<android.support.v4.widget.DrawerLayout\n    android:id=\"@+id/drawer_layout\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:openDrawer=\"start\">\n\n    <include\n        layout=\"@layout/app_bar_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n    <android.support.design.widget.NavigationView\n        android:id=\"@+id/nav_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        android:fitsSystemWindows=\"true\"\n        app:headerLayout=\"@layout/nav_header_main\"\n        app:menu=\"@menu/activity_main_drawer\"/>\n\n</android.support.v4.widget.DrawerLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/app_bar_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<android.support.design.widget.CoordinatorLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\"ai.saiy.android.ui.activity.ActivityHome\">\n\n    <android.support.design.widget.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/AppTheme.AppBarOverlay\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimary\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\">\n\n            <ProgressBar\n                android:id=\"@+id/progress\"\n                android:layout_width=\"32dp\"\n                android:layout_height=\"32dp\"\n                android:layout_gravity=\"center_vertical|end\"\n                android:indeterminate=\"true\"\n                android:visibility=\"invisible\"/>\n\n        </android.support.v7.widget.Toolbar>\n\n    </android.support.design.widget.AppBarLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fragmentContent\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:paddingTop=\"?attr/actionBarSize\"/>\n\n</android.support.design.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/assist_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2014 The Android Open Source Project\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          http://www.apache.org/licenses/LICENSE-2.0\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<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          android:layout_width=\"wrap_content\"\n          android:layout_height=\"wrap_content\"\n          android:layout_margin=\"10dp\"\n          android:text=\"Nothing to see here\"\n          android:textAppearance=\"?android:attr/textAppearanceMedium\"/>"
  },
  {
    "path": "app/src/main/res/layout/cardview_bugs_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout android:id=\"@+id/bugsItemContainer\"\n              style=\"@style/card_view_item_single\"\n              xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:card_view=\"http://schemas.android.com/apk/res-auto\"\n              xmlns:tools=\"http://schemas.android.com/tools\"\n              android:layout_width=\"fill_parent\"\n              android:layout_height=\"wrap_content\"\n              android:layout_weight=\"1\"\n              android:orientation=\"horizontal\">\n\n    <android.support.v7.widget.CardView\n        android:id=\"@+id/cardView\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_weight=\"1.0\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:foreground=\"?android:attr/selectableItemBackground\"\n        android:minHeight=\"?android:attr/listPreferredItemHeightLarge\"\n        android:orientation=\"horizontal\"\n        card_view:cardCornerRadius=\"4dp\"\n        card_view:cardElevation=\"4dp\"\n        card_view:cardUseCompatPadding=\"true\">\n\n        <LinearLayout\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:duplicateParentState=\"true\"\n            android:orientation=\"horizontal\">\n\n            <ImageView\n                android:id=\"@+id/iconMain\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical|start\"\n                android:layout_marginLeft=\"@dimen/activity_vertical_margin\"\n                android:layout_marginStart=\"@dimen/activity_vertical_margin\"\n                android:duplicateParentState=\"true\"\n                tools:src=\"@drawable/ic_bug\"/>\n\n            <LinearLayout\n                android:layout_width=\"0dip\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginBottom=\"@dimen/activity_horizontal_margin\"\n                android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n                android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n                android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n                android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n                android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n                android:layout_weight=\"1.0\"\n                android:duplicateParentState=\"true\"\n                android:orientation=\"vertical\">\n\n                <TextView\n                    android:id=\"@+id/title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"@dimen/activity_horizontal_margin\"\n                    android:duplicateParentState=\"true\"\n                    android:fontFamily=\"sans-serif-smallcaps\"\n                    android:textColor=\"@color/colorPrimaryDark\"\n                    android:textStyle=\"bold\"\n                    tools:text=\"@string/bug_title_offline_recognition\"/>\n\n                <TextView\n                    android:id=\"@+id/subtitle\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:duplicateParentState=\"true\"\n                    android:fontFamily=\"sans-serif-light\"\n                    android:textColor=\"@color/colorSubtitle\"\n                    android:textSize=\"12sp\"\n                    android:textStyle=\"italic\"\n                    android:visibility=\"visible\"\n                    tools:text=\"@string/bug_content_offline_recognition\"/>\n            </LinearLayout>\n\n            <ImageView\n                android:id=\"@+id/iconExtra\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical|end\"\n                android:layout_marginEnd=\"@dimen/activity_vertical_margin\"\n                android:layout_marginRight=\"@dimen/activity_vertical_margin\"\n                android:duplicateParentState=\"true\"\n                tools:src=\"@drawable/chevron\"/>\n        </LinearLayout>\n    </android.support.v7.widget.CardView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_bugs_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout\n    android:id=\"@+id/layout_bugs_fragment_main\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:context=\"ai.saiy.android.ui.activity.ActivityHome\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:duplicateParentState=\"true\"\n        android:minHeight=\"?android:attr/listPreferredItemHeight\"\n        android:orientation=\"horizontal\"\n        android:paddingBottom=\"4dp\">\n\n        <EditText\n            android:id=\"@+id/etCommand\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n            android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n            android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n            android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n            android:layout_toLeftOf=\"@+id/ibRun\"\n            android:layout_toStartOf=\"@+id/ibRun\"\n            android:fontFamily=\"sans-serif-light\"\n            android:hint=\"@string/hint_bugs_command\"\n            android:imeActionId=\"@integer/ime_go\"\n            android:imeActionLabel=\"@string/menu_run\"\n            android:imeOptions=\"actionGo\"\n            android:inputType=\"text\"\n            android:lines=\"1\"\n            android:minHeight=\"?android:attr/listPreferredItemHeight\"\n            android:textColorHint=\"@color/colorFade\"\n            android:textSize=\"14sp\"/>\n\n\n        <ImageButton\n            android:id=\"@+id/ibRun\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentEnd=\"true\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n            android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n            android:contentDescription=\"@string/ac_button_done\"\n            android:elevation=\"4dp\"\n            android:minHeight=\"?android:attr/listPreferredItemHeight\"\n            app:srcCompat=\"@drawable/ic_function\"/>\n\n    </RelativeLayout>\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/layout_bugs_fragment_recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"vertical\"/>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_chooser_song_recognition.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<android.support.constraint.LinearConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                                                   xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                                                   android:orientation=\"horizontal\"\n                                                   android:layout_width=\"match_parent\"\n                                                   android:layout_height=\"match_parent\"\n                                                   android:id=\"@+id/linearConstraintLayout\">\n\n\n    <ImageView\n        android:src=\"@android:drawable/sym_def_app_icon\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:id=\"@+id/imageView3\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/linearConstraintLayout\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginStart=\"16dp\"\n        app:layout_constraintTop_toTopOf=\"@+id/linearConstraintLayout\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/linearConstraintLayout\"/>\n\n    <TextView\n        android:text=\"TextView\"\n        android:layout_width=\"58dp\"\n        android:layout_height=\"16dp\"\n        android:id=\"@+id/textView3\"\n        app:layout_constraintLeft_toRightOf=\"@+id/imageView3\"\n        android:layout_marginLeft=\"24dp\"\n        android:layout_marginStart=\"24dp\"\n        app:layout_constraintTop_toTopOf=\"@+id/linearConstraintLayout\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/linearConstraintLayout\"/>\n\n    <CheckBox\n        android:text=\"CheckBox\"\n        android:layout_width=\"94dp\"\n        android:layout_height=\"32dp\"\n        android:id=\"@+id/checkBox2\"\n        app:layout_constraintTop_toTopOf=\"@+id/linearConstraintLayout\"\n        app:layout_constraintRight_toRightOf=\"@+id/linearConstraintLayout\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/linearConstraintLayout\"/>\n</android.support.constraint.LinearConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_common_fragment_parent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<android.support.constraint.ConstraintLayout\n    android:id=\"@+id/layout_common_fragment_main\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:context=\"ai.saiy.android.ui.activity.ActivityHome\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/layout_common_fragment_recycler_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:scrollbars=\"vertical\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"/>\n\n</android.support.constraint.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_item_ui_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout android:id=\"@+id/itemContainer\"\n              style=\"@style/list_item_single\"\n              xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:tools=\"http://schemas.android.com/tools\"\n              android:layout_width=\"fill_parent\"\n              android:layout_height=\"?android:attr/listPreferredItemHeight\"\n              android:layout_weight=\"1\"\n              android:background=\"?android:attr/selectableItemBackground\"\n              android:clickable=\"true\"\n              android:focusable=\"true\"\n              android:minHeight=\"64dp\"\n              android:orientation=\"horizontal\">\n\n    <ImageView\n        android:id=\"@+id/iconMain\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical|start\"\n        android:layout_marginLeft=\"@dimen/activity_vertical_margin\"\n        android:layout_marginStart=\"@dimen/activity_vertical_margin\"\n        android:duplicateParentState=\"true\"\n        tools:src=\"@mipmap/ic_launcher\"/>\n\n    <LinearLayout\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_weight=\"1.0\"\n        android:duplicateParentState=\"true\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:fontFamily=\"sans-serif-smallcaps\"\n            android:textColor=\"@color/colorFade\"\n            android:textStyle=\"bold\"\n            tools:text=\"@string/menu_memory_usage\"/>\n\n        <!--android:textColor=\"@color/colorFadeLight\"-->\n\n        <TextView\n            android:id=\"@+id/subtitle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:fontFamily=\"sans-serif-light\"\n            android:textColor=\"@color/colorSubtitle\"\n            android:textSize=\"12sp\"\n            android:textStyle=\"italic\"\n            android:visibility=\"visible\"\n            tools:text=\"@string/menu_tap_configure\"/>\n    </LinearLayout>\n\n    <ImageView\n        android:id=\"@+id/iconExtra\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical|end\"\n        android:layout_marginEnd=\"@dimen/activity_vertical_margin\"\n        android:layout_marginRight=\"@dimen/activity_vertical_margin\"\n        android:duplicateParentState=\"true\"\n        tools:src=\"@drawable/chevron\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/memory_dialog_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/memorySeekBarText\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:text=\"@string/memory_usage_text\"\n        android:textColor=\"@color/colorPrimary\"/>\n\n    <SeekBar\n        android:id=\"@+id/memorySeekBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginBottom=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:defaultValue=\"5\"\n        android:max=\"14\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/nav_header_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/nav_header_height\"\n    android:background=\"@drawable/side_nav_bar\"\n    android:gravity=\"bottom\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:theme=\"@style/ThemeOverlay.AppCompat.Dark\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"@dimen/nav_header_vertical_spacing\"\n        app:srcCompat=\"@drawable/ic_account_circle\"/>\n\n    <ImageView\n        android:id=\"@+id/imageView2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"@dimen/nav_header_vertical_spacing\"\n        app:srcCompat=\"@drawable/ic_logo_saiy_vector\"/>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/pause_detection_dialog_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/pauseSeekBarText\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:text=\"@string/pause_detection_text\"\n        android:textColor=\"@color/colorPrimary\"/>\n\n    <SeekBar\n        android:id=\"@+id/pauseSeekBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginBottom=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:defaultValue=\"2\"\n        android:max=\"5\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/tts_volume_dialog_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/volumeSeekBarText\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:text=\"@string/media_stream\"\n        android:textColor=\"@color/colorPrimary\"/>\n\n    <SeekBar\n        android:id=\"@+id/volumeSeekBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginBottom=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginEnd=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginStart=\"@dimen/activity_horizontal_margin\"\n        android:layout_marginTop=\"@dimen/activity_horizontal_margin\"\n        android:defaultValue=\"4\"\n        android:max=\"8\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/menu/activity_main_drawer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <group android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_home\"\n            android:checked=\"true\"\n            android:icon=\"@drawable/ic_home_variant\"\n            android:title=\"@string/title_home\"/>\n        <item\n            android:id=\"@+id/nav_settings\"\n            android:icon=\"@drawable/ic_cog\"\n            android:title=\"@string/title_settings\"/>\n        <item\n            android:id=\"@+id/nav_customisation\"\n            android:icon=\"@drawable/ic_fingerprint\"\n            android:title=\"@string/title_customisation\"/>\n        <item\n            android:id=\"@+id/nav_advanced_settings\"\n            android:icon=\"@drawable/ic_pill\"\n            android:title=\"@string/title_advanced\"/>\n        <item\n            android:id=\"@+id/nav_super_user\"\n            android:icon=\"@drawable/ic_linux\"\n            android:title=\"@string/title_superuser\"/>\n        <item\n            android:id=\"@+id/nav_about\"\n            android:icon=\"@drawable/ic_info\"\n            android:title=\"@string/title_about\"/>\n    </group>\n\n    <item android:title=\"@string/title_communicate\">\n        <menu>\n            <item\n                android:id=\"@+id/nav_share\"\n                android:icon=\"@drawable/ic_menu_share\"\n                android:title=\"@string/title_share\"/>\n            <item\n                android:id=\"@+id/nav_feedback\"\n                android:icon=\"@drawable/ic_thumbs_up_down\"\n                android:title=\"@string/title_feedback\"/>\n        </menu>\n    </item>\n\n    <item android:title=\"@string/title_social\">\n        <menu>\n            <item\n                android:id=\"@+id/nav_twitter\"\n                android:icon=\"@drawable/ic_twitter\"\n                android:title=\"@string/title_twitter\"/>\n            <item\n                android:id=\"@+id/nav_google_plus\"\n                android:icon=\"@drawable/ic_google_plus\"\n                android:title=\"@string/title_google_plus\"/>\n            <item\n                android:id=\"@+id/nav_forum\"\n                android:icon=\"@drawable/ic_forum\"\n                android:title=\"@string/title_forum\"/>\n        </menu>\n    </item>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_main.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n      xmlns:tools=\"http://schemas.android.com/tools\"\n      tools:context=\"ai.saiy.android.ui.activity.ActivityHome\">\n    <item\n        android:id=\"@+id/action_power\"\n        android:icon=\"@drawable/ic_power\"\n        android:orderInCategory=\"100\"\n        android:title=\"@string/action_power\"\n        app:showAsAction=\"always\"/>\n</menu>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n    <!--<color name=\"colorPrimary\">#5239FE</color>-->\n    <!--<color name=\"colorPrimaryDark\">#462cf9</color>-->\n    <!--<color name=\"colorFade\">#6253FE</color>-->\n    <!--<color name=\"colorFadeLight\">#7467f9</color>-->\n\n    <color name=\"colorPrimary\">#607d8b</color>\n    <color name=\"colorPrimaryDark\">#546e7a</color>\n    <color name=\"colorFade\">#78909c</color>\n    <color name=\"colorTint\">#ECEFF1</color>\n    <color name=\"colorAccent\">#FF4081</color>\n    <color name=\"colorAccentLight\">#FF80AB</color>\n    <color name=\"colorAccentDark\">#F50057</color>\n    <color name=\"colorSubtitle\">#4c4c4c</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"nav_header_vertical_spacing\">16dp</dimen>\n    <dimen name=\"nav_header_height\">160dp</dimen>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/drawables.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n    <item name=\"ic_menu_camera\" type=\"drawable\">@android:drawable/ic_menu_camera</item>\n    <item name=\"ic_menu_gallery\" type=\"drawable\">@android:drawable/ic_menu_gallery</item>\n    <item name=\"ic_menu_slideshow\" type=\"drawable\">@android:drawable/ic_menu_slideshow</item>\n    <item name=\"ic_menu_manage\" type=\"drawable\">@android:drawable/ic_menu_manage</item>\n    <item name=\"ic_menu_share\" type=\"drawable\">@android:drawable/ic_menu_share</item>\n    <item name=\"ic_menu_send\" type=\"drawable\">@android:drawable/ic_menu_send</item>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n    <integer name=\"animate_slow\">925</integer>\n    <integer name=\"animate_slow_offset\">200</integer>\n    <integer name=\"ime_go\">99</integer>\n    <integer name=\"hash_version\">191925</integer>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n\n    <!-- SPEECH STRINGS -->\n\n    <!-- PLEASE NOTE -->\n    <!-- Specific errors should only have one entry, so a user can 'quote' them in a bug report.\n    Or at the very least convey the error type so the issue can be easily identified -->\n\n    <!-- License Strings -->\n    <string name=\"google_play_services\" translatable=\"false\">Google Play Services</string>\n    <string name=\"license_android_open_source\" translatable=\"false\">The Android Open Source Project</string>\n    <string name=\"gson\" translatable=\"false\">GSON</string>\n    <string name=\"license_google_inc\" translatable=\"false\">Copyright © Google Inc.</string>\n    <string name=\"volley\" translatable=\"false\">Volley</string>\n    <string name=\"license_volley\" translatable=\"false\">Xiaoke Zhang &amp; The Android Open Source Project</string>\n    <string name=\"two_forty_four\" translatable=\"false\">Two Forty Four a.m.</string>\n    <string name=\"license_two_forty_four\" translatable=\"false\">Copyright © 2012 two forty four a.m. LLC</string>\n    <string name=\"speechutils\" translatable=\"false\">Speechutils</string>\n    <string name=\"license_kaarel_kaljurand\" translatable=\"false\">Copyright © Kaarel Kaljurand</string>\n    <string name=\"microsoft_translator\" translatable=\"false\">Microsoft Translator</string>\n    <string name=\"license_microsoft_translator\" translatable=\"false\">Jonathan Griggs</string>\n    <string name=\"apache_commons\" translatable=\"false\">Apache Commons</string>\n    <string name=\"license_apache_commons\" translatable=\"false\">Copyright © Apache Software Foundation</string>\n    <string name=\"simmetrics\" translatable=\"false\">Simmetrics</string>\n    <string name=\"license_simmetrics\" translatable=\"false\">Copyright © 2014 - 2016 Simmetrics Authors</string>\n    <string name=\"nuance_speechkit\" translatable=\"false\">Nuance Speechkit</string>\n    <string name=\"license_nuance_speechkit\" translatable=\"false\">Copyright © Nuance Inc. 2002 Jean-Marc Valin &amp; Wimba S.A</string>\n    <string name=\"guava\" translatable=\"false\">Guava</string>\n    <string name=\"license_guava\" translatable=\"false\">Copyright © 2011 The Guava Authors</string>\n    <string name=\"microsoft_cognitive\" translatable=\"false\">Microsoft Cognitive Services</string>\n    <string name=\"license_microsoft_cognitive\" translatable=\"false\">Copyright © Microsoft Corporation</string>\n    <string name=\"api_ai\" translatable=\"false\">API AI</string>\n    <string name=\"license_api_ai\" translatable=\"false\">Copyright © 2014 Speaktoit, Inc.</string>\n    <string name=\"simple_xml\" translatable=\"false\">Simple XML</string>\n    <string name=\"license_simple_xml\" translatable=\"false\">Copyright © Niall Gallagher</string>\n    <string name=\"material_icons\" translatable=\"false\">Material Design Icons</string>\n    <string name=\"license_material_icons\" translatable=\"false\">Copyright © 2014, Austin Andrews</string>\n    <string name=\"material_dialogs\" translatable=\"false\">Material Dialogs</string>\n    <string name=\"license_material_dialogs\" translatable=\"false\">Copyright © 2014-2016 Aidan Michael Follestad</string>\n    <string name=\"pocketsphinx\" translatable=\"false\">Pocketsphinx</string>\n    <string name=\"license_pocketsphinx\" translatable=\"false\">Copyright © 2013-2015, Alpha Cephei Inc. All rights reserved.</string>\n    <string name=\"sound_bible\" translatable=\"false\">Sound Bible</string>\n    <string name=\"license_sound_bible\" translatable=\"false\">Credit Mike Koenig @ Soundbible.com</string>\n\n    <!-- App Strings -->7\n\n    <!-- Preserve HTML -->\n    <string name=\"content_disclaimer\" translatable=\"true\">here is the application disclaimer.</string>\n\n    <string name=\"content_developer_note\" translatable=\"true\">here is the developer note</string>\n    <string name=\"content_whats_new\" translatable=\"true\">here is the what\\'s new content</string>\n    <string name=\"content_supported_languages\" translatable=\"true\">here is the supported languages text</string>\n    <string name=\"content_unknown_command\" translatable=\"true\">When a command is not recognised</string>\n\n    <string name=\"navigation_drawer_open\" translatable=\"true\">Open navigation drawer</string>\n    <string name=\"navigation_drawer_close\" translatable=\"true\">Close navigation drawer</string>\n    <string name=\"app_name\" translatable=\"false\">Saiy</string>\n    <string name=\"control_saiy_permission_label\" translatable=\"true\">Remotely control Saiy features</string>\n    <string name=\"control_saiy_permission_description\" translatable=\"true\">control certain features of Saiy. These features include\n        the recording of audio, passing of this speech data to the remote application and varying the type of voice engine used.</string>\n    <string name=\"error_missing_permissions\" translatable=\"true\">Your application is missing permission.CONTROL_SAIY</string>\n    <string name=\"tile_label\" translatable=\"true\">Saiy Recognition</string>\n    <string name=\"accessibility_description\" translatable=\"true\">Read and announce notification content.\n        Interact with user input parameters.</string>\n    <string name=\"accessibility_enable\" translatable=\"true\">Please enable the Say accessibility service to use\n        this feature.</string>\n    <string name=\"error\" translatable=\"true\">error</string>\n    <string name=\"enabled\" translatable=\"true\">Enabled</string>\n    <string name=\"disabled\" translatable=\"true\">Disabled</string>\n    <string name=\"success\" translatable=\"true\">success</string>\n    <string name=\"failed\" translatable=\"true\">failed</string>\n    <string name=\"chooser_share_via\" translatable=\"true\">Share via</string>\n    <string name=\"chooser_email_via\" translatable=\"true\">Email via</string>\n    <string name=\"feedback\" translatable=\"true\">feedback</string>\n    <string name=\"model\" translatable=\"true\">model</string>\n    <string name=\"manufacturer\" translatable=\"true\">manufacturer</string>\n    <string name=\"android\" translatable=\"false\">Android</string>\n    <string name=\"locale\" translatable=\"false\">locale</string>\n    <string name=\"save\" translatable=\"true\">save</string>\n    <string name=\"close\" translatable=\"true\">close</string>\n    <string name=\"engine\" translatable=\"true\">engine</string>\n    <string name=\"ask_repeat\" translatable=\"true\">Ask you to repeat</string>\n    <string name=\"state_command_unknown\" translatable=\"true\">State command unknown</string>\n    <string name=\"menu_send_to\" translatable=\"true\">Send to</string>\n    <string name=\"menu_application_disclaimer\" translatable=\"true\">Application Disclaimer</string>\n    <string name=\"menu_developer_note\" translatable=\"true\">Developer Note</string>\n    <string name=\"menu_whats_new\" translatable=\"true\">\"What's New?\"</string>\n    <string name=\"menu_accept\" translatable=\"true\">Accept</string>\n    <string name=\"menu_uninstall\" translatable=\"true\">Uninstall</string>\n    <string name=\"menu_lets_do_it\" translatable=\"true\">\"Let's do it!\"</string>\n    <string name=\"menu_excited\" translatable=\"true\">\"I'm so excited!!\"</string>\n    <string name=\"menu_select\" translatable=\"true\">\"Select\"</string>\n    <string name=\"menu_supported_languages\" translatable=\"true\">Supported Languages</string>\n    <string name=\"menu_unknown_commands\" translatable=\"true\">Unknown Commands</string>\n    <string name=\"custom_intro_text\" translatable=\"true\">How should I address you?</string>\n    <string name=\"custom_intro_hint\" translatable=\"true\">I\\'m listening your greatness</string>\n    <string name=\"custom_intro_checkbox_title\" translatable=\"true\">Use randomly</string>\n    <string name=\"silence\" translatable=\"true\">silence</string>\n    <string name=\"male\" translatable=\"true\">male</string>\n    <string name=\"female\" translatable=\"true\">female</string>\n    <string name=\"clear\" translatable=\"true\">clear</string>\n    <string name=\"seconds\" translatable=\"true\">seconds</string>\n    <string name=\"second\" translatable=\"true\">second</string>\n    <string name=\"minutes\" translatable=\"true\">minutes</string>\n    <string name=\"minute\" translatable=\"true\">minute</string>\n    <string name=\"text_default\" translatable=\"true\">default</string>\n    <string name=\"provider_default\" translatable=\"true\">provider default</string>\n    <string name=\"tts_gender_text\" translatable=\"true\">Preferred gender when available</string>\n    <string name=\"hotword_intro_text\" translatable=\"true\">Select hotword configuration</string>\n    <string name=\"blacklist_intro_text\" translatable=\"true\">Select apps to blacklist</string>\n    <string name=\"blacklist_no_apps\" translatable=\"true\">Currently, no installed applications can communicate with Saiy</string>\n\n    <!-- Notification channels -->\n    <string name=\"menu_not_channel_permanent\" translatable=\"true\">\"Permanent\"</string>\n    <string name=\"menu_not_channel_permanent_description\" translatable=\"true\">\"Blar\"</string>\n    <string name=\"menu_not_channel_interaction\" translatable=\"true\">\"Interaction\"</string>\n    <string name=\"menu_not_channel_interaction_description\" translatable=\"true\">\"Blar.\"</string>\n    <string name=\"menu_not_channel_information\" translatable=\"true\">\"Information\"</string>\n    <string name=\"menu_not_channel_information_description\" translatable=\"true\">\"Blar\"</string>\n    <string name=\"menu_not_channel_priority\" translatable=\"true\">\"Priority\"</string>\n    <string name=\"menu_not_channel_priority_description\" translatable=\"true\">\"Blar\"</string>\n\n    <!-- UI Strings -->\n    <string name=\"menu_tap_begin\" translatable=\"true\">tap to begin</string>\n    <string name=\"menu_tap_view\" translatable=\"true\">tap to view</string>\n    <string name=\"menu_tap_topics\" translatable=\"true\">tap for topics</string>\n    <string name=\"menu_tap_contribute\" translatable=\"true\">tap to contribute</string>\n    <string name=\"menu_tap_create\" translatable=\"true\">tap to create</string>\n    <string name=\"menu_tap_options\" translatable=\"true\">tap for options</string>\n    <string name=\"menu_tap_toggle\" translatable=\"true\">tap to toggle</string>\n    <string name=\"menu_tap_set\" translatable=\"true\">tap to set</string>\n    <string name=\"menu_tap_configure\" translatable=\"true\">tap to configure</string>\n    <string name=\"menu_tap_tweak\" translatable=\"true\">tap to tweak</string>\n    <string name=\"menu_tap_legends\" translatable=\"true\">tap for legends</string>\n    <string name=\"menu_tap_send\" translatable=\"true\">tap to send</string>\n    <string name=\"menu_tap_help\" translatable=\"true\">tap for help</string>\n\n    <string name=\"title_home\" translatable=\"true\">Home</string>\n    <string name=\"title_settings\" translatable=\"true\">Settings</string>\n    <string name=\"title_advanced\" translatable=\"true\">Advanced</string>\n    <string name=\"title_troubleshooting_bugs\" translatable=\"true\">Troubleshooting &amp; Bugs</string>\n    <string name=\"title_troubleshooting\" translatable=\"true\">Troubleshooting</string>\n    <string name=\"title_superuser\" translatable=\"true\">Superuser</string>\n    <string name=\"title_customisation\" translatable=\"true\">Customisation</string>\n    <string name=\"title_about\" translatable=\"true\">About</string>\n    <string name=\"title_feedback\" translatable=\"true\">Feedback</string>\n    <string name=\"title_communicate\" translatable=\"true\">Communicate</string>\n    <string name=\"title_social\" translatable=\"true\">Social</string>\n    <string name=\"title_share\" translatable=\"true\">Share</string>\n    <string name=\"title_twitter\" translatable=\"true\">Twitter</string>\n    <string name=\"title_google_plus\" translatable=\"true\">Google+</string>\n    <string name=\"title_forum\" translatable=\"true\">Forum</string>\n    <string name=\"action_power\" translatable=\"true\">Power</string>\n    <string name=\"menu_voice_tutorial\" translatable=\"true\">Voice Tutorial</string>\n    <string name=\"menu_ping\" translatable=\"true\">Ping Test Network</string>\n    <string name=\"menu_memory_usage\" translatable=\"true\">Memory Usage</string>\n    <string name=\"menu_root\" translatable=\"true\">Root Options</string>\n    <string name=\"menu_intercept_google\" translatable=\"true\">Intercept Google Now</string>\n    <string name=\"menu_start_boot\" translatable=\"true\">Start at Boot</string>\n    <string name=\"menu_start_boot_lower\" translatable=\"true\">\"Start at boot\"</string>\n    <string name=\"menu_start_driving\" translatable=\"true\">\"Start when driving\"</string>\n    <string name=\"menu_prevent_sleep\" translatable=\"true\">\"Prevent device sleeping\"</string>\n    <string name=\"menu_lock_security\" translatable=\"true\">\"Secure info when locked\"</string>\n    <string name=\"menu_algorithms\" translatable=\"true\">Algorithms</string>\n    <string name=\"menu_language\" translatable=\"true\">Language</string>\n    <string name=\"menu_synthesised_voice\" translatable=\"true\">Google Synthesised Voice</string>\n    <string name=\"menu_user_guide\" translatable=\"true\">User Guide</string>\n    <string name=\"menu_default_apps\" translatable=\"true\">Default Applications</string>\n    <string name=\"menu_toast\" translatable=\"true\">Toast Unknown Commands</string>\n    <string name=\"menu_offline\" translatable=\"true\">Use Offline Recognition</string>\n    <string name=\"menu_hotword_detection\" translatable=\"true\">Hotword Detection</string>\n    <string name=\"menu_tts_gender\" translatable=\"true\">Text to Speech Gender</string>\n    <string name=\"menu_blacklist\" translatable=\"true\">Manage Blacklist</string>\n    <string name=\"menu_vocal_verification\" translatable=\"true\">Vocal Verification</string>\n    <string name=\"dialog_id_verification\" translatable=\"true\">Voice account</string>\n    <string name=\"dialog_id_verification_content\" translatable=\"true\">Select account to associate</string>\n    <string name=\"menu_unlink_association\" translatable=\"true\">Unlink Association</string>\n    <string name=\"content_unlink_association\" translatable=\"true\">%1$s already has a vocal\n        association with Saiy. Would you like to unlink the current association and start a new one?</string>\n    <string name=\"menu_motion\" translatable=\"true\">Motion Activity Recognition</string>\n    <string name=\"menu_pause\" translatable=\"true\">Pause Timeout</string>\n    <string name=\"pause_detection_text\" translatable=\"true\">Pause for breath is</string>\n    <string name=\"media_stream\" translatable=\"true\">media stream</string>\n    <string name=\"above\" translatable=\"true\">above</string>\n    <string name=\"below\" translatable=\"true\">below</string>\n    <string name=\"adhere_to\" translatable=\"true\">adhere to</string>\n    <string name=\"memory_usage_text\" translatable=\"true\">Release resources after</string>\n    <string name=\"menu_haptic\" translatable=\"true\">Use Haptic Feedback</string>\n    <string name=\"menu_temperature_units\" translatable=\"true\">Temperature Units</string>\n    <string name=\"menu_saiy\" translatable=\"true\">Copyright © 2016 Saiy™ Ltd.</string>\n    <string name=\"menu_legal\" translatable=\"true\">License &amp; Legal</string>\n    <string name=\"menu_release_notes\" translatable=\"true\">Release Notes</string>\n    <string name=\"menu_source_code\" translatable=\"true\">Source Code</string>\n    <string name=\"menu_developer_api\" translatable=\"true\">Developer APIs</string>\n    <string name=\"menu_development\" translatable=\"true\">Development</string>\n    <string name=\"menu_testing\" translatable=\"true\">Testing</string>\n    <string name=\"menu_run\" translatable=\"true\">Run</string>\n    <string name=\"menu_daniel_schlapa\" translatable=\"false\">Daniel Schlapa</string>\n    <string name=\"menu_hannes_ahremark\" translatable=\"false\">Hannes Ahremark</string>\n    <string name=\"submenu_daniel_schlapa\" translatable=\"false\">blog.schlapa.net (dtrieb@xda)</string>\n    <string name=\"menu_commercial_enquiries\" translatable=\"true\">Commercial Enquiries</string>\n    <string name=\"menu_commercial_enquiry\" translatable=\"true\">Commercial Enquiry</string>\n    <string name=\"menu_special_thanks\" translatable=\"true\">Special Thanks</string>\n    <string name=\"menu_app_icon\" translatable=\"true\">App Icon</string>\n    <string name=\"submenu_studio_ahremark\" translatable=\"false\">studioahremark.com</string>\n    <string name=\"menu_custom_intro\" translatable=\"true\">Custom Intro</string>\n    <string name=\"menu_privacy_policy\" translatable=\"true\">Privacy Policy</string>\n    <string name=\"menu_volume_settings\" translatable=\"true\">Volume Settings</string>\n    <string name=\"hint_bugs_command\" translatable=\"true\">Enter a command to run…</string>\n    <string name=\"menu_recogniser_busy_fix\" translatable=\"true\">Recognizer Busy Fix</string>\n    <string name=\"menu_okay_google_fix\" translatable=\"true\">Okay Google Fix</string>\n\n    <string name=\"an_unknown_application\" translatable=\"true\">\"an unknown application\"</string>\n\n    <string name=\"test_string\" translatable=\"true\">\"asdasdasdasdashalsfhakhfssadasdaasdsagdsafgfghffhdsasdddfdghhgfhsdfdsad\"</string>\n\n    <!-- Accessibility Content descriptions -->\n    <string name=\"ac_button_done\" translatable=\"true\">Done button</string>\n\n    <!-- Permission Strings -->\n    <string name=\"permission_request\" translatable=\"true\">Permission Request</string>\n    <string name=\"permission_notification_ticker\" translatable=\"true\">Permission Request</string>\n    <string name=\"permission_notification_text\" translatable=\"true\">Permission Request - click for information</string>\n    <string name=\"permission_unknown\" translatable=\"true\">Without this permission I cannot function.\n        You can grant this by clicking the Permissions button below</string>\n    <string name=\"permission_audio\" translatable=\"true\">I require the, microphone, permission to record your voice and\n        understand what you are saying. Without this permission, I\\'m afraid I\\'ll be useless\\!\n        You can grant this by clicking the Permissions button below</string>\n    <string name=\"permission_group_contacts\" translatable=\"true\">I require the, accounts and contacts, permission\n        to do many things. To associate your voice with a specific account. To assist you with making\n        and receiving calls. To help you send text messages and announce their content. Sending emails, your\n        social media obligations, and much more.</string>\n    <string name=\"permission_group_contacts_denied\" translatable=\"true\">I require the, accounts and contacts,\n        permission to use this feature. You can manually allow this in the Application Settings, under\n        permissions.</string>\n\n    <!-- Notification data -->\n    <string name=\"notification_ticker\" translatable=\"true\">\"Saiy has become self-aware\"</string>\n    <string name=\"notification_ai_level\" translatable=\"true\">\"AI level %1$s\"</string>\n    <string name=\"notification_listening\" translatable=\"true\">\"Listening\"</string>\n    <string name=\"notification_speaking\" translatable=\"true\">\"Dictating\"</string>\n    <string name=\"notification_fetching\" translatable=\"true\">\"Fetching\"</string>\n    <string name=\"notification_initialising_tts\" translatable=\"true\">\"Initialising TTS\"</string>\n    <string name=\"notification_computing\" translatable=\"true\">\"Computing\"</string>\n    <string name=\"notification_tap_cancel\" translatable=\"true\">\"tap to cancel\"</string>\n    <string name=\"notification_tap_stop\" translatable=\"true\">\"tap to stop\"</string>\n    <string name=\"notification_stop_hotword\" translatable=\"true\">\"Stop hotword detection\"</string>\n\n    <!-- App - Generic error Strings -->\n    <string name=\"error_no_application\" translatable=\"true\">\"No application is installed to handle this request?\"</string>\n\n    <!-- Speech - Specific error Strings -->\n    <string name=\"error_empty_voice_data\" translatable=\"true\">\"I didn't receive any voice data from the recognition provider.\"</string>\n    <string name=\"error_empty_profanity\" translatable=\"true\">\"I didn't receive any voice data. This could be due to the profanity\n        filter set by your voice recognition provider.\"</string>\n    <string name=\"error_unknown_action\" translatable=\"true\">\"I don't know what I'm supposed to be doing.\"</string>\n    <string name=\"error_network\" translatable=\"true\">\"No network connection!\"</string>\n    <string name=\"error_hotword\" translatable=\"true\">\"Hotword engine failed to initialise :(\"</string>\n    <string name=\"error_audio\" translatable=\"true\">\"There was an audio recording error\"</string>\n\n    <!-- TTS - Specific error Strings -->\n    <string name=\"error_tts_initialisation\" translatable=\"true\">\"There is a problem with your Text to Speech engine.\"</string>\n    <string name=\"error_tts_progress\" translatable=\"true\">\"There was a problem with the Text to Speech playback.\"</string>\n    <string name=\"error_tts_voice\" translatable=\"true\">\"Unable to set TTS language.\"</string>\n    <string name=\"error_tts_volume\" translatable=\"true\">\"The media volume is muted.\"</string>\n\n    <!-- Issue Strings -->\n    <string name=\"issue_vr_text\" translatable=\"true\">\"In order for the application to function, you need to have a\n        voice recognition provider installed. Please install the Google Now application from the Play Store.\"</string>\n    <string name=\"issue_tts_engine_text\" translatable=\"true\">\"In order for the application to speak, you need to\n        install a Text to Speech engine. Please select one from the list below and then set it as your default\n        in the Android Text to Speech Settings, which can be found under the Language and Input section\"</string>\n    <string name=\"issue_tts_language_text\" translatable=\"true\">\"I was unable to speak in the requested language. This\n        could be a simple solution, such as an additional download of the language files, which you can do from\n        the settings within your voice engine. Click on the link below to go there.\"</string>\n    <string name=\"issue_vlingo_text\" translatable=\"true\">\"Unfortunately, Samsung do not allow their Vlingo voice\n        recognition to be used by any other applications, than S Voice. Please change the default provider to Google.\n        Click the button below to go to the Settings.\"</string>\n    <string name=\"issue_usage_stats_bug\" translatable=\"true\">\"Unfortunately, your device is suffering from a known Android bug, where I am unable to open the Usage Statistics Settings in order for you to grant me permission to access them. This will cause a number of problems with commands associated with application usage. Please check for an update to your Android version in your device settings, as your manufacturer may have already pushed a fix to resolve this.\"</string>\n\n    <!-- Troubleshooting and bugs Strings -->\n\n    <string name=\"bug_title_offline_recognition\" translatable=\"true\">\"Offline recognition fails\"</string>\n    <string name=\"bug_content_offline_recognition\" translatable=\"true\">This feature is only available if you are\n        using Google Voice Recognition.\\n\\nYou\\'ll need to make sure you have correctly downloaded and installed\n        the offline recognition files from the Android Voice Search Settings.\\n\\nIt is often necessary to reboot\n        your device once you have done this.\\n\\nClick to go to the Settings.</string>\n\n    <string name=\"bug_title_no_speech\" translatable=\"true\">\"I don't hear any speech?\"</string>\n    <string name=\"bug_content_no_speech\" translatable=\"true\">First thing to check is your media volume. On some\n        devices it may not be obvious that this stream alone is set too low or muted.\\n\\nA more likely problem\n        is your voice engine is in error; this happens all too often… In the Android Text to Speech\n        Settings double check any additional download files are installed and up to date.\n        There\\'s an option to \\'listen to a sample\\' which you can test too.\\n\\nIt\\'s highly recommended to\n        use the Google text to speech engine as at the time of writing,\n        despite taking a little longer to initially warm up, it is the only one that seems to work at least\n        relatively reliably…\\n\\nIf all else fails, try rebooting your device, as this can sometimes fix weird\n        and wonderful problems for no apparent reason at all…\\n\\nTap for Text to Speech Settings. Tap and hold\n        for Volume Settings</string>\n\n    <string name=\"bug_title_delay_speech\" translatable=\"true\">\"There's a delay before speech\"</string>\n    <string name=\"bug_content_delay_speech\" translatable=\"true\">Text to speech engines take up a lot of resource\n        and consequently can take a while to initialise and be ready to speak. This is more common on higher\n        quality engines, regardless of whether they are downloaded and installed on the device or not.\\n\\nIn the\n        Superuser Settings, under Memory Usage, you can adjust how long the text to speech engine is bound to\n        Saiy and kept in the memory. The longer allowed time, the less likely the initialisation process\n        will be required for subsequent commands\\n\\nThis additional memory usage\n        should be negligible to the performance of any modern device. If Android needs the memory back,\n        it will take it!\\n\\nIn the Saiy Settings, if you have selected to use the network synthesised voices\n        (available via Google\\'s text to speech engine), a delay can occur due to the speed of your network\n        connection. If your mobile connectivity is a little hit and miss, disabling this feature unless you are\n        connected to WiFi, could certainly help…\\n\\nTap for settings. Tap and hold for Superuser Settings</string>\n\n    <!-- App announcement strings -->\n\n    <string name=\"app_speech_usage_stats\" translatable=\"true\">So I can automatically toggle the hotword\n        recognition when you open certain applications, that may stream media content, or need access to the\n        microphone, I require the statistics permission here. I\\'ll then be able to check which apps you are using,\n        and act accordingly.</string>\n\n    <!-- APP ARRAYS - APP ARRAYS - APP ARRAYS - APP ARRAYS -->\n\n    <integer-array name=\"array_se\" translatable=\"false\">\n        <item>@raw/fart1</item>\n        <item>@raw/fart2</item>\n        <item>@raw/fart3</item>\n        <item>@raw/burp1</item>\n        <item>@raw/burp2</item>\n        <item>@raw/cough1</item>\n        <item>@raw/cough2</item>\n        <item>@raw/cry1</item>\n        <item>@raw/giggle1</item>\n        <item>@raw/giggle2</item>\n        <item>@raw/pee1</item>\n        <item>@raw/puke1</item>\n        <item>@raw/sneeze1</item>\n        <item>@raw/flush1</item>\n    </integer-array>\n\n    <!-- Preserve order -->\n    <string-array name=\"array_supported_languages\" translatable=\"true\">\n        <item>@string/english</item>\n        <item>@string/french</item>\n        <item>@string/german</item>\n        <item>@string/italian</item>\n        <item>@string/spanish</item>\n        <item>@string/portuguese</item>\n        <item>@string/japanese</item>\n        <item>@string/polish</item>\n    </string-array>\n\n    <!-- Preserve order -->\n    <string-array name=\"array_temperature_units\" translatable=\"false\">\n        <item>@string/celsius</item>\n        <item>@string/fahrenheit</item>\n    </string-array>\n\n    <!-- Preserve order -->\n    <string-array name=\"array_unknown_action\" translatable=\"false\">\n        <item>@string/state_command_unknown</item>\n        <item>@string/ask_repeat</item>\n        <item>@string/google_capital</item>\n        <item>@string/wolfram_alpha_capital</item>\n        <item>@string/tasker_capital</item>\n    </string-array>\n\n    <!-- Preserve order -->\n    <string-array name=\"array_gender\" translatable=\"false\">\n        <item>@string/male</item>\n        <item>@string/female</item>\n    </string-array>\n\n    <!-- Preserve order -->\n    <string-array name=\"array_hotword\" translatable=\"false\">\n        <item>@string/menu_start_boot_lower</item>\n        <item>@string/menu_start_driving</item>\n        <item>@string/menu_prevent_sleep</item>\n        <item>@string/menu_lock_security</item>\n    </string-array>\n\n    <!-- COMMAND STRINGS - COMMAND STRINGS - COMMAND STRINGS - COMMAND STRINGS -->\n\n    <!-- Command - Wolfram Alpha -->\n    <string name=\"wolfram_alpha_capital\" translatable=\"false\">\"Wolfram Alpha\"</string>\n    <string name=\"wolfram_alpha\" translatable=\"false\">\"wolfram alpha\"</string>\n    <string name=\"alpha\" translatable=\"false\">\"alpha\"</string>\n\n    <!-- Speech - Wolfram Alpha | error unknown  -->\n    <string-array name=\"array_error_wolfram_alpha_unknown\" translatable=\"true\">\n        <item>\"Sorry %1$s, but Wolfram Alpha could not resolve that question\"</item>\n        <item>\"Sorry %1$s, Wolfram Alpha does not have the answer to that\"</item>\n        <item>\"Sorry %1$s, Wolfram Alpha couldn't answer that\"</item>\n        <item>\"Sorry %1$s, Wolfram Alpha didn't understand that\"</item>\n        <item>\"Sorry %1$s, but Wolfram Alpha didn't understand that\"</item>\n    </string-array>\n\n    <!-- Command - Speech enrollment -->\n    <string name=\"speech_enroll_instructions_first\" translatable=\"true\">\"I need to capture approximately fifteen\n        seconds of audio, so please count quickly up to forty. That should be sufficient.\"</string>\n    <string name=\"speech_enroll_instructions_40\" translatable=\"true\">\"Please count quickly up to forty.\"</string>\n    <string name=\"speech_enroll_instructions_15\" translatable=\"true\">\"Please count quickly up to fifteen.\"</string>\n    <string name=\"speech_enroll_success\" translatable=\"true\">\"The vocal verification process succeeded. I'll\n        now be able to identify you, before providing sensitive information, or on request\"</string>\n\n    <!-- Command - Speech enrollment | error -->\n    <string name=\"error_speech_enrollment_account\" translatable=\"true\">\"Sorry %1$s, but the remote enrollment\n        process failed. I\\'m sure my creator is aware and working on a fix as we speak.\"</string>\n\n    <!-- Command - Google Now | error -->\n    <string name=\"error_google_now_broadcast\" translatable=\"true\">\"Failed sending to Google Now\"</string>\n\n    <!-- Command - Wolfram Alpha | error -->\n    <string name=\"error_wolfram_alpha_install\" translatable=\"true\">\"You can install the Wolfram Alpha Android application,\n        to view these results more easily.\"</string>\n    <string name=\"error_wolfram_alpha_broadcast\" translatable=\"true\">\"Failed sending to Wolfram Alpha\"</string>\n\n    <!-- Command - Hotword -->\n    <string name=\"start\" translatable=\"true\">\"start\"</string>\n    <string name=\"stop\" translatable=\"true\">\"stop\"</string>\n    <string name=\"toggle\" translatable=\"true\">\"toggle\"</string>\n    <string name=\"turn\" translatable=\"true\">\"turn\"</string>\n    <string name=\"word_switch\" translatable=\"true\">\"switch\"</string>\n    <string name=\"on\" translatable=\"true\">\"on\"</string>\n    <string name=\"off\" translatable=\"true\">\"off\"</string>\n    <string name=\"enable\" translatable=\"true\">\"enable\"</string>\n    <string name=\"disable\" translatable=\"true\">\"disable\"</string>\n    <string name=\"listening\" translatable=\"true\">\"listening\"</string>\n    <string name=\"hotword\" translatable=\"true\">\"hotword\"</string>\n    <string name=\"hot_word\" translatable=\"true\">\"hot word\"</string>\n\n    <!-- Command - Emotion -->\n    <string name=\"emotion\" translatable=\"true\">\"emotion\"</string>\n    <string name=\"feeling\" translatable=\"true\">\"feeling\"</string>\n\n    <!-- Command - Emotion | error length -->\n    <string name=\"error_emotion_length\" translatable=\"true\">\"You need to speak for at least 13 seconds,\n        for a successful analysis.\"</string>\n\n    <!-- Command - Emotion | error BV connection -->\n    <string name=\"error_beyond_verbal_connection\" translatable=\"true\">\"Sorry %1$s, but I was unable\n        to connect to Beyond Verbal's servers.\"</string>\n\n    <!-- Command - Emotion | Notification -->\n    <string name=\"emotion_notification_text\" translatable=\"true\">\"Analysis Complete - click for details\"</string>\n    <string name=\"emotion_notification_ticker\" translatable=\"true\">\"Analysis Complete\"</string>\n    <string name=\"emotion_notify_analyse\" translatable=\"true\">\"Thank you. I'll notify you shortly when I've\n        completed the emotion analysis\"</string>\n\n    <!-- Command - Voice Identification -->\n    <string name=\"analyse\" translatable=\"true\">\"analyse\"</string>\n    <string name=\"analysis\" translatable=\"true\">\"analysis\"</string>\n    <string name=\"voice\" translatable=\"true\">\"voice\"</string>\n    <string name=\"error_vi_no_account\" translatable=\"true\">\"You need to create an account to associate your voice\n        to.\"</string>\n    <string name=\"error_vi_status\" translatable=\"true\">\"I don't have a complete voice footprint associated with\n        your account. You'll need to create another one.\"</string>\n    <string name=\"add_new\" translatable=\"true\">add new</string>\n    <string name=\"vocal_notification_text\" translatable=\"true\">\"Verification Complete - click for details\"</string>\n    <string name=\"vocal_notification_ticker\" translatable=\"true\">\"Verification Complete\"</string>\n    <string name=\"vocal_notify_enroll\" translatable=\"true\">\"Thank you. I'll notify you shortly when I've\n        finished analysing the audio\"</string>\n    <string name=\"vocal_notify_verify\" translatable=\"true\">\"Okay. I'll notify you when I've analysed the audio.\"</string>\n\n    <!-- Command - Tasker -->\n    <string name=\"tasker_capital\" translatable=\"false\">\"Tasker\"</string>\n    <string name=\"tasker\" translatable=\"false\">\"tasker\"</string>\n    <string name=\"task_\" translatable=\"true\">\"task \"</string>\n    <string name=\"called_\" translatable=\"true\">\"called \"</string>\n    <string name=\"named_\" translatable=\"true\">\"named \"</string>\n\n    <!-- Command - Tasker | error -->\n    <string name=\"error_tasker_install\" translatable=\"true\">\"You need to install the Tasker application\n        to use this command.\"</string>\n    <string name=\"error_tasker_execute\" translatable=\"true\">\"Something went wrong executing the task, %1$s\"</string>\n    <string name=\"error_tasker_no_tasks\" translatable=\"true\">\"You don't appear to have any tasks to execute\"</string>\n    <string name=\"error_tasker_enable\" translatable=\"true\">\"Please enable Tasker\"</string>\n    <string name=\"error_tasker_external_access\" translatable=\"true\">\"So I can execute tasks for you, please tick the box that\n        allows external access\"</string>\n    <string name=\"error_tasker_permissions\" translatable=\"true\">\"There was an error communicating with Tasker.\n        This is a known Android bug where the Tasker permissions are not detected, if you installed Tasker after me.\n        You'll need to reinstall me, so the permissions are correctly applied. Click on the re-assimilate button\"</string>\n    <string name=\"error_tasker_broadcast\" translatable=\"true\">\"Failed sending to Tasker\"</string>\n\n    <!-- Speech - Tasker | error unknown -->\n    <string-array name=\"array_error_tasker_match\" translatable=\"true\">\n        <item>\"Sorry %1$s, but I failed to find a matching task name\"</item>\n        <item>\"Sorry %1$s, I failed to find a matching task name\"</item>\n        <item>\"Sorry %1$s, I couldn't find a matching task name\"</item>\n        <item>\"Sorry %1$s, but I couldn't find a matching task name\"</item>\n        <item>\"Sorry %1$s, I failed to find a task with that name\"</item>\n        <item>\"Sorry %1$s, but I failed to find a task with that name\"</item>\n    </string-array>\n\n    <!-- Speech | error secure -->\n    <string-array name=\"array_error_secure\" translatable=\"true\">\n        <item>\"Sorry %1$s, but this information is not permitted when the device is locked\"</item>\n        <item>\"Sorry %1$s, this command is not available when the device is locked\"</item>\n        <item>\"Sorry %1$s, you can't access this command when the device is locked\"</item>\n        <item>\"This information is not permitted when the device is locked\"</item>\n        <item>\"This command is not available when the device is locked\"</item>\n        <item>\"You can't access this command when the device is locked\"</item>\n    </string-array>\n\n    <!-- Command - Clipboard -->\n    <string name=\"clipboard\" translatable=\"true\">\"clipboard\"</string>\n    <string name=\"clip_board\" translatable=\"true\">\"clip board\"</string>\n\n    <!-- Command - Song Recognition -->\n    <string name=\"name_that_tune\" translatable=\"true\">\"name that tune\"</string>\n\n    <string name=\"sound_search\" translatable=\"true\">\"sound search\"</string>\n    <string name=\"google_capital\" translatable=\"false\">\"Google\"</string>\n    <string name=\"google\" translatable=\"false\">\"google\"</string>\n    <string name=\"sound_hound\" translatable=\"false\">\"Sound Hound\"</string>\n    <string name=\"sound_hound_premium\" translatable=\"false\">\"Sound Hound Premium\"</string>\n    <string name=\"shazam\" translatable=\"false\">\"Shazam\"</string>\n    <string name=\"shazam_encore\" translatable=\"false\">\"Shazam Encore\"</string>\n    <string name=\"track_id\" translatable=\"false\">\"Track ID\"</string>\n\n    <!-- Command - Song Recognition error -->\n    <string name=\"error_song_recognition_app_failed\" translatable=\"true\">\"Sorry %1$s, but there was\n        a problem opening the %2$s application\"</string>\n    <string name=\"error_song_recognition_default_app\" translatable=\"true\">\"The application, %1$s,\n        is no longer installed. Please select or install another one.\"</string>\n    <string name=\"error_song_recognition_chooser\" translatable=\"true\">\"Please select or install the\n        supported application you would like to handle this command.\"</string>\n\n    <!-- Command - Name -->\n    <string name=\"call_me_\" translatable=\"true\">\"call me \"</string>\n    <string name=\"address_me_as_\" translatable=\"true\">\"address me as \"</string>\n    <string name=\"addressed_as_\" translatable=\"true\">\"addressed as \"</string>\n    <string name=\"my_name_is_\" translatable=\"true\">\"my name is \"</string>\n    <string name=\"be_called_\" translatable=\"true\">\"be called \"</string>\n    <string name=\"be_known_as_\" translatable=\"true\">\"be known as \"</string>\n\n    <!-- Command - Pardon  -->\n    <string name=\"pardon\" translatable=\"true\">\"pardon\"</string>\n    <string name=\"say_that_again\" translatable=\"true\">\"say that again\"</string>\n    <string name=\"what_did_you_say\" translatable=\"true\">\"what did you say\"</string>\n    <string name=\"come_again\" translatable=\"true\">\"come again\"</string>\n    <string name=\"repeat\" translatable=\"true\">\"repeat\"</string>\n    <string name=\"said\" translatable=\"true\">\"said\"</string>\n    <string name=\"that\" translatable=\"true\">\"that\"</string>\n\n    <!-- Command - Cancel -->\n    <string name=\"cancel_\" translatable=\"true\">\"cancel \"</string>\n    <string name=\"cancel_that\" translatable=\"true\">\"cancel that\"</string>\n    <string name=\"never_mind\" translatable=\"true\">\"never mind\"</string>\n    <string name=\"shush\" translatable=\"true\">\"shush\"</string>\n    <string name=\"shut_up\" translatable=\"true\">\"shut up\"</string>\n\n    <!-- Command - Cancel | misheard -->\n    <string name=\"council_\" translatable=\"false\">\"council \"</string>\n\n    <!-- Command - Spell -->\n    <string name=\"spell_\" translatable=\"true\">\"spell \"</string>\n\n    <!-- Command - Calculate -->\n    <string name=\"calculate\" translatable=\"true\">\"calculate\"</string>\n\n    <!-- Command - Translate  -->\n    <string name=\"translate_\" translatable=\"true\">\"translate \"</string>\n    <string name=\"into\" translatable=\"true\">\"into\"</string>\n    <string name=\"in_to\" translatable=\"true\">\"in to\"</string>\n    <string name=\"to\" translatable=\"true\">\"to\"</string>\n\n    <string name=\"german\" translatable=\"true\">german</string>\n    <string name=\"bosnian\" translatable=\"true\">bosnian</string>\n    <string name=\"croatian\" translatable=\"true\">croatian</string>\n    <string name=\"czech\" translatable=\"true\">czech</string>\n    <string name=\"haitian_creole\" translatable=\"true\">haitian creole</string>\n    <string name=\"french\" translatable=\"true\">french</string>\n    <string name=\"kiswahili\" translatable=\"true\">kiswahili</string>\n    <string name=\"swahili\" translatable=\"true\">swahili</string>\n    <string name=\"klingon\" translatable=\"true\">klingon</string>\n    <string name=\"italian\" translatable=\"true\">italian</string>\n    <string name=\"serbian\" translatable=\"true\">serbian</string>\n    <string name=\"serbian_cyrillic\" translatable=\"true\">serbian_cyrillic</string>\n    <string name=\"serbian_latin\" translatable=\"true\">serbian_latin</string>\n    <string name=\"polish\" translatable=\"true\">polish</string>\n    <string name=\"spanish\" translatable=\"true\">spanish</string>\n    <string name=\"romanian\" translatable=\"true\">romanian</string>\n    <string name=\"english\" translatable=\"true\">english</string>\n    <string name=\"arabic\" translatable=\"true\">arabic</string>\n    <string name=\"bulgarian\" translatable=\"true\">bulgarian</string>\n    <string name=\"catalan\" translatable=\"true\">catalan</string>\n    <string name=\"chinese_simplified\" translatable=\"true\">chinese simplified</string>\n    <string name=\"chinese_traditional\" translatable=\"true\">chinese traditional</string>\n    <string name=\"danish\" translatable=\"true\">danish</string>\n    <string name=\"dutch\" translatable=\"true\">dutch</string>\n    <string name=\"estonian\" translatable=\"true\">estonian</string>\n    <string name=\"finnish\" translatable=\"true\">finnish</string>\n    <string name=\"greek\" translatable=\"true\">greek</string>\n    <string name=\"haitian\" translatable=\"true\">haitian</string>\n    <string name=\"hebrew\" translatable=\"true\">hebrew</string>\n    <string name=\"hindi\" translatable=\"true\">hindi</string>\n    <string name=\"hungarian\" translatable=\"true\">hungarian</string>\n    <string name=\"indonesian\" translatable=\"true\">indonesian</string>\n    <string name=\"japanese\" translatable=\"true\">japanese</string>\n    <string name=\"korean\" translatable=\"true\">korean</string>\n    <string name=\"latvian\" translatable=\"true\">latvian</string>\n    <string name=\"lithuanian\" translatable=\"true\">lithuanian</string>\n    <string name=\"malaysian\" translatable=\"true\">malaysian</string>\n    <string name=\"malay\" translatable=\"true\">malay</string>\n    <string name=\"maltese\" translatable=\"true\">maltese</string>\n    <string name=\"norwegian\" translatable=\"true\">norwegian</string>\n    <string name=\"persian\" translatable=\"true\">persian</string>\n    <string name=\"portuguese\" translatable=\"true\">portuguese</string>\n    <string name=\"portuguese_brazilian\" translatable=\"true\">portuguese brazilian</string>\n    <string name=\"brazilian\" translatable=\"true\">brazilian</string>\n    <string name=\"russian\" translatable=\"true\">russian</string>\n    <string name=\"slovak\" translatable=\"true\">slovak</string>\n    <string name=\"slovakian\" translatable=\"true\">slovakian</string>\n    <string name=\"slovenian\" translatable=\"true\">slovenian</string>\n    <string name=\"swedish\" translatable=\"true\">swedish</string>\n    <string name=\"thai\" translatable=\"true\">thai</string>\n    <string name=\"turkish\" translatable=\"true\">turkish</string>\n    <string name=\"ukrainian\" translatable=\"true\">ukrainian</string>\n    <string name=\"urdu\" translatable=\"true\">urdu</string>\n    <string name=\"vietnamese\" translatable=\"true\">vietnamese</string>\n    <string name=\"welsh\" translatable=\"true\">welsh</string>\n    <string name=\"chinese\" translatable=\"true\">chinese</string>\n    <string name=\"czechoslovakian\" translatable=\"true\">czechoslovakian</string>\n    <string name=\"mandarin\" translatable=\"true\">mandarin</string>\n    <string name=\"cantonese\" translatable=\"true\">cantonese</string>\n\n    <!-- Command - Translate error -->\n    <string name=\"error_translate_length\" translatable=\"true\">\"I'm afraid %1$s, that translations are currently limited to 2000 characters.\"</string>\n    <string name=\"error_translate\" translatable=\"true\">\"Sorry %1$s, but there was a problem completing that translation.\"</string>\n    <string name=\"error_translate_unsupported\" translatable=\"true\">\"I'm sorry %1$s, but that language is not currently supported for\n        translations\"</string>\n\n    <!-- Command - Remote -->\n    <string name=\"remote_success\" translatable=\"true\">\"I successfully processed a request from the application, %1$s.\"</string>\n\n    <!-- Command - Remote error -->\n    <string name=\"error_remote\" translatable=\"true\">\"A request from the application, %1$s, has failed.\"</string>\n    <string name=\"error_remote_unknown\" translatable=\"true\">\"A request from a remote application has failed.\n        The remote application did not identify itself. You can delete which applications are allowed to send\n        me command requests from my Settings.\"</string>\n    <string name=\"error_remote_command_register\" translatable=\"true\">The application, %1$s, failed to successfully\n        register a command.</string>\n    <string name=\"remote_command_register_response\" translatable=\"true\">The application, %1$s, successfully\n        registered the command, %2$s</string>\n\n    <!-- Command - Battery -->\n    <string name=\"battery\" translatable=\"true\">\"battery\"</string>\n    <string name=\"temperature\" translatable=\"true\">\"temperature\"</string>\n    <string name=\"level\" translatable=\"true\">\"level\"</string>\n    <string name=\"percentage\" translatable=\"true\">\"percentage\"</string>\n    <string name=\"percent\" translatable=\"true\">\"percent\"</string>\n    <string name=\"voltage\" translatable=\"true\">\"voltage\"</string>\n    <string name=\"volts\" translatable=\"true\">\"volts\"</string>\n    <string name=\"status\" translatable=\"true\">\"status\"</string>\n    <string name=\"health\" translatable=\"true\">\"health\"</string>\n\n    <string name=\"charging\" translatable=\"true\">\"charging\"</string>\n    <string name=\"ac_charging\" translatable=\"true\">\"AC charging\"</string>\n    <string name=\"usb_charging\" translatable=\"true\">\"USB charging\"</string>\n    <string name=\"discharging\" translatable=\"true\">\"discharging\"</string>\n    <string name=\"consuming_battery\" translatable=\"true\">\"consuming battery\"</string>\n    <string name=\"fully_charged\" translatable=\"true\">\"full charged\"</string>\n    <string name=\"currently_indeterminable\" translatable=\"true\">\"currently indeterminable\"</string>\n\n    <string name=\"cold\" translatable=\"true\">\"cold\"</string>\n    <string name=\"dead\" translatable=\"true\">\"dead\"</string>\n    <string name=\"good\" translatable=\"true\">\"good\"</string>\n    <string name=\"over_heating\" translatable=\"true\">\"over heating\"</string>\n    <string name=\"over_voltage\" translatable=\"true\">\"over voltage\"</string>\n\n    <string name=\"celsius\" translatable=\"true\">\"celsius\"</string>\n    <string name=\"fahrenheit\" translatable=\"true\">\"fahrenheit\"</string>\n    <string name=\"degrees\" translatable=\"true\">\"degrees\"</string>\n\n    <!-- Command - Battery error -->\n    <string name=\"error_battery_access\" translatable=\"true\">\"Sorry %1$s, there was a problem accessing\n        your battery information.\"</string>\n    <string name=\"error_battery_unknown\" translatable=\"true\">\"Sorry %1$s, I'm not sure what specific\n        battery information you required.\"</string>\n\n    <!-- SPEECH RESPONSES - SPEECH RESPONSES - SPEECH RESPONSES -->\n    <!-- SPEECH RESPONSES - SPEECH RESPONSES - SPEECH RESPONSES -->\n    <!-- SPEECH RESPONSES - SPEECH RESPONSES - SPEECH RESPONSES -->\n\n    <!-- Speech - Beyond Verbal - error -->\n    <string-array name=\"array_beyond_verbal_error\" translatable=\"true\">\n        <item>\"Sorry %1$s, but I'm afraid the emotion analysis failed.\"</item>\n        <item>\"Sorry %1$s, but I'm afraid I was unable to analyse the emotions of the audio.\"</item>\n        <item>\"Apologies %1$s, but I failed to successfully analyse the emotional content.\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal -->\n    <string-array name=\"array_beyond_verbal\" translatable=\"true\">\n        <item>\"OK %1$s. I'll be listening.\"</item>\n        <item>\"OK %1$s. I'm ready to analyse you.\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper start -->\n    <string-array name=\"array_beyond_verbal_temper_intro\" translatable=\"true\">\n        <item>\"Your overall temperament is\"</item>\n        <item>\"Your underlying temperament is\"</item>\n        <item>\"Your general temperament is\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper start desc -->\n    <string-array name=\"array_beyond_verbal_temper_start_desc\" translatable=\"true\">\n        <item>\"Which could be described as\"</item>\n        <item>\"Which could be interpreted as\"</item>\n        <item>\"Which can be described as\"</item>\n        <item>\"Which can be interpreted as\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper intro-->\n    <string-array name=\"array_beyond_verbal_temper_connector\" translatable=\"true\">\n        <item>\"somewhat\"</item>\n        <item>\"almost\"</item>\n        <item>\"relatively\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper connector-->\n    <string-array name=\"array_beyond_verbal_temper_gap\" translatable=\"true\">\n        <item>\"seemingly\"</item>\n        <item>\"and\"</item>\n        <item>\"perhaps\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper connector two-->\n    <string-array name=\"array_beyond_verbal_temper_connector_two\" translatable=\"true\">\n        <item>\"that suggests you are\"</item>\n        <item>\"which suggests you are\"</item>\n        <item>\"which can suggest that you are\"</item>\n        <item>\"which can suggest you are\"</item>\n        <item>\"suggesting you are\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - temper connector three-->\n    <string-array name=\"array_beyond_verbal_temper_connector_three\" translatable=\"true\">\n        <item>\"You have feelings of\"</item>\n        <item>\"There are feelings of\"</item>\n        <item>\"I detect feelings of\"</item>\n        <item>\"You show signs of\"</item>\n        <item>\"There are emotions of\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - arousal intro -->\n    <string name=\"beyond_verbal_arousal_intro\" translatable=\"true\">\"Your emotional energy level is\"</string>\n\n    <!-- Speech - Beyond Verbal - valence intro -->\n    <string-array name=\"array_beyond_verbal_valence_intro\" translatable=\"true\">\n        <item>\"Your overall tone is\"</item>\n        <item>\"Your general tone is\"</item>\n        <item>\"Your tone is generally\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - moods connector -->\n    <string-array name=\"array_beyond_verbal_moods_connector\" translatable=\"true\">\n        <item>\"Were detected emotions, as well as\"</item>\n        <item>\"Were emotions detected, as well as\"</item>\n        <item>\"Were detected feelings, as well as\"</item>\n        <item>\"Were feelings detected, as well as\"</item>\n        <item>\"Were detected emotions, in addition to\"</item>\n        <item>\"Were emotions detected, in addition to\"</item>\n        <item>\"Were detected feelings, in addition to\"</item>\n        <item>\"Were feelings detected, in addition to\"</item>\n        <item>\"Were detected, as well as\"</item>\n        <item>\"Were detected, in addition to\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - low first -->\n    <string-array name=\"array_beyond_verbal_synonyms_low_first\" translatable=\"true\">\n        <item>\"depressive\"</item>\n        <item>\"inhibited\"</item>\n        <item>\"dispirited\"</item>\n        <item>\"distressed\"</item>\n        <item>\"foreboding\"</item>\n        <item>\"bleak\"</item>\n        <item>\"doleful\"</item>\n        <item>\"dreary\"</item>\n        <item>\"gloomy\"</item>\n        <item>\"sombre\"</item>\n        <item>\"saddened\"</item>\n        <item>\"insulted\"</item>\n        <item>\"self critical\"</item>\n        <item>\"self analytical\"</item>\n        <item>\"anxious\"</item>\n        <item>\"concerned\"</item>\n        <item>\"fatigued\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - low second -->\n    <string-array name=\"array_beyond_verbal_synonyms_low_second\" translatable=\"true\">\n        <item>\"waning\"</item>\n        <item>\"growing smaller\"</item>\n        <item>\"pulling back\"</item>\n        <item>\"reserved\"</item>\n        <item>\"restrained\"</item>\n        <item>\"aloof\"</item>\n        <item>\"sedate\"</item>\n        <item>\"withdrawn\"</item>\n        <item>\"demure\"</item>\n        <item>\"standoffish\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - medium first -->\n    <string-array name=\"array_beyond_verbal_synonyms_medium_first\" translatable=\"true\">\n        <item>\"warm\"</item>\n        <item>\"friendly\"</item>\n        <item>\"positive\"</item>\n        <item>\"empathetic\"</item>\n        <item>\"accepting\"</item>\n        <item>\"kind\"</item>\n        <item>\"affectionate\"</item>\n        <item>\"loving\"</item>\n        <item>\"calm\"</item>\n        <item>\"motivated\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - medium second -->\n    <string-array name=\"array_beyond_verbal_synonyms_medium_second\" translatable=\"true\">\n        <item>\"matter of fact\"</item>\n        <item>\"pragmatic\"</item>\n        <item>\"stoic\"</item>\n        <item>\"apathetic\"</item>\n        <item>\"stolid\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - high first -->\n    <string-array name=\"array_beyond_verbal_synonyms_high_first\" translatable=\"true\">\n        <item>\"aggressive\"</item>\n        <item>\"resistant\"</item>\n        <item>\"forceful\"</item>\n        <item>\"arrogant\"</item>\n        <item>\"belligerent\"</item>\n        <item>\"disapproving\"</item>\n        <item>\"pugnacious\"</item>\n        <item>\"unwelcoming\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - high second -->\n    <string-array name=\"array_beyond_verbal_synonyms_high_second\" translatable=\"true\">\n        <item>\"het up\"</item>\n        <item>\"angry\"</item>\n        <item>\"hostile\"</item>\n        <item>\"unsympathetic\"</item>\n        <item>\"argumentative\"</item>\n    </string-array>\n\n    <string name=\"beyond_verbal_verbose\" translatable=\"true\">\"OK %1$s. Don't forget you need to speak\n        for at least thirteen seconds.\"</string>\n\n    <string name=\"beyond_verbal_extra_verbose\" translatable=\"true\">\"OK %1$s. In order for me to analyse\n        your emotional state, you need to speak for at least thirteen seconds. After that, I'll\n        detect when you've finished speaking. You could tell me about your day today, or about a\n        memory from your past. If you can't think of anything, just close your eyes and count slowly\n        up to fifteen, whilst your mind wanders. I'll let you know with a notification when the\n        analysis is complete.\"</string>\n\n    <!-- Speech - Beyond Verbal | analysis complete response  -->\n    <string-array name=\"array_bv_analysis_complete\" translatable=\"true\">\n        <item>\"Thank you %1$s. I'll notify you shortly when I've completed the emotion analysis.\"</item>\n        <item>\"Thank you. I'll notify you when I've completed the analysis.\"</item>\n        <item>\"Thanks. I'll let you know with a notification when I've completed the analysis.\"</item>\n        <item>\"Thank you %1$s. I'll notify you when I've finished the analysis.\"</item>\n    </string-array>\n\n    <!-- Speech - Wolfram Alpha  -->\n    <string-array name=\"array_wolfram_alpha_intro\" translatable=\"true\">\n        <item>\"According to Wolfram Alpha\"</item>\n        <item>\"As reported by Wolfram Alpha\"</item>\n        <item>\"The Wolfram Alpha response is\"</item>\n    </string-array>\n\n    <!-- Speech - Tasker  -->\n    <string-array name=\"array_tasker\" translatable=\"true\">\n        <item>\"Task %1$s executed\"</item>\n        <item>\"Executed\"</item>\n        <item>\"Task executed\"</item>\n        <item>\"I executed the task %1$s\"</item>\n    </string-array>\n\n    <!-- Speech - Battery  -->\n    <string-array name=\"array_battery\" translatable=\"true\">\n        <item>\"Your battery %1$s is currently %2$s\"</item>\n        <item>\"Your battery %1$s is %2$s\"</item>\n        <item>\"The battery %1$s is reporting %2$s\"</item>\n        <item>\"The battery %1$s is currently %2$s\"</item>\n    </string-array>\n\n    <!-- Speech - Song Recognition  -->\n    <string-array name=\"array_song_recognition\" translatable=\"true\">\n        <item>\"Starting %1$s\"</item>\n        <item>\"Opening %1$s\"</item>\n        <item>\"Asking %1$s\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal enrollment api error  -->\n    <string-array name=\"array_error_enrollment_api\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but the vocal verification process encountered a problem.\"</item>\n        <item>\"Sorry %1$s, but the vocal verification process encountered a problem.\"</item>\n    </string-array>\n\n    <!-- Speech - User Name  -->\n    <string-array name=\"array_user_name\" translatable=\"true\">\n        <item>\"I will do. %1$s.\"</item>\n        <item>\"Okay, %1$s.\"</item>\n        <item>\"As you wish, %1$s.\"</item>\n        <item>\"Certainly, %1$s.\"</item>\n        <item>\"I will address you as, %1$s.\"</item>\n        <item>\"I will call you, %1$s.\"</item>\n        <item>\"If that's what you want, %1$s.\"</item>\n        <item>\"of course, %1$s.\"</item>\n        <item>\"Whatever you say, %1$s.\"</item>\n        <item>\"I like the name, %1$s.\"</item>\n        <item>\"%1$s is a lovely name\"</item>\n        <item>\"My favourite name is, %1$s.\"</item>\n        <item>\"%1$s. My favourite name\"</item>\n    </string-array>\n\n    <!-- Speech - User Name | error  -->\n    <string-array name=\"array_user_name_error\" translatable=\"true\">\n        <item>\"Sorry %1$s, but I'm not sure what you wanted me to call you?\"</item>\n        <item>\"Really sorry %1$s, but I'm not sure what you wanted me to call you?\"</item>\n        <item>\"Sorry %1$s, but I didn't correctly hear how you wanted me to address you.\"</item>\n    </string-array>\n\n    <!-- Speech - User Name | repeat  -->\n    <string-array name=\"array_user_name_repeat\" translatable=\"true\">\n        <item>\"I'm already addressing you as %1$s.\"</item>\n        <item>\"I already address you as %1$s.\"</item>\n        <item>\"I'm already calling you %1$s.\"</item>\n        <item>\"I already call you %1$s.\"</item>\n        <item>\"I call you %1$s at the moment\"</item>\n        <item>\"I call you %1$s already\"</item>\n    </string-array>\n\n    <!-- Speech - Intro  -->\n    <string name=\"intro_1\" translatable=\"true\">\"How can I help you %1$s?\"</string>\n    <string name=\"intro_2\" translatable=\"true\">\"I'm listening %1$s.\"</string>\n    <string name=\"intro_3\" translatable=\"true\">\"What can I do for you %1$s?\"</string>\n    <string name=\"intro_4\" translatable=\"true\">\"Yes %1$s?\"</string>\n    <string name=\"intro_5\" translatable=\"true\">\"Hello %1$s?\"</string>\n\n    <string-array name=\"array_intro\" translatable=\"true\">\n        <item>@string/intro_1</item>\n        <item>@string/intro_2</item>\n        <item>@string/intro_3</item>\n        <item>@string/intro_4</item>\n        <item>@string/intro_5</item>\n    </string-array>\n\n    <!-- Speech - No command was understood -->\n    <string-array name=\"array_no_comprende\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but I'm not sure what you meant by that.\"</item>\n        <item>\"Really sorry, but I'm not sure what you meant by that %1$s.\"</item>\n        <item>\"I'm sorry %1$s, but I didn't recognise that command.\"</item>\n        <item>\"I'm sorry, but I didn't recognise that command %1$s.\"</item>\n    </string-array>\n\n    <!-- Speech - Repeat command -->\n    <string-array name=\"array_repeat_command\" translatable=\"true\">\n        <item>\"Really sorry %1$s, could you say that again please.\"</item>\n        <item>\"Sorry %1$s, could you say that again please.\"</item>\n        <item>\"Really sorry %1$s, could you repeat that please.\"</item>\n        <item>\"Sorry %1$s, could you repeat that please.\"</item>\n        <item>\"Could you say that again please %1$s.\"</item>\n        <item>\"Sorry, could you say that again please %1$s.\"</item>\n        <item>\"Really sorry, could you repeat that please %1$s.\"</item>\n        <item>\"Sorry, could you repeat that please %1$s.\"</item>\n        <item>\"Sorry %1$s, I was in a daydream. Could you say that again please.\"</item>\n        <item>\"Sorry, I was in a daydream. Could you say that again please %1$s.\"</item>\n        <item>\"Please can you repeat that %1$s.\"</item>\n    </string-array>\n\n    <!-- Speech - No network connection -->\n    <string-array name=\"array_no_network\" translatable=\"true\">\n        <item>\"Sorry %1$s, but there's a problem with your network connection.\"</item>\n        <item>\"There's a problem with your network connection.\"</item>\n        <item>\"Sorry %1$s, but there's a problem with your network connectivity.\"</item>\n        <item>\"There's a problem with your network connectivity.\"</item>\n        <item>\"Sorry %1$s, but I lost the network connection.\"</item>\n        <item>\"I lost the network connection.\"</item>\n        <item>\"Sorry %1$s, but I lost network connectivity.\"</item>\n        <item>\"I lost network connectivity.\"</item>\n        <item>\"Sorry %1$s, but your network connection is unstable.\"</item>\n        <item>\"There's a problem with your network connection.\"</item>\n        <item>\"Sorry %1$s, but I couldn't process the network request.\"</item>\n        <item>\"I couldn't process the network request.\"</item>\n        <item>\"Sorry %1$s, but there's an issue with your network connection.\"</item>\n        <item>\"There's a issue with your network connection.\"</item>\n        <item>\"Sorry %1$s, but there's an issue with your network connectivity.\"</item>\n        <item>\"There's a issue with your network connectivity.\"</item>\n    </string-array>\n\n    <!-- Speech - No memory  -->\n    <string-array name=\"array_no_memory\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but I've completely forgotten what we were talking about.\"</item>\n        <item>\"Sorry %1$s, but I've completely forgotten what we were talking about.\"</item>\n        <item>\"Sorry %1$s, but I've no idea what we were talking about.\"</item>\n        <item>\"Sorry %1$s, but I've no idea what we were saying.\"</item>\n        <item>\"Sorry %1$s, I was busy looking at your %2$s and completely forgot what we were saying.\"</item>\n        <item>\"Sorry %1$s, I was busy looking at your %2$s and completely forgot what we were talking about.\"</item>\n        <item>\"Sorry %1$s, I was busy looking at your %2$s and I've no idea what we were saying.\"</item>\n        <item>\"Sorry %1$s, I was busy looking at your %2$s and I've no idea what we were talking about.\"</item>\n        <item>\"Sorry %1$s, but my mind is like a sieve today and I can't remember what we were saying.\"</item>\n        <item>\"Sorry %1$s, but my mind is like a sieve today and I can't remember what we were talking about.\"</item>\n    </string-array>\n\n    <!-- Speech - No memory  -->\n    <string name=\"memory_extra_twitter\" translatable=\"true\">\"Twitter feed\"</string>\n    <string name=\"memory_extra_facebook\" translatable=\"true\">\"Facebook stream\"</string>\n    <string name=\"memory_extra_tinder\" translatable=\"true\">\"Tinder matches\"</string>\n    <string name=\"memory_extra_snapchat\" translatable=\"true\">\"Snapchat pictures\"</string>\n    <string name=\"memory_extra_whatsapp\" translatable=\"true\">\"Whatsapp messages\"</string>\n    <string name=\"memory_extra_1\" translatable=\"true\">\"browser history\"</string>\n    <string name=\"memory_extra_2\" translatable=\"true\">\"photos\"</string>\n    <string name=\"memory_extra_3\" translatable=\"true\">\"text messages\"</string>\n    <string name=\"memory_extra_4\" translatable=\"true\">\"emails\"</string>\n\n    <string-array name=\"array_no_memory_extra\" translatable=\"true\">\n        <item>@string/memory_extra_1</item>\n        <item>@string/memory_extra_2</item>\n        <item>@string/memory_extra_3</item>\n        <item>@string/memory_extra_4</item>\n        <item>@string/memory_extra_twitter</item>\n        <item>@string/memory_extra_facebook</item>\n        <item>@string/memory_extra_tinder</item>\n        <item>@string/memory_extra_snapchat</item>\n        <item>@string/memory_extra_whatsapp</item>\n    </string-array>\n\n    <string-array name=\"array_no_memory_extra_unknown\" translatable=\"true\">\n        <item>@string/memory_extra_1</item>\n        <item>@string/memory_extra_2</item>\n        <item>@string/memory_extra_3</item>\n        <item>@string/memory_extra_4</item>\n    </string-array>\n\n    <string name=\"cancelled\" translatable=\"true\">\"cancelled\"</string>\n    <string name=\"okay\" translatable=\"true\">\"okay\"</string>\n    <string name=\"alright\" translatable=\"true\">\"alright\"</string>\n    <string name=\"understood\" translatable=\"true\">\"understood\"</string>\n    <string name=\"right_you_are\" translatable=\"true\">\"right you are\"</string>\n    <string name=\"okey_dokey\" translatable=\"true\">\"okey dokey\"</string>\n\n    <!-- Speech - Cancelled  -->\n    <string-array name=\"array_cancel\" translatable=\"true\">\n        <item>@string/cancelled</item>\n        <item>@string/okay</item>\n        <item>@string/alright</item>\n        <item>@string/understood</item>\n        <item>@string/right_you_are</item>\n        <item>@string/okey_dokey</item>\n    </string-array>\n\n    <!-- Speech - Copied to clipboard  -->\n    <string-array name=\"array_clipboard_copy\" translatable=\"true\">\n        <item>\"I've copied it to the clipboard for you.\"</item>\n        <item>\"That's copied to the clipboard.\"</item>\n        <item>\"I've put it in the clipboard data.\"</item>\n        <item>\"You can paste it from the clipboard\"</item>\n        <item>\"That's copied to the clipboard, in case you need it.\"</item>\n    </string-array>\n\n    <!-- Speech - Clipboard access error -->\n    <string-array name=\"array_error_clipboard_access\" translatable=\"true\">\n        <item>\"There was a problem accessing your clipboard.\"</item>\n        <item>\"Sorry, but I was unable to access the clipboard data.\"</item>\n        <item>\"Sorry, but I was unable to access any clipboard data.\"</item>\n        <item>\"I was unable to access any clipboard data.\"</item>\n        <item>\"I was unable to access the clipboard data\"</item>\n        <item>\"I don't seem to be able to access your clipboard data at the moment.\"</item>\n    </string-array>\n\n    <!-- Speech - Clipboard data error -->\n    <string-array name=\"array_error_clipboard_data\" translatable=\"true\">\n        <item>\"The clipboard does not appear to contain any data.\"</item>\n        <item>\"I couldn't detect any data held on the clipboard.\"</item>\n        <item>\"The clipboard appears to be void of any data.\"</item>\n        <item>\"I'm afraid I can't find any data on the clipboard.\"</item>\n    </string-array>\n\n    <!-- Speech - Spell error  -->\n    <string-array name=\"array_error_spell\" translatable=\"true\">\n        <item>\"Sorry, but I didn't detect a word to spell.\"</item>\n        <item>\"I didn't detect the word you wanted to spell in the voice data.\"</item>\n        <item>\"I didn't detect the word you wanted to spell.\"</item>\n        <item>\"Sorry, I'm not sure what you wanted to spell.\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal enrollment error  -->\n    <string-array name=\"array_error_enrollment\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but the verification process failed.\"</item>\n        <item>\"Sorry %1$s, the verification process failed.\"</item>\n        <item>\"Sorry %1$s, but there was a problem with the verification process.\"</item>\n        <item>\"Really sorry %1$s, there was a problem with the verification process.\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal id error  -->\n    <string-array name=\"array_error_vocal_id\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but I was unable to process the audio successfully.\"</item>\n        <item>\"Sorry %1$s, there was a problem when processing the audio.\"</item>\n        <item>\"Sorry %1$s, but an error occurred when I was processing the audio.\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal id high -->\n    <string-array name=\"array_vocal_id_high\" translatable=\"true\">\n        <item>\"I can say with a high level of certainty, that it was you speaking, %1$s\"</item>\n        <item>\"I can say with a high level of probability, that it was you speaking, %1$s\"</item>\n        <item>\"There was a high probability, that it was you speaking, %1$s\"</item>\n        <item>\"I'm certain that it was you speaking, %1$s\"</item>\n        <item>\"I'd recognise that voice anywhere, %1$s. It was you.\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal id medium -->\n    <string-array name=\"array_vocal_id_medium\" translatable=\"true\">\n        <item>\"I can say with only a medium to low level of certainty, that it was you speaking, %1$s\"</item>\n        <item>\"I can say with only a medium to low level of probability, that it was you speaking, %1$s\"</item>\n        <item>\"There is only a medium to low probability, that it was you speaking, %1$s\"</item>\n    </string-array>\n\n    <!-- Speech - Vocal id low -->\n    <string-array name=\"array_vocal_id_low\" translatable=\"true\">\n        <item>\"I was unable to verify the voice footprint.\"</item>\n        <item>\"I could not match the voice footprint.\"</item>\n        <item>\"There was no match for the voice footprint.\"</item>\n        <item>\"I was unable to successfully match the voice footprint.\"</item>\n        <item>\"I was unable to verify that speaker.\"</item>\n        <item>\"I did not recognise that speaker.\"</item>\n    </string-array>\n\n    <!-- Introduction - name -->\n    <string name=\"master\" translatable=\"true\">\"Master\"</string>\n\n\n    <!-- GENERIC STRINGS - GENERIC STRINGS - GENERIC STRINGS -->\n    <string name=\"_and\" translatable=\"true\">\" and\"</string>\n    <string name=\"low\" translatable=\"true\">\"low\"</string>\n    <string name=\"medium\" translatable=\"true\">\"medium\"</string>\n    <string name=\"high\" translatable=\"true\">\"high\"</string>\n    <string name=\"negative\" translatable=\"true\">\"negative\"</string>\n    <string name=\"neutral\" translatable=\"true\">\"neutral\"</string>\n    <string name=\"positive\" translatable=\"true\">\"positive\"</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"md_title_color\">@color/colorPrimaryDark</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"AppTheme.ActivityDialogTheme\" parent=\"@android:style/Theme.Holo.Light.Dialog.NoActionBar\">\n        <item name=\"android:windowActionBar\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\"/>\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\"/>\n\n    <style name=\"dialog_animation_left\">\n        <item name=\"android:windowEnterAnimation\">@android:anim/slide_in_left</item>\n        <item name=\"android:windowExitAnimation\">@android:anim/slide_out_right</item>\n    </style>\n\n    <style name=\"dialog_animation_right\">\n        <item name=\"android:windowEnterAnimation\">@anim/slide_in_right</item>\n        <item name=\"android:windowExitAnimation\">@anim/slide_out_left</item>\n    </style>\n\n    <style name=\"list_item_single\">\n        <item name=\"android:clickable\">true</item>\n        <item name=\"android:paddingTop\">10dip</item>\n        <item name=\"android:paddingBottom\">10dip</item>\n        <item name=\"android:gravity\">center_vertical</item>\n    </style>\n\n    <style name=\"card_view_item_single\">\n        <item name=\"android:paddingTop\">5dip</item>\n        <item name=\"android:paddingBottom\">5dip</item>\n        <item name=\"android:gravity\">center_vertical</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-en-rUS/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n\n    <string name=\"title_customisation\" translatable=\"true\">Customization</string>\n    <string name=\"error_hotword\" translatable=\"true\">\"Hotword engine failed to initialize :(\"</string>\n    <string name=\"menu_synthesised_voice\" translatable=\"true\">Google Synthesized Voice</string>\n\n    <string name=\"notification_initialising_tts\" translatable=\"true\">\"Initializing TTS\"</string>\n\n    <string name=\"cancelled\" translatable=\"true\">\"cancelled\"</string>\n    <string name=\"analyse\" translatable=\"true\">\"analyze\"</string>\n    <string name=\"vocal_notify_verify\" translatable=\"true\">\"Okay. I'll notify you know when I've analyzed the audio.\"</string>\n\n\n    <string name=\"bug_content_delay_speech\" translatable=\"true\">Text to speech engines take up a lot of resource\n        and consequently can take a while to initialize and be ready to speak. This is more common on higher\n        quality engines, regardless of whether they are downloaded and installed on the device or not.\\n\\nIn the\n        Superuser Settings, under Memory Usage, you can adjust how long the text to speech engine is bound to\n        Saiy and kept in the memory. The longer allowed time, the less likely the initialization process\n        will be required for subsequent commands\\n\\nThis additional memory usage\n        should be negligible to the performance of any modern device. If Android needs the memory back,\n        it will take it!\\n\\nIn the Saiy Settings, if you have selected to use the network synthesized voices\n        (available via Google\\'s text to speech engine), a delay can occur due to the speed of your network\n        connection. If your mobile connectivity is a little hit and miss, disabling this feature unless you are\n        connected to WiFi, could certainly help…\\n\\nTap for settings. Tap and hold for Superuser Settings</string>\n\n    <!-- Speech - No command was understood -->\n    <string-array name=\"array_no_comprende\" translatable=\"true\">\n        <item>\"Really sorry %1$s, but I'm not sure what you meant by that.\"</item>\n        <item>\"Really sorry, but I'm not sure what you meant by that %1$s.\"</item>\n        <item>\"I'm sorry %1$s, but I didn't recognize that command.\"</item>\n        <item>\"I'm sorry, but I didn't recognize that command  %1$s.\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal - error -->\n    <string-array name=\"array_beyond_verbal_error\" translatable=\"true\">\n        <item>\"Sorry %1$s, but I'm afraid the emotion analysis failed.\"</item>\n        <item>\"Sorry %1$s, but I'm afraid I was unable to analyze the emotions of the audio.\"</item>\n        <item>\"Apologies %1$s, but I failed to successfully analyze the emotional content.\"</item>\n    </string-array>\n\n    <!-- Speech - Beyond Verbal -->\n    <string-array name=\"array_beyond_verbal\" translatable=\"true\">\n        <item>\"OK %1$s. I'll be listening.\"</item>\n        <item>\"OK %1$s. I'm ready to analyze you.\"</item>\n    </string-array>\n\n    <string name=\"beyond_verbal_extra_verbose\" translatable=\"true\">\"OK %1$s. In order for me to analyze\n        your emotional state, you need to speak for at least thirteen seconds. After that, I'll\n        detect when you've finished speaking. You could tell me about your day today, or about a\n        memory from your past. If you can't think of anything, just close your eyes and count slowly\n        up to fifteen, whilst your mind wanders. I'll let you know with a notification when the\n        analysis is complete.\"</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v21/styles.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<!--\n  ~ Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n  ~\n  ~ This program is free software: you can redistribute it and/or modify\n  ~ it under the terms of the GNU Affero General Public License as published\n  ~ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n  ~\n  ~ You should have received a copy of the GNU Affero General Public License\n  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.\n  -->\n\n<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/accessibility_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Copyright (C) 2011 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\n<accessibility-service\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:canRetrieveWindowContent=\"true\"\n    android:description=\"@string/accessibility_description\"\n    android:settingsActivity=\"ai.saiy.android.ui.activity.MainActivity\"/>\n"
  },
  {
    "path": "app/src/main/res/xml/interaction_service.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/**\n * Copyright (c) 2014, 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-->\n<voice-interaction-service xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                           xmlns:tools=\"http://schemas.android.com/tools\"\n                           android:sessionService=\"ai.saiy.android.recognition.provider.saiy.assist.SaiyInteractionSessionService\"\n                           android:recognitionService=\"\"\n                           android:settingsActivity=\"ai.saiy.android.ui.activity.ActivityAssistSettings\"\n                           android:supportsAssist=\"true\"\n                           tools:targetApi=\"lollipop\"/>"
  },
  {
    "path": "app/src/test/java/ai/saiy/android/ExampleUnitTest.java",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage ai.saiy.android;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "/*\n * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 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 Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.1.0'\n        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2'\n\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "pocketsphinx-android-5prealpha-nolib/build.gradle",
    "content": "configurations.maybeCreate(\"default\")\nartifacts.add(\"default\", file('pocketsphinx-android-5prealpha-nolib.jar'))"
  }
]