diff --git a/.gitignore b/.gitignore index e511255..c737d54 100644 --- a/.gitignore +++ b/.gitignore @@ -41,17 +41,7 @@ captures/ # IntelliJ *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml +.idea/ # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41a..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 61a9130..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index a5f05cd..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index d5d35ec..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index e41eb3f..64a6f43 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # USB Gadget Tool -USB Gadget Tool Screenshot +USB Gadget Tool Screenshot Convert your Android phone to any USB device you like! USB Gadget Tool allows you to create and activate USB device roles, like a mouse or a keyboard. Connected USB hosts (e.g. a normal computer) will then identify your Android device only under that role. @@ -10,23 +10,33 @@ Following USB gadgets are integrated: * Keyboard & Mouse (/dev/hidg0, /dev/hidg1) * FIDO CTAP (/dev/hidg0; for WebAuthn) * CCID (/dev/ccid_ctrl, /dev/ccid_bulk) +* UVC camera (/dev/video?) USB Gadget Tool requires root permissions and a Kernel with ConfigFS support. Currently the app only enables the USB Gadget. For the usage of these device endpoints (e.g. /dev/hidg0) further apps are required (see Use-Cases). +[Get it on F-Droid](https://f-droid.org/packages/net.tjado.usbgadget/) + ## Use-Cases * [Authorizer](https://github.com/tejado/Authorizer) * [hid-gadget-test](https://github.com/pelya/android-keyboard-gadget) +## Features +* Comfortable USB gadget management (listing, adding and activating) +* Adding & activating USB Gadgets during boot +* Adding functions to USB Gadgets +* Device info (Kernel version and available gadgets in Kernel) +* Available in F-Droid store + ## Roadmap * Mount of /config if not available * Alert if ConfigFS is not supported -* Creating & enabling USB Gadgets during boot * Import custom USB Gadget profiles -* Adding functions to USB Gadgets * Example USB Gadget usage (USB Gadget Tool currently only manages USB Gadgets, not implementations of these) * Optional telemetry to understand better how all the Android vendors compile their kernel (e.g. what USB Gadgets are available) -* F-Droid & Google Play Store distribution +* Google Play Store distribution ## How does it work? USB Gadget Tool uses ConfigFS - an userspace API inside the Linux Kernel - for creation of arbitrary USB composite devices. diff --git a/app/build.gradle b/app/build.gradle index 7dc37b1..16fccf7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' android { - compileSdkVersion 29 + compileSdkVersion 33 defaultConfig { applicationId "net.tjado.usbgadget" - minSdkVersion 26 - targetSdkVersion 29 - versionCode 3 - versionName "0.3" + minSdkVersion 23 + targetSdkVersion 30 + versionCode 4 + versionName "0.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -20,22 +21,31 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } + + buildFeatures { + viewBinding true + } + } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' + testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85cd48e..7a9a710 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/usbFunctionProfiles/CCID b/app/src/main/assets/usbFunctionProfiles/CCID index 7b83f72..da0d9f5 100644 --- a/app/src/main/assets/usbFunctionProfiles/CCID +++ b/app/src/main/assets/usbFunctionProfiles/CCID @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="keyboard" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/CTAP b/app/src/main/assets/usbFunctionProfiles/CTAP index de06145..18f1f8c 100644 --- a/app/src/main/assets/usbFunctionProfiles/CTAP +++ b/app/src/main/assets/usbFunctionProfiles/CTAP @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="keyboard" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/Keyboard b/app/src/main/assets/usbFunctionProfiles/Keyboard index 6283238..18cf54e 100644 --- a/app/src/main/assets/usbFunctionProfiles/Keyboard +++ b/app/src/main/assets/usbFunctionProfiles/Keyboard @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="keyboard" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/Mouse b/app/src/main/assets/usbFunctionProfiles/Mouse index 09c5fa7..627a1ad 100644 --- a/app/src/main/assets/usbFunctionProfiles/Mouse +++ b/app/src/main/assets/usbFunctionProfiles/Mouse @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="keyboard" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/UVC b/app/src/main/assets/usbFunctionProfiles/UVC new file mode 100644 index 0000000..9557d17 --- /dev/null +++ b/app/src/main/assets/usbFunctionProfiles/UVC @@ -0,0 +1,103 @@ +#!/bin/sh + +GADGET_PATH="____gadgetPath____" + +cd $GADGET_PATH/configs/ +CONFIG_PATH="$GADGET_PATH/configs/`ls -1 | head -1`/" +STRINGS_PATH="$GADGET_PATH/strings/0x409/" +FUNCTION_PATH="$GADGET_PATH/functions/uvc.usb0" + +mkdir -p $FUNCTION_PATH +cd $FUNCTION_PATH + +echo 3072 > streaming_maxpacket +echo 10 > streaming_maxburst + +mkdir -p control/header/h +ln -s control/header/h control/class/fs +ln -s control/header/h control/class/ss +ln -s control/header/h control/class/hs + +# uncompressed +mkdir -p $FUNCTION_PATH/streaming/uncompressed/u/360p +cd $FUNCTION_PATH/streaming/uncompressed/u/360p +echo "666666\n1000000\n5000000\n" > dwFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/uncompressed/u/720p +cd $FUNCTION_PATH/streaming/uncompressed/u/720p +echo 1280 > wWidth +echo 720 > wHeight +echo 29491200 > dwMinBitRate +echo 29491200 > dwMaxBitRate +echo 1843200 > dwMaxVideoFrameBufferSize +echo "5000000\n" > dwFrameInterval +echo 5000000 > dwDefaultFrameInterval + + +# mjpeg +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/360p +cd $FUNCTION_PATH/streaming/mjpeg/m/360p +echo 640 > wWidth +echo 360 > wHeight +echo 18432000 > dwMinBitRate +echo 55296000 > dwMaxBitRate +echo 460800 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/720p +cd $FUNCTION_PATH/streaming/mjpeg/m/720p +echo 1280 > wWidth +echo 720 > wHeight +echo 29491200 > dwMinBitRate +echo 29491200 > dwMaxBitRate +echo 1843200 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/1080p +cd $FUNCTION_PATH/streaming/mjpeg/m/1080p +echo 1920 > wWidth +echo 1080 > wHeight +echo 66355200 > dwMinBitRate +echo 995328000 > dwMaxBitRate +echo 4147200 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/2160p +cd $FUNCTION_PATH/streaming/mjpeg/m/2160p +echo 3840 > wWidth +echo 2160 > wHeight +echo 265420800 > dwMinBitRate +echo 3981312000 > dwMaxBitRate +echo 16588800 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + + +# h264 +mkdir -p $FUNCTION_PATH/streaming/h264/h/960p +cd $FUNCTION_PATH/streaming/h264/h/960p +echo 1920 > wWidth +echo 960 > wHeight +echo 40 > bLevelIDC +echo "333667\n" > dwFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/h264/h/1920p +cd $FUNCTION_PATH/streaming/h264/h/1920p +echo "333667\n" > dwFrameInterval + + + +cd $FUNCTION_PATH +mkdir -p streaming/header/h +ln -s streaming/uncompressed/u streaming/header/h +ln -s streaming/mjpeg/m streaming/header/h +ln -s streaming/h264/h streaming/header/h +ln -s streaming/header/h streaming/class/fs/ +ln -s streaming/header/h streaming/class/hs/ +ln -s streaming/header/h streaming/class/ss/ + + +ln -s $FUNCTION_PATH $CONFIG_PATH/uvc.usb0 diff --git a/app/src/main/assets/usbGadgetProfiles/CCID b/app/src/main/assets/usbGadgetProfiles/CCID index d0f5ce6..1066cd7 100644 --- a/app/src/main/assets/usbGadgetProfiles/CCID +++ b/app/src/main/assets/usbGadgetProfiles/CCID @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="ccid3" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -27,6 +27,7 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 30 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration ln -s ${GADGET_PATH}/functions/ccid.usb0 $CONFIG_PATH/ccid.usb0 diff --git a/app/src/main/assets/usbGadgetProfiles/CTAP b/app/src/main/assets/usbGadgetProfiles/CTAP index ac5278a..5582648 100644 --- a/app/src/main/assets/usbGadgetProfiles/CTAP +++ b/app/src/main/assets/usbGadgetProfiles/CTAP @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="ctap3" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -42,6 +42,7 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 30 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration ln -s ${GADGET_PATH}/functions/hid.usb0 $CONFIG_PATH/hid.usb0 \ No newline at end of file diff --git a/app/src/main/assets/usbGadgetProfiles/Empty b/app/src/main/assets/usbGadgetProfiles/Empty index b3be8b2..9d09722 100644 --- a/app/src/main/assets/usbGadgetProfiles/Empty +++ b/app/src/main/assets/usbGadgetProfiles/Empty @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="gadget_${RANDOM}" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -23,5 +23,6 @@ echo "Android USB Gadget" > product echo "42" > serialnumber cd $CONFIG_PATH -echo 120 > MaxPower +echo 100 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration diff --git a/app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard b/app/src/main/assets/usbGadgetProfiles/MouseKeyboard similarity index 96% rename from app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard rename to app/src/main/assets/usbGadgetProfiles/MouseKeyboard index e47b1d8..b7c7d96 100644 --- a/app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard +++ b/app/src/main/assets/usbGadgetProfiles/MouseKeyboard @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="keyboard" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -49,7 +49,8 @@ echo "Android USB Gadget" > product echo "42" > serialnumber cd $CONFIG_PATH -echo 120 > MaxPower +echo 100 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration ln -s ${GADGET_PATH}/functions/hid.keyboard $CONFIG_PATH/hid.keyboard diff --git a/app/src/main/assets/usbGadgetProfiles/Serial b/app/src/main/assets/usbGadgetProfiles/Serial new file mode 100644 index 0000000..6e8a7ca --- /dev/null +++ b/app/src/main/assets/usbGadgetProfiles/Serial @@ -0,0 +1,39 @@ +#!/bin/sh + +CONFIGFS_DIR="/config" +GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" + +GADGET="____gadgetName____" +GADGET_PATH=${GADGETS_PATH}/${GADGET} + +CONFIG_PATH="$GADGET_PATH/configs/c.1/" +STRINGS_PATH="$GADGET_PATH/strings/0x409/" + +mkdir -p $CONFIG_PATH +mkdir -p $STRINGS_PATH + +FUNCTION_PATH_SERIAL="$GADGET_PATH/functions/acm.usb0" +mkdir -p "$FUNCTION_PATH_SERIAL" + +cd $GADGET_PATH +echo 0x1d6b > idVendor # Linux Foundation +echo 0x0104 > idProduct # Multifunction Composite Gadget +echo 0x0100 > bcdDevice # v1.0.0 +echo 0x0200 > bcdUSB # USB2 + +echo 0xEF > bDeviceClass +echo 0x02 > bDeviceSubClass +echo 0x01 > bDeviceProtocol + +cd $STRINGS_PATH +echo "tejado" > manufacturer +echo "Serial USB Gadget" > product +echo "42" > serialnumber + +cd $CONFIG_PATH +echo 100 > MaxPower +mkdir -p strings/0x409 +echo "Serial Config" > strings/0x409/configuration + +ln -s ${CONFIG_PATH} ${GADGET_PATH}/os_desc +ln -s $FUNCTION_PATH_SERIAL $CONFIG_PATH/ \ No newline at end of file diff --git a/app/src/main/assets/usbGadgetProfiles/UVC b/app/src/main/assets/usbGadgetProfiles/UVC new file mode 100644 index 0000000..7bc7574 --- /dev/null +++ b/app/src/main/assets/usbGadgetProfiles/UVC @@ -0,0 +1,129 @@ +#!/bin/sh + +CONFIGFS_DIR="/config" +GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" + +GADGET="____gadgetName____" +GADGET_PATH=${GADGETS_PATH}/${GADGET} + +CONFIG_PATH="$GADGET_PATH/configs/c.1/" +STRINGS_PATH="$GADGET_PATH/strings/0x409/" +FUNCTION_PATH="$GADGET_PATH/functions/uvc.usb0" + +mkdir -p $CONFIG_PATH +mkdir -p $STRINGS_PATH +mkdir -p $FUNCTION_PATH + +cd $FUNCTION_PATH + +echo 3072 > streaming_maxpacket +echo 10 > streaming_maxburst + +mkdir -p control/header/h +ln -s control/header/h control/class/fs +ln -s control/header/h control/class/ss +ln -s control/header/h control/class/hs + +# uncompressed +mkdir -p $FUNCTION_PATH/streaming/uncompressed/u/360p +cd $FUNCTION_PATH/streaming/uncompressed/u/360p +echo "666666\n1000000\n5000000\n" > dwFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/uncompressed/u/720p +cd $FUNCTION_PATH/streaming/uncompressed/u/720p +echo 1280 > wWidth +echo 720 > wHeight +echo 29491200 > dwMinBitRate +echo 29491200 > dwMaxBitRate +echo 1843200 > dwMaxVideoFrameBufferSize +echo "5000000\n" > dwFrameInterval +echo 5000000 > dwDefaultFrameInterval + + +# mjpeg +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/360p +cd $FUNCTION_PATH/streaming/mjpeg/m/360p +echo 640 > wWidth +echo 360 > wHeight +echo 18432000 > dwMinBitRate +echo 55296000 > dwMaxBitRate +echo 460800 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/720p +cd $FUNCTION_PATH/streaming/mjpeg/m/720p +echo 1280 > wWidth +echo 720 > wHeight +echo 29491200 > dwMinBitRate +echo 29491200 > dwMaxBitRate +echo 1843200 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/1080p +cd $FUNCTION_PATH/streaming/mjpeg/m/1080p +echo 1920 > wWidth +echo 1080 > wHeight +echo 66355200 > dwMinBitRate +echo 995328000 > dwMaxBitRate +echo 4147200 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/mjpeg/m/2160p +cd $FUNCTION_PATH/streaming/mjpeg/m/2160p +echo 3840 > wWidth +echo 2160 > wHeight +echo 265420800 > dwMinBitRate +echo 3981312000 > dwMaxBitRate +echo 16588800 > dwMaxVideoFrameBufferSize +echo "333333\n666666\n1000000\n5000000\n" > dwFrameInterval +echo 333333 > dwDefaultFrameInterval + + +# h264 +mkdir -p $FUNCTION_PATH/streaming/h264/h/960p +cd $FUNCTION_PATH/streaming/h264/h/960p +echo 1920 > wWidth +echo 960 > wHeight +echo 40 > bLevelIDC +echo "333667\n" > dwFrameInterval + +mkdir -p $FUNCTION_PATH/streaming/h264/h/1920p +cd $FUNCTION_PATH/streaming/h264/h/1920p +echo "333667\n" > dwFrameInterval + + + +cd $FUNCTION_PATH +mkdir -p streaming/header/h +ln -s streaming/uncompressed/u streaming/header/h +ln -s streaming/mjpeg/m streaming/header/h +ln -s streaming/h264/h streaming/header/h +ln -s streaming/header/h streaming/class/fs/ +ln -s streaming/header/h streaming/class/hs/ +ln -s streaming/header/h streaming/class/ss/ + + +cd $GADGET_PATH +echo 0x1d6b > idVendor # Linux Foundation +echo 0x0104 > idProduct # Multifunction Composite Gadget +echo 0x0100 > bcdDevice # v1.0.0 +echo 0x0200 > bcdUSB # USB2 + +echo 0xEF > bDeviceClass +echo 0x02 > bDeviceSubClass +echo 0x01 > bDeviceProtocol + +cd $STRINGS_PATH +echo "tejado" > manufacturer +echo "Android USB Camera" > product +echo "42" > serialnumber + +cd $CONFIG_PATH +echo 500 > MaxPower +mkdir -p strings/0x409 +echo "UVC Config" > strings/0x409/configuration + +ln -s ${GADGET_PATH}/functions/uvc.usb0 $CONFIG_PATH/uvc.usb0 \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..b900c92 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt b/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt new file mode 100644 index 0000000..6daa34f --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt @@ -0,0 +1,44 @@ +package net.tjado.usbgadget + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class BootConfiguration : BroadcastReceiver() { + private val TAG = "DEVICE_BOOT" + + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_BOOT_COMPLETED == intent.action) { + Log.i(TAG, "Intent ACTION_LOCKED_BOOT_COMPLETED") + val gmPreferences = GadgetManagerPreferences(context) + + val shellApi = GadgetShellApi() + if(!shellApi.hasRootSync()) { + Log.e(TAG, "No root permissions... aborting") + return + } + Log.i(TAG, "Seems root is available... proceeding...") + + val assets = GadgetAssetProfiles(context.applicationContext as Application) + val activeGadget = gmPreferences.getActiveBootGadget() + + for(profile in assets.allGadgetProfiles) { + if( gmPreferences.isAddedDuringBoot(profile) ) { + Log.i(TAG, "Adding gadget $profile") + + shellApi.exec(assets.getProfileGadget(profile)!!) { response -> + if (response != null && response.first == true && profile == activeGadget) { + Log.i(TAG, "Activating gadget $profile") + shellApi.activate("/config/usb_gadget/${profile}", null) + } else if (response != null && response.first == false) { + Log.e(TAG, "Activation failed: ${response.second}") + } + } + } + } + + Log.i(TAG, "Synchronous processing finished. ") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt b/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt new file mode 100644 index 0000000..772c362 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt @@ -0,0 +1,39 @@ +package net.tjado.usbgadget + +import kotlinx.coroutines.* + +// gist by Shanmuga Santhosh A +// https://gist.github.com/shanmugasanthosh7/417f413f359d10c8f4581542a11b3f91/ +abstract class CoroutineTask { + + protected open fun onPreExecute() {} + + protected abstract fun doInBackground(vararg params: Params): APair + + protected open fun onPostExecute(result: APair) {} + + private val mUiScope by lazy { CoroutineScope(Dispatchers.Main) } + + private lateinit var mJob: Job + + fun execute(vararg params: Params) { + mJob = mUiScope.launch { + // run on UI/Main thread + onPreExecute() + + // execute on worker thread + val result = async(Dispatchers.IO) { doInBackground(*params) } + + // return result on UI/Main thread + onPostExecute(result.await()) + } + } + + fun cancel() { + if (::mJob.isInitialized && mJob.isActive) mJob.cancel() + } + + fun cancelAll() { + if (mUiScope.isActive) mUiScope.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java deleted file mode 100644 index c0d7076..0000000 --- a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.tjado.usbgadget; - -import android.graphics.Typeface; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.fragment.app.Fragment; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.io.BufferedReader; -import java.io.StringReader; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -public class DeviceInfoFragment extends Fragment { - - private MutableLiveData> deviceData; - private View v; - - public DeviceInfoFragment() {} - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.v = inflater.inflate(R.layout.fragment_device_info, container, false); - - deviceData = new MutableLiveData<>(); - deviceData.setValue(new TreeMap<>(new DeviceInfoMapComparator())); - - deviceData.observe(getViewLifecycleOwner(), item -> { - this.loadData(item); - }); - - GadgetShellApi gsa = new GadgetShellApi(); - gsa.updateDeviceInfo(deviceData); - - return v; - } - - private void loadData(Map deviceData) { - - LinearLayout list = this.v.findViewById(R.id.list_device_data); - list.removeAllViews(); - - View viHead = getLayoutInflater().inflate(R.layout.row_device_info, null); - - TextView tvHeadName = viHead.findViewById(R.id.name); - tvHeadName.setText("Kernel Config Parameter"); - tvHeadName.setTypeface(null, Typeface.BOLD); - - TextView tvHeadValue = viHead.findViewById(R.id.value); - tvHeadValue.setText("Value"); - tvHeadValue.setTypeface(null, Typeface.BOLD); - list.addView(viHead); - - for (Map.Entry entry : deviceData.entrySet()) { - View vi = getLayoutInflater().inflate(R.layout.row_device_info, null); - - TextView tvName = vi.findViewById(R.id.name); - tvName.setText(entry.getKey().toUpperCase()); - - TextView tvValue = vi.findViewById(R.id.value); - String value = entry.getValue(); - - String color; - switch (value) { - case "y": - value = "Yes"; - color = "#008000"; - break; - case "n": - value = "No"; - color = "#ff0000"; - break; - case "NOT_SET": - value = "Not set"; - color = "#ff0000"; - break; - default: - color = "#000000"; - } - - tvValue.setText(Html.fromHtml(String.format("%s", color, value), Html.FROM_HTML_MODE_LEGACY)); - - list.addView(vi); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt new file mode 100644 index 0000000..fd307cd --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt @@ -0,0 +1,79 @@ +package net.tjado.usbgadget + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.graphics.Typeface +import android.text.Html +import android.view.View +import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import net.tjado.usbgadget.databinding.FragmentDeviceInfoBinding +import net.tjado.usbgadget.databinding.RowDeviceInfoBinding +import java.util.* + + +class DeviceInfoFragment : Fragment() { + private var _binding: FragmentDeviceInfoBinding? = null + private val binding get() = _binding!! + + private var deviceData: MutableLiveData?>? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentDeviceInfoBinding.inflate(inflater, container, false) + val view = binding.root + + deviceData = MutableLiveData() + deviceData!!.value = TreeMap(DeviceInfoMapComparator()) + deviceData!!.observe(viewLifecycleOwner) { item: TreeMap? -> loadData(item) } + val shellAPi = GadgetShellApi() + shellAPi.updateDeviceInfo(deviceData!!) + + return view + } + + private fun loadData(deviceData: Map?) { + binding.listDeviceData.removeAllViews() + + val rowHead = RowDeviceInfoBinding.inflate(layoutInflater) + rowHead.name.text = "Kernel Config Parameter" + rowHead.name.setTypeface(null, Typeface.BOLD) + rowHead.value.text = "Value" + rowHead.value.setTypeface(null, Typeface.BOLD) + + binding.listDeviceData.addView(rowHead.root) + + for (entry in deviceData!!.entries) { + val rowEntry = RowDeviceInfoBinding.inflate(layoutInflater) + rowEntry.name.text = entry.key.uppercase(Locale.getDefault()) + + var value = entry.value + var color: String + when (value) { + "y" -> { + value = "Yes" + color = "#008000" + } + "n" -> { + value = "No" + color = "#ff0000" + } + "NOT_SET" -> { + value = "Not set" + color = "#ff0000" + } + else -> color = "#000000" + } + rowEntry.value.text = Html.fromHtml( + String.format("%s", color, value), + Html.FROM_HTML_MODE_LEGACY + ) + + binding.listDeviceData.addView(rowEntry.root) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java deleted file mode 100644 index e6aeae9..0000000 --- a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.tjado.usbgadget; - -import java.util.Comparator; -import java.util.TreeMap; - -public class DeviceInfoMapComparator implements Comparator { - - @Override - public int compare(String s1, String s2) { - if (s1.equalsIgnoreCase("KERNEL_VERSION")) { - return -1; - } else if (s2.equalsIgnoreCase("KERNEL_VERSION")) { - return 1; - } else { - return s1.compareTo(s2); - } - } -} diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt new file mode 100644 index 0000000..4dada15 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt @@ -0,0 +1,15 @@ +package net.tjado.usbgadget + +import java.util.Comparator + +class DeviceInfoMapComparator : Comparator { + override fun compare(s1: String, s2: String): Int { + return if (s1.equals("KERNEL_VERSION", ignoreCase = true)) { + -1 + } else if (s2.equals("KERNEL_VERSION", ignoreCase = true)) { + 1 + } else { + s1.compareTo(s2) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java deleted file mode 100644 index b389d49..0000000 --- a/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Authorizer - * - * Copyright 2016 by Tjado Mäcke - * Licensed under GNU General Public License 3.0. - * - * @license GPL-3.0 - */ -package net.tjado.usbgadget; - -import android.util.Pair; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; - -/* - * Class by muzikant - * - * Reference: - * http://muzikant-android.blogspot.com/2011/02/how-to-get-root-access-and-execute.html - * http://stackoverflow.com/a/7102780 - */ - -public class ExecuteAsRootUtil -{ - - public static boolean canRunRootCommands() - { - boolean retval = false; - Process suProcess; - - try - { - suProcess = Runtime.getRuntime().exec("su"); - - DataOutputStream os = new DataOutputStream(suProcess.getOutputStream()); - DataInputStream osRes = new DataInputStream(suProcess.getInputStream()); - - if (null != os && null != osRes) - { - // Getting the id of the current user to check if this is root - os.writeBytes("id\n"); - os.flush(); - - String currUid = osRes.readLine(); - boolean exitSu = false; - if (null == currUid) - { - retval = false; - exitSu = false; - Log.d("ROOT", "Can't get root access or denied by user"); - } - else if (currUid.contains("uid=0")) - { - retval = true; - exitSu = true; - Log.d("ROOT", "Root access granted"); - } - else - { - retval = false; - exitSu = true; - Log.d("ROOT", "Root access rejected: " + currUid); - } - - if (exitSu) - { - os.writeBytes("exit\n"); - os.flush(); - } - } - } - catch (Exception e) - { - // Can't get root ! - // Probably broken pipe exception on trying to write to output stream (os) after su failed, meaning that the device is not rooted - - retval = false; - Log.d("ROOT", "Root access rejected [" + e.getClass().getName() + "] : " + e.getMessage()); - } - - return retval; - } - - public static Pair execute(String command) { - return execute(new String[]{ command }); - } - - public static Pair execute(String[] commands) - { - Boolean retval = false; - String output = null; - - try - { - if (commands != null && commands.length > 0) - { - Process suProcess = Runtime.getRuntime().exec("su -"); - - DataOutputStream stdin = new DataOutputStream(suProcess.getOutputStream()); - InputStream stdout = suProcess.getInputStream(); - InputStream stderr = suProcess.getErrorStream(); - - for (String cmd: commands) { - Log.d("ROOT", String.format("Execute command: %s", cmd)); - stdin.writeBytes(cmd); - stdin.flush(); - } - - stdin.writeBytes("exit\n"); - stdin.flush(); - - stdin.close(); - - StringBuffer sbOut = new StringBuffer(); - Scanner scanner = new Scanner(stdout); - while (scanner.hasNext()) { - sbOut.append(scanner.nextLine()); - sbOut.append(System.lineSeparator()); - } - - StringBuffer sbErr = new StringBuffer(); - Scanner scannerErr = new Scanner(stderr); - while (scannerErr.hasNext()) { - sbErr.append(scannerErr.nextLine()); - if (scannerErr.hasNext()) - sbErr.append(System.lineSeparator()); - } - - output = sbOut.toString(); - Log.d("ROOT (stdout)", output); - - String error = sbErr.toString(); - Log.d("ROOT (stderr)", error); - - retval = ! error.equals("Permission denied"); - } - } - catch (IOException | SecurityException ex) - { - Log.w("ROOT", "Can't get root access", ex); - } - catch (Exception ex) - { - Log.w("ROOT", "Error executing internal operation", ex); - } - - return new Pair(retval, output); - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.kt b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.kt new file mode 100644 index 0000000..efae1c2 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.kt @@ -0,0 +1,120 @@ +/** + * Authorizer + * + * Copyright 2016 by Tjado Mäcke @maecke.de> + * Licensed under GNU General Public License 3.0. + * + * @license GPL-3.0 //opensource.org/licenses/GPL-3.0> + */ +package net.tjado.usbgadget + +import androidx.core.util.Pair +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.lang.Exception +import java.util.* + +/* + * Class by muzikant + * + * Reference: + * http://muzikant-android.blogspot.com/2011/02/how-to-get-root-access-and-execute.html + * http://stackoverflow.com/a/7102780 + */ +object ExecuteAsRootUtil { + fun canRunRootCommands(): Boolean { + var retval = false + val suProcess: Process + try { + suProcess = Runtime.getRuntime().exec("su") + val os = DataOutputStream(suProcess.outputStream) + val osRes = DataInputStream(suProcess.inputStream) + + if (null != os && null != osRes) { + // Getting the id of the current user to check if this is root + os.writeBytes("id\n") + os.flush() + val currUid = osRes.readLine() + val exitSu: Boolean + + if (null == currUid) { + retval = false + exitSu = false + Log.d("ROOT", "Can't get root access or denied by user") + } else if (currUid.contains("uid=0")) { + retval = true + exitSu = true + Log.d("ROOT", "Root access granted") + } else { + retval = false + exitSu = true + Log.d("ROOT", "Root access rejected: $currUid") + } + + if (exitSu) { + os.writeBytes("\nexit\n") + os.flush() + } + } + } catch (e: Exception) { + // Can't get root ! + // Probably broken pipe exception on trying to write to output stream (os) after su failed, meaning that the device is not rooted + retval = false + Log.d("ROOT", "Root access rejected [" + e.javaClass.name + "] : " + e.message) + } + + return retval + } + + fun execute(command: String): Pair<*, *> { + return execute(arrayOf(command)) + } + + fun execute(commands: Array?): Pair<*, *> { + var retval = false + var output: String? = null + + try { + if (commands != null && commands.isNotEmpty()) { + val suProcess = Runtime.getRuntime().exec("su -") + val stdin = DataOutputStream(suProcess.outputStream) + val stdout = suProcess.inputStream + val stderr = suProcess.errorStream + for (cmd in commands) { + Log.d("ROOT", String.format("Execute command: %s", cmd)) + stdin.writeBytes(cmd) + stdin.flush() + } + stdin.writeBytes("\nexit\n") + stdin.flush() + stdin.close() + val sbOut = StringBuffer() + val scanner = Scanner(stdout) + while (scanner.hasNext()) { + sbOut.append(scanner.nextLine()) + sbOut.append(System.lineSeparator()) + } + val sbErr = StringBuffer() + val scannerErr = Scanner(stderr) + while (scannerErr.hasNext()) { + sbErr.append(scannerErr.nextLine()) + if (scannerErr.hasNext()) sbErr.append(System.lineSeparator()) + } + output = sbOut.toString() + Log.d("ROOT (stdout)", output) + val error = sbErr.toString() + Log.d("ROOT (stderr)", error) + retval = error != "Permission denied" + } + } catch (ex: IOException) { + Log.w("ROOT", "Can't get root access", ex) + } catch (ex: SecurityException) { + Log.w("ROOT", "Can't get root access", ex) + } catch (ex: Exception) { + Log.w("ROOT", "Error executing internal operation", ex) + } + + return Pair(retval, output) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java deleted file mode 100644 index 0da62ca..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java +++ /dev/null @@ -1,108 +0,0 @@ -package net.tjado.usbgadget; - -import android.app.Activity; -import android.graphics.Color; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.cardview.widget.CardView; -import androidx.recyclerview.widget.RecyclerView; - -import java.lang.ref.WeakReference; -import java.util.List; - -public class GadgetAdapter extends RecyclerView.Adapter { - private Activity context; - private LayoutInflater inflater; - private final GadgetAdapterClickInterface listener; - List data; - - public GadgetAdapter(Activity context, List data, GadgetAdapterClickInterface clickListener) { - this.context = context; - this.data = data; - listener = clickListener; - } - - // Inflate the layout when viewholder created - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - - View view = inflater.from(parent.getContext()). - inflate(R.layout.cardview_gadget, parent, false); - return new DefaultViewHolder(view, listener); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - - initLayoutDefault((DefaultViewHolder) holder, position); - } - - - @Override - public int getItemCount() { - return data.size(); - } - - private void initLayoutDefault(DefaultViewHolder holder, int pos) { - holder.gadget = data.get(pos); - holder.manufacturer.setText(holder.gadget.getValue("manufacturer")); - holder.product.setText(holder.gadget.getValue("product")); - holder.serialnumber.setText(holder.gadget.getValue("serialnumber")); - holder.path.setText(holder.gadget.getValue("gadget_path")); - holder.functions.setText(Html.fromHtml(holder.gadget.getFormattedFunctions(), Html.FROM_HTML_MODE_LEGACY)); - - holder.status.setChecked(holder.gadget.isActivated()); - - String udc = holder.gadget.getValue("udc"); - holder.udc.setText(udc); - if (holder.gadget.isActivated()) { - holder.card.setCardBackgroundColor(Color.WHITE); - } else { - holder.card.setCardBackgroundColor(Color.LTGRAY); - } - - } - - // Static inner class to initialize the views of rows - static class DefaultViewHolder extends RecyclerView.ViewHolder { - GadgetObject gadget; - CardView card; - TextView manufacturer; - TextView product; - TextView serialnumber; - TextView udc; - TextView functions; - TextView path; - Switch status; - - private WeakReference listenerRef; - - public DefaultViewHolder(View itemView, GadgetAdapterClickInterface clickInterface) { - super(itemView); - card = itemView.findViewById(R.id.cv_gadget); - manufacturer = itemView.findViewById(R.id.tv_gadget_manufacturer); - product = itemView.findViewById(R.id.tv_gadget_product); - serialnumber = itemView.findViewById(R.id.tv_gadget_sn); - udc = itemView.findViewById(R.id.tv_gadget_udc); - path = itemView.findViewById(R.id.tv_gadget_path); - functions = itemView.findViewById(R.id.tv_gadget_functions); - status = itemView.findViewById(R.id.tv_gadget_status); - - status.setOnClickListener((buttonView) -> { - listenerRef.get().onStatusChange(gadget, status.isChecked()); - }); - - listenerRef = new WeakReference<>(clickInterface); - - itemView.setOnClickListener(view -> { - listenerRef.get().onGadgetClicked( gadget ); - }); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt new file mode 100644 index 0000000..f8a37ca --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt @@ -0,0 +1,76 @@ +package net.tjado.usbgadget + +import android.app.Activity +import android.graphics.Color +import android.text.Html +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import net.tjado.usbgadget.databinding.CardviewGadgetBinding +import java.lang.ref.WeakReference + + +class GadgetAdapter( + private val context: Activity, + private var data: List, + private val listener: GadgetAdapterClickInterface +) : RecyclerView.Adapter() { + private val inflater: LayoutInflater? = null + + private var _binding: CardviewGadgetBinding? = null + private val binding get() = _binding!! + + interface GadgetAdapterClickInterface { + fun onGadgetClicked(gadget: GadgetObject) + fun onStatusChange(gadget: GadgetObject, newStatus: Boolean) + } + + // Inflate the layout when viewholder created + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DefaultViewHolder { + _binding = CardviewGadgetBinding.inflate(LayoutInflater.from(parent.context), parent, false) + + return DefaultViewHolder(binding, listener) + } + + override fun onBindViewHolder(holder: DefaultViewHolder, position: Int) { + with(holder) { + gadget = data[position] + binding.tvGadgetManufacturer.text = gadget?.getValue("manufacturer") + binding.tvGadgetProduct.text = gadget?.getValue("product") + binding.tvGadgetSn.text = gadget?.getValue("serialnumber") + binding.tvGadgetPath.text = gadget?.getValue("gadget_path") + binding.tvGadgetFunctions.text = Html.fromHtml(holder.gadget?.formattedFunctions, Html.FROM_HTML_MODE_LEGACY) + binding.tvGadgetStatus.isChecked = gadget?.isActivated == true + + val udc = gadget!!.getValue("udc") + binding.tvGadgetUdc.text = udc + if (gadget?.isActivated == true) { + binding.cvGadget.setCardBackgroundColor(Color.WHITE) + } else { + binding.cvGadget.setCardBackgroundColor(Color.LTGRAY) + } + } + } + + override fun getItemCount(): Int { + return data.size + } + + // Static inner class to initialize the views of rows + inner class DefaultViewHolder(val binding: CardviewGadgetBinding, clickInterface: GadgetAdapterClickInterface) : RecyclerView.ViewHolder(binding.root) { + var gadget: GadgetObject? = null + private val listenerRef: WeakReference + + init { + listenerRef = WeakReference(clickInterface) + + binding.tvGadgetStatus.setOnClickListener { + gadget?.let { listenerRef.get()?.onStatusChange(it, binding.tvGadgetStatus.isChecked) } + } + + itemView.setOnClickListener { + gadget?.let { listenerRef.get()?.onGadgetClicked(it) } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java b/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java deleted file mode 100644 index 4141292..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.tjado.usbgadget; - -public interface GadgetAdapterClickInterface { - - void onGadgetClicked(GadgetObject gadget); - void onStatusChange( GadgetObject gadget, Boolean newStatus); -} diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java deleted file mode 100644 index 9f7699c..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.tjado.usbgadget; - -import android.app.Application; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Scanner; - -public class GadgetAssetProfiles { - - private Application app; - - public GadgetAssetProfiles(Application app) { - this.app = app; - } - - public String[] getAllGadgetProfiles() { - String [] gadgetProfileList; - - try { - gadgetProfileList = this.app.getAssets().list("usbGadgetProfiles/"); - } catch (IOException e) { - gadgetProfileList = null; - } - - return gadgetProfileList; - } - - public String[] getAllFunctionProfiles() { - String [] gadgetProfileList; - - try { - gadgetProfileList = this.app.getAssets().list("usbFunctionProfiles/"); - } catch (IOException e) { - gadgetProfileList = null; - } - - return gadgetProfileList; - } - - public String getProfileFromAsset(String profileFolder, String assetFile) { - try { - InputStream is = this.app.getAssets().open(String.format("%s/%s", profileFolder, assetFile)); - Scanner scanner = new Scanner(is); - - StringBuffer sb = new StringBuffer(); - while (scanner.hasNext()) { - sb.append(scanner.nextLine()).append("\n"); - } - - return sb.toString(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - public String getProfileGadget(String assetFile) { - return getProfileFromAsset("usbGadgetProfiles", assetFile); - } - - public String getProfileFunction(String assetFile, String gadgetPath) { - if (!GadgetShellApi.isValidGadgetPath(gadgetPath)) { - return null; - } - - Map tokens = new HashMap<>(); - tokens.put("gadgetPath", gadgetPath); - String profile = getProfileFromAsset("usbFunctionProfiles", assetFile); - - return parseAsset(profile, tokens); - } - - public String parseAsset(String profile, Map tokens) { - if (profile == null || profile.equals("")) { - return ""; - } - - if (tokens != null && tokens.size() > 0) { - for (Map.Entry token : tokens.entrySet()) { - profile = profile.replace("____" + token.getKey() + "____", token.getValue()); - } - } - - return profile; - } -} diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt new file mode 100644 index 0000000..c7af33c --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt @@ -0,0 +1,64 @@ +package net.tjado.usbgadget + +import android.app.Application +import android.text.TextUtils + + +class GadgetAssetProfiles(private val app: Application) { + + val allGadgetProfiles: Array + get() { + return app.assets.list("usbGadgetProfiles/") as Array + } + + val allFunctionProfiles: Array + get() { + return app.assets.list("usbFunctionProfiles/") as Array + } + + fun getProfileFromAsset(profileFolder: String, assetFile: String): String { + val inputStream = app.assets.open("$profileFolder/$assetFile") + return inputStream.bufferedReader().use { it.readText() } + } + + fun getProfileGadget(assetFile: String): String? { + return getProfileGadget(assetFile, assetFile) + } + + fun getProfileGadget(assetFile: String, gadgetName: String): String? { + if (TextUtils.isEmpty(gadgetName)) { + return null + } + + val tokens: MutableMap = HashMap() + tokens["gadgetName"] = gadgetName + val profile = getProfileFromAsset("usbGadgetProfiles", assetFile) + return parseAsset(profile, tokens) + } + + fun getProfileFunction(assetFile: String, gadgetPath: String): String? { + if (!GadgetShellApi.isValidGadgetPath(gadgetPath)) { + return null + } + + val tokens: MutableMap = HashMap() + tokens["gadgetPath"] = gadgetPath + val profile = getProfileFromAsset("usbFunctionProfiles", assetFile) + return parseAsset(profile, tokens) + } + + fun parseAsset(profile: String, tokens: Map): String { + if (TextUtils.isEmpty(profile)) { + return "" + } + + var parsedProfile = profile + if (tokens.isNotEmpty()) { + for ((key, value) in tokens) { + parsedProfile = parsedProfile.replace("____" + key + "____", value) + } + } + + return parsedProfile + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java deleted file mode 100644 index 9e9c813..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java +++ /dev/null @@ -1,125 +0,0 @@ -package net.tjado.usbgadget; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.Switch; -import android.widget.TextView; -import android.widget.ViewFlipper; - -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; - -import java.util.ArrayList; - -public class GadgetFragment extends Fragment { - - private GadgetViewModel gadgetViewModel; - private ViewFlipper rootFlipper; - private GadgetObject gadget; - private String[] functionProfileList; - private View view; - - public GadgetFragment(GadgetObject g) { - this.gadget = g; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - view = inflater.inflate(R.layout.fragment_gadget, container, false); - gadgetViewModel = new ViewModelProvider(getActivity()).get(GadgetViewModel.class); - - rootFlipper = view.findViewById(R.id.flipper); - - gadgetViewModel.getGadgets().observe(getViewLifecycleOwner(), item -> { - for (GadgetObject g : item) { - if (g.getValue("gadget_path").equals(this.gadget.getValue("gadget_path"))) { - this.gadget = g; - refreshData(); - } - } - }); - - gadgetViewModel.hasRootPermissions().observe(getViewLifecycleOwner(), item -> { - rootFlipper.setDisplayedChild(item ? 0 : 1); - }); - - GadgetAssetProfiles gap = new GadgetAssetProfiles(getActivity().getApplication()); - functionProfileList = gap.getAllFunctionProfiles(); - - Button addFunction = view.findViewById(R.id.btn_function_add); - addFunction.setOnClickListener(v -> { - final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext()); - alertBuilder.setTitle("Add Function to Gadget"); - alertBuilder.setCancelable(true); - - alertBuilder.setItems(functionProfileList, (dialog, which) -> { - gadgetViewModel.loadFunctionProfile(gadget, functionProfileList[which]); - }); - - alertBuilder.show(); - }); - - - refreshData(); - - return view; - } - - private void refreshData() { - View v = view; - TextView manufacturer = v.findViewById(R.id.tv_gadget_manufacturer); - TextView product = v.findViewById(R.id.tv_gadget_product); - TextView serialnumber = v.findViewById(R.id.tv_gadget_sn); - TextView udc = v.findViewById(R.id.tv_gadget_udc); - TextView path = v.findViewById(R.id.tv_gadget_path); - Switch status = v.findViewById(R.id.tv_gadget_status); - - manufacturer.setText(gadget.getValue("manufacturer")); - product.setText(gadget.getValue("product")); - serialnumber.setText(gadget.getValue("serialnumber")); - path.setText(gadget.getValue("gadget_path")); - udc.setText(gadget.getValue("udc")); - - status.setChecked(gadget.isActivated()); - status.setOnClickListener((buttonView) -> { - if(status.isChecked()) { - gadget.activate(response -> { - gadgetViewModel.updateGadgetData(); - }); - } else { - gadget.deactivate(response -> { - gadgetViewModel.updateGadgetData(); - }); - } - }); - - LinearLayout list = v.findViewById(R.id.list_functons); - list.removeAllViews(); - - ArrayList functions = gadget.getFunctions(); - for(String functionName : functions) { - View vi = getLayoutInflater().inflate(R.layout.row_function, null); - - TextView tv = vi.findViewById(R.id.name); - tv.setText(functionName); - - Switch f_status = vi.findViewById(R.id.status); - f_status.setChecked( gadget.getActiveFunctions().contains(functionName) ); - f_status.setOnClickListener((buttonView) -> { - if (f_status.isChecked()){ - gadget.activateFunction(functionName, false); - } else { - gadget.deactivateFunction(functionName, true); - } - gadgetViewModel.reloadGadgetData(); - }); - - list.addView(vi); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt new file mode 100644 index 0000000..98be221 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt @@ -0,0 +1,101 @@ +package net.tjado.usbgadget + + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle + +import android.view.View +import android.widget.* +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.Fragment +import net.tjado.usbgadget.databinding.FragmentGadgetBinding +import net.tjado.usbgadget.databinding.RowFunctionBinding + + +class GadgetFragment(private var gadget: GadgetObject) : Fragment() { + private var _binding: FragmentGadgetBinding? = null + private val binding get() = _binding!! + + private lateinit var gadgetViewModel: GadgetViewModel + private lateinit var functionProfileList: Array + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentGadgetBinding.inflate(inflater, container, false) + val view = binding.root + + gadgetViewModel = ViewModelProvider(requireActivity()).get(GadgetViewModel::class.java) + gadgetViewModel.gadgets.observe(viewLifecycleOwner) { item: List -> + for (g in item) { + if (g.getValue("gadget_path") == gadget.getValue("gadget_path")) { + gadget = g + refreshData() + } + } + } + + gadgetViewModel.hasRootPermissions().observe(viewLifecycleOwner) { + item -> binding.flipper.displayedChild = if (item) 0 else 1 + } + + val assets = GadgetAssetProfiles(requireActivity().application) + functionProfileList = assets.allFunctionProfiles + + val addFunction = view.findViewById